realpath
并readlink
返回绝对路径:
+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 处理它们即可。
一些有用的参考资料:
如何查找并安全地处理包含换行符、空格或两者的文件名?:优秀的 Grey Cat's Wiki 上的常见问题解答之一。
忘记在 bash/POSIX shell 中引用变量的安全隐患:我在本回答开头引用的同一篇文章。如果您未能正确引用 shell 变量,则可能会出现所有错误,这篇文章非常详细地解释了所有错误。
为什么我的 shell 脚本会因空格或其他特殊字符而阻塞?:您想要了解的有关在 shell 中处理任意文件名的所有内容。
什么时候需要双引号?:有关引号和变量的更多信息,特别是一些不需要引用它们的情况