当命令的部分参数来自变量时,我无法理解命令替换的工作原理。
为了说明这一点,我将仅提供一系列命令:
我将首先A B C
在 tmp 目录中创建一个目录,在新目录中我将创建一个新文件abcfile
:
user@desktop /tmp
$ pwd
/tmp
user@desktop /tmp
$ mkdir "A B C"
user@desktop /tmp
$ ls
'A B C'
user@desktop /tmp
$ touch "A B C"/abcfile
user@desktop /tmp
$ ls "A B C"/
abcfile
我将新地将值分配A B C
给变量$DIR
,并尝试使用ls
调用命令的各种方式来调用以列出该目录的内容
user@desktop /tmp
$ DIR="A B C"
当变量不加引号时,命令会按预期失败
user@desktop /tmp
$ ls $DIR
ls: cannot access 'A': No such file or directory
ls: cannot access 'B': No such file or directory
ls: cannot access 'C': No such file or directory
引用变量后,它按预期工作
user@desktop /tmp
$ ls "$DIR"
abcfile
如果我想要命令的输出作为字符串
user@desktop /tmp
$ echo $(ls "$DIR")
abcfile
这个命令有效,但是为什么它有效呢?根据 shellcheck 这实际上是不正确的,变量应该在双引号之外
user@desktop /tmp
$ echo "$(ls "$DIR")"
abcfile
如果内部变量没有双引号,则命令失败
user@desktop /tmp
$ echo "$(ls $DIR)"
ls: cannot access 'A': No such file or directory
ls: cannot access 'B': No such file or directory
ls: cannot access 'C': No such file or directory
答案1
我假设您知道输出命令的输出不需要使用asecho "$(somecommand)"
来替换,并且您使用此构造只是作为示例。somecommand
echo
该命令echo $(ls "$DIR")
“有效”是因为ls "$DIR"
输出abcfile
了echo
输出。
在这种情况下,在这里引用命令替换不会产生任何影响。该字符串abcfile
不需要引号,除非您更改$IFS
为包含该文件名中存在的字符(请参阅下面的原因)。
然而,考虑
A B C/
|-- A*
`-- abcfile
现在:
$ echo "$(ls "$DIR")"
A*
abcfile
$ echo $(ls "$DIR")
A B C abcfile
您会注意到最后一个输出将文件名扩展A*
为文件名通配模式,它与目录的名称匹配A B C
。您还会注意到我们丢失了输出的换行符ls
。
换行符丢失是因为 shell 丢失了分词在 的未加引号的输出上ls
,将其拆分为空格、制表符和换行符上的单词( 的默认内容$IFS
)。
插入目录名A B C
是因为分词生成的单词经过了文件名生成(通配符)。
如果新文件的名称为A* A*
,则目录的名称将被插入两次:
A B C/
|-- A* A*
`-- abcfile
$ echo $(ls "$DIR")
A B C A B C abcfile
有关的:
答案2
看来这是主要的误解:
变量应该在双引号之外
user@desktop /tmp $ echo "$(ls "$DIR")" abcfile
该变量不在双引号之外。 shell考虑在里面引用$()
分别地而不是在外面引用。这意味着echo "$(ls "$DIR")"
双引号中实际上是嵌套的:
echo "$(ls "$DIR")"
# ^ ^ inner quotes
# ^ ^ outer quotes
因为一般来说双引号变量是好的和整个$()
陈述(除非你知道你不想引用),你需要这些引用中的每一个。
比较“怪癖2”这个答案。
附带问题:
如果我想要命令的输出作为字符串
user@desktop /tmp $ echo $(ls "$DIR") abcfile
输出到 stdout 没有类型,echo
不会使其成为字符串。在这种特殊情况下,echo
什么都不改变,但请参阅出什么问题了echo $(stuff)
?