如何正确执行
首先,请始终引用您的变量。如果正确引用,您尝试做的事情会很好:
$ 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 对未加引号的字符串执行了单词拆分和glob扩展。在这种情况下,问题在于单词拆分,因为外壳程序看到了路径中的空格并读取了每个空格分隔的字符串作为单独的参数。
该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水珠,特别是水珠范围,它匹配在当前目录中,其名称是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]*
unquoted [p*]
扩展为匹配文件名的列表(在这种情况下,仅一个),这就是传递给的文件名echo
。您应该引用所有内容的另一个原因。
最后,您显示的实际错误是第二次运行命令时发生的错误,并且在尝试创建时第一步失败/home/terdon/foo/\e[92mM@r|<
,因为先前的调用已经创建了该目录。
更一般而言,每当您发现使用任意文件名时,请始终使用shell glob。这样的事情:
for file in *; do command "$file"; done
这将适用于任何文件名。不管碰巧包含什么。在上面的示例中,您可以完成以下操作:
emacs /home/terdon/*92mM*/pullingATerdon
任何唯一标识目标文件的glob都可以。这样,您不必担心特殊字符,只需让Shell处理它们即可。
一些有用的参考:
如何查找和安全处理包含换行符,空格或两者都包含的文件名?:出色的灰猫维基上的常见问题之一。
忘记在bash / POSIX shell中引用变量的安全隐患:我在此答案开头引用的同一篇文章。如果您未能正确引用Shell变量,那么将对所有可能出错的问题进行详尽而详尽的解释。
为什么我的Shell脚本会在空白或其他特殊字符上造成窒息?:您想了解的有关在Shell中处理任意文件名的所有信息。
什么时候需要双引号?:更多有关引号和变量的信息,尤其是一些不需要引号的情况
vim "$bacon"