我如何清理或逃避 realpath 或 readlink 返回的绝对路径?

我如何清理或逃避 realpath 或 readlink 返回的绝对路径?

realpathreadlink返回绝对路径:

+akiva@X230:~$ realpath ZannaIsAwesome
/home/akiva/ZannaIsAwesome

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

在此处输入图片描述

例如:

在此处输入图片描述

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

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

不用说,vim $bacon不会像预期的那样发挥作用。

我该怎么做才能净化这个绝对路径,以便它可以与其他命令一起工作?

答案1

如何正确执行此操作

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

$ pwd
/home/terdon/foo/\e[92mM@r|< +'|'|_e|\|\|0rth [`-_-"]
$ ls
pullingATerdon

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

现在,让我们将路径分配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|<':

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

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

它按预期工作。是的,您也可以在(适当的)编辑器中打开路径: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|<':

这表明,它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

再次,由于单词拆分,它将尝试创建三个目录,而不是一个。首先,它(成功)创建了目录/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 [`-_-"]'

然后,它也成功地+'|'|_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

然后,它尝试创建目录[`-_-"]/pullingATerdon。此操作失败mkdir,因为默认情况下不会创建子目录(如果使用则可以创建子目录-p):

$ mkdir baz/bar
mkdir: cannot create directory ‘baz/bar’: No such file or directory

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

这就是失败的原因,但发生的事情更复杂。你使用的字符串实际上是一个 shell glob,具体来说是一个全局范围`,它匹配当前目录中名称为、-或5 个字符之一的所有文件。由于当前目录中没有这样的文件,因此 glob 不会匹配任何内容,并且按照 bash 中的默认行为,返回自身_"

$ echo "[\`-_-\"]/pullingATerdon"  ## some escaping is needed here
+ echo '[`-_-"]/pullingATerdon'    ## but it echoes the right thing
[`-_-"]/pullingATerdon             ## and matches nothing, so returns itself.

为了澄清起见,如果你给出一个与某些内容匹配的 glob,则会发生以下情况:

$ echo [p]*   ## any filename starting with a p
pullingATerdon
$ echo "[p]*" ## the string "[p]*"
[p]*

未加引号的[p*]将扩展为匹配文件名列表(在本例中只有一个),这就是传递给 的内容echo。这也是为什么你应该引用所有内容的另一个原因。

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


更一般地,每当您发现自己要使用任意文件名时,请始终使用 shell 通配符。例如:

for file in *; do command "$file"; done

这对任何文件名都适用。无论它包含什么。在上面的例子中,你可以这样做:

emacs /home/terdon/*92mM*/pullingATerdon

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


一些有用的参考资料:

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

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

  3. 为什么我的 shell 脚本会因空格或其他特殊字符而阻塞?:您想要了解的有关在 shell 中处理任意文件名的所有内容。

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

相关内容