如何清理或转义 realpath 或 readlink 返回的绝对路径?

Ano*_*non 9 command-line bash paths

realpathreadlink返回绝对路径:

+akiva@X230:~$ realpath ZannaIsAwesome
/home/akiva/ZannaIsAwesome
Run Code Online (Sandbox Code Playgroud)

像这样的路径很容易处理。但是,这样的事情会有一些问题:

在此处输入图片说明

例如:

在此处输入图片说明

因此,需要对这样的名称进行清理,以便能够将其提供给其他命令。用例可能是这样的:

+a@X230:~/\e[92mM@r|< $hu+'|'|_e|\|\|0rth [`-_-"]$ bacon=$(realpath pullingATerdon)
+a@X230:~$ vim $bacon 
Run Code Online (Sandbox Code Playgroud)

不用说,vim $bacon不会按预期工作。

我该怎么做才能清理该绝对路径,以便它可以与其他命令一起使用?

ter*_*don 12

如何正确执行此操作

首先,始终引用您的变量。如果您正确引用它,您正在尝试做的事情会很好:

$ pwd
/home/terdon/foo/\e[92mM@r|< +'|'|_e|\|\|0rth [`-_-"]
$ ls
pullingATerdon
Run Code Online (Sandbox Code Playgroud)

为了保持一致性,我保留了您选择的奇怪文件名(虽然我不知道您为什么选择它)。

现在,让我们将 的路径分配pullingATerdon给一个变量,然后尝试打开该文件:

$ bacon="$(realpath pullingATerdon)"
$ echo "$bacon"
/home/terdon/foo/\e[92mM@r|< +'|'|_e|\|\|0rth [`-_-"]/pullingATerdon
$ ls $bacon
ls: cannot access '+'\''|'\''|_e|\|\|0rth': No such file or directory
ls: cannot access '[`-_-"]/pullingATerdon': No such file or directory
'/home/terdon/foo/\e[92mM@r|<':
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,这失败了。但是,如果我们现在正确引用它:

$ ls -l "$bacon"
-rw-r--r-- 1 terdon terdon 0 Mar 14 23:15 '/home/terdon/foo/\e[92mM@r|< +'\''|'\''|_e|\|\|0rth [`-_-"]/pullingATerdon'
Run Code Online (Sandbox Code Playgroud)

它按预期工作。是的,您也可以在(适当的)编辑器中打开路径:emacs "$bacon"会工作得很好。好的,也会vim和其他任何事情。您选择的编辑器虽然很遗憾,但无关紧要。


为什么你的失败了

跟踪在您的情况下实际发生的情况的一种快速方法是使用set -x(再次使用将其关闭set +x),这会导致 shell 在运行它之前打印它将运行的每个命令。使用以下命令打开 shell 的调试消息set -x

$ set -x
$ /bin/ls $bacon 
+ ls '/home/terdon/foo/\e[92mM@r|<' '+'\''|'\''|_e|\|\|0rth' '[`-_-"]/pullingATerdon'
ls: cannot access '+'\''|'\''|_e|\|\|0rth': No such file or directory
ls: cannot access '[`-_-"]/pullingATerdon': No such file or directory
'/home/terdon/foo/\e[92mM@r|<':
Run Code Online (Sandbox Code Playgroud)

这向我们展示了ls使用三个单独的参数运行的:'/home/terdon/foo/\e[92mM@r|<','+'\''|'\''|_e|\|\|0rth''[`-_-"]/pullingATerdon'。发生这种情况是因为 shell对未加引号的字符串执行分词和全局扩展。在这种情况下,问题是分词,因为 shell 看到路径中的空格并将每个空格分隔的字符串作为单独的参数读取。

mkdir示例略有不同,但这是因为您向我们展示了第二次调用命令的错误消息。我猜你试过一次,然后第二次运行它以获得你的问题的输出。第一次运行它时,它看起来像这样:

$ mkdir $(realpath pullingATerdon)
++ realpath pullingATerdon
+ mkdir '/home/terdon/foo/\e[92mM@r|<' '+'\''|'\''|_e|\|\|0rth' '[`-_-"]/pullingATerdon'
mkdir: cannot create directory ‘[`-_-"]/pullingATerdon’: No such file or directory
Run Code Online (Sandbox Code Playgroud)

同样,由于分词,这将尝试创建三个目录,而不是一个。首先,它创建(成功)目录/home/terdon/foo/\e[92mM@r|<

$ ls -l /home/terdon/foo/
total 8
drwxr-xr-x 2 terdon terdon 4096 Mar 15 00:20 '\e[92mM@r|<'
drwxr-xr-x 3 terdon terdon 4096 Mar 15 00:20 '\e[92mM@r|< +'\''|'\''|_e|\|\|0rth [`-_-"]'
Run Code Online (Sandbox Code Playgroud)

然后,它也成功地+'|'|_e|\|\|0rth在当前目录中创建了一个名为的目录:

$ ls -l
total 4
drwxr-xr-x 2 terdon terdon 4096 Mar 15 00:37 '+'\''|'\''|_e|\|\|0rth'
-rw-r--r-- 1 terdon terdon    0 Mar 15 00:36  pullingATerdon
Run Code Online (Sandbox Code Playgroud)

然后,它尝试创建目录[`-_-"]/pullingATerdon。这失败了,因为mkdir,默认情况下,不创建子目录(它可以,如果你运行它-p):

$ mkdir baz/bar
mkdir: cannot create directory ‘baz/bar’: No such file or directory
Run Code Online (Sandbox Code Playgroud)

由于您的未加引号的字符串包含一个/mkdir认为是两个目录的路径,试图找到最上面的一个,但失败了。

这就是它失败的原因,但发生的事情更复杂。您使用的字符串实际上是一个 shell glob,特别是一个glob range,它匹配当前目录中名称为`-_或5 个字符之一的所有文件"。由于当前目录中没有此类文件,因此 glob 不匹配任何内容,并且与 bash 中的默认行为一样,返回自身:

$ ls -l
total 4
drwxr-xr-x 2 terdon terdon 4096 Mar 15 00:37 '+'\''|'\''|_e|\|\|0rth'
-rw-r--r-- 1 terdon terdon    0 Mar 15 00:36  pullingATerdon
Run Code Online (Sandbox Code Playgroud)

澄清一下,如果你给出一个匹配某些东西的 glob 会发生什么:

$ echo [p]*   ## any filename starting with a p
pullingATerdon
$ echo "[p]*" ## the string "[p]*"
[p]*
Run Code Online (Sandbox Code Playgroud)

不带引号的[p*]被扩展为匹配文件名的列表(在这种情况下只有一个),这就是传递给echo. 您应该引用所有内容的另一个原因。

最后,您显示的实际错误来自您第二次运行该命令,并且在尝试 create 时在第一步失败/home/terdon/foo/\e[92mM@r|<,因为之前的调用已经创建了该目录。


更一般地,每当您发现自己使用任意文件名时,请始终使用 shell glob。像这样的事情:

for file in *; do command "$file"; done
Run Code Online (Sandbox Code Playgroud)

这适用于任何文件名。不管它碰巧包含什么。在我们上面的例子中,你可以这样做:

emacs /home/terdon/*92mM*/pullingATerdon
Run Code Online (Sandbox Code Playgroud)

任何唯一标识目标文件的 glob 都可以。这样,您无需担心特殊字符,只需让 shell 处理它们即可。


一些有用的参考资料:

  1. 如何查找并安全地处理包含换行符、空格或两者的文件名?:优秀的 Gray Cat's Wiki 上的常见问题解答之一。

  2. 忘记在 bash/POSIX shell 中引用变量的安全隐患:我在本答案开头引用的同一篇文章。如果您未能正确引用您的 shell 变量,可能会出错的所有事情的详细解释。

  3. 为什么我的 shell 脚本会因空格或其他特殊字符而阻塞?:你想知道的关于在 shell 中处理任意文件名的一切。

  4. 什么时候需要双引号?:更多关于引号和变量的信息,特别是一些不需要引用它们的情况