在bash中,`和'有什么区别?

3
在OS X终端中运行此语句
for i in `ls -v *.mkv`; do echo $i; done

这条语句将会按照文件名的顺序成功打印出目录中所有文件的名称,并使每个文件名单独占一行。

来源:这个StackOverFlow答案

然而,如果我在OS X终端中运行此语句

for i in 'ls -v *.mkv'; do echo $i; done

输出结果为“ls -v fileName1.mkv fileName2.mkv”等,所有文件名都连接成一个长行(而不是每个文件名单独打印在一行上)。

我的问题���:

  1. 在bash中,`和'有什么区别?
  2. 为什么这种差异会导致完全不同的输出?
  3. 哪个键盘组合键可以产生`?(键盘组合键)

2
不要使用 ls 命令,直接使用 for i in *.mkv; do echo "$i"; done - anubhava
1
expression 执行表达式的内容并输出结果。 - Zohar81
1
请看这篇帖子:http://askubuntu.com/a/20041/336375 - Cyrus
1
回答第三个问题:这取决于您的键盘布局,对于美国、德国、法国等不同的布局是不同的。 - Gerald Schneider
1
除了https://dev59.com/NWkw5IYBdhLWcg3wMHum之外。 - tripleee
显示剩余4条评论
3个回答

7

1) 反引号之间的文本将被执行并替换为所包含命令的输出,因此:

echo `echo 42`

将扩展为:

echo 42

这被称为命令替换,也可以使用$(command)语法实现。在您的情况下,可以使用以下方式:
for i in `ls -v *.mkv`; do ...

如果您的目录包含3个名为a.mkvb.mkvc.mkv的文件,则会被替换为以下内容:

for i in a.mkv  b.mkv  c.mkv; do ...

引号双引号中的文本只是带有字符(如空格)转义符的普通Bash字符串(Bash中还有其他引用字符串的方式,在此处描述):

echo "This is just a plain and simple String" 
echo 'and this is another string' 

使用'"之间的区别在于,用"括起来的字符串可以插值变量,例如:

variable=42
echo "Your value is $variable"

或者:

variable=42
echo "Your value is ${variable}"

输出:

Your value is 42

2) 通配符表达式如*.mkv在一个被称为Globbing的过程中被替换为扩展名。 在大多数命令中,使用通配符激活Globbing,而不需要将表达式包含在字符串中:

echo *.mkv

将打印:
a.mkv b.mkv c.mkv

同时:

echo "*.mkv"

打印:

*.mkv

在你的for循环中,i变量的值为"ls -v *.mkv",但是循环体内的echo命令使用了没有引号的$i,因此Bash在那里应用了globbing,最终结果如下:
for i in 'ls -v *.mkv'; do 
   #   echo $i 
   #
   # which is expanded to:
   #   echo ls -v *.mkv    (no quotes) 
   #
   # and the globbing process transform the above into:
   echo ls -v a.mkv b.mkv c.mkv

在应用了通配符后,这只是一个包含文件名的单行字符串。

3) 这取决于您的键盘布局。

保持字符的一个技巧是使用程序ascii,搜索字符96(十六进制60),复制它并将其保存在剪贴板上(您可以使用parcellite或任何其他适合您需求的剪贴板管理器)。


更新:@triplee所建议,您应该查看无用的ls使用,因为这被认为是bash陷阱,有更好的方法来实现您想要做的事情。


也许还可以提到无用的ls,以及http://mywiki.wooledge.org/ParsingLs。 - tripleee
我不确定你在提到复合命令时想要表达什么意思;没有进行任何特殊处理。此外,引号也不会创建一个字符串; foo"foo" 都创建相同的字符串。引号的作用是隐式地转义引号中包含的每个字符。foo\ bar"foo bar" 会创建相同的字符串。 - chepner
@chepner非常感谢您提出的这些好观点。已更新答案中的字符串部分。能否在复合命令部分进行更详细的阐述?我正在尝试解释为什么通配符应用于in之后的字符串而不是其他命令,我确信这是由于复合命令的原因。 - higuaro
1
通配符应用于for循环中的echo $ifor中的列表只有一个元素。i被设置为该元素(ls -v *.mkv)。echo $i只执行一次。它扩展为echo ls -v *.mkv。通配符在此处执行,然后回显单个长字符串。 - Ken Thomases
@KenThomases 谢谢!我怎么会错过那个!非常感谢你! - higuaro

1

'expression',将输出表达式中的确切字符串。

`expression`,将执行表达式的内容并回显输出。

例如:

 x="ls"
 echo "$x" --> $x
 echo `$x` --> file1 file2 ... (the content of your current dir)

1
反引号意味着“运行反引号中的内容作为命令,然后将该命令的输出视为我在此处键入的内容”。单引号表示一个字面字符串。因此,在第一种情况下,发生的情况如下:
bash将ls -v *.mkv作为命令运行,输出类似于:
fileName1.mkv
fileName2.mkv

bash然后将其替换回反引号包围的命令所在的位置,即它有效地使您的for语句变成了这样:

for i in fileName1.mkv fileName2.mkv; do echo $i; done

这里有两个“tokens”: "fileName1.mkv" 和 "fileName2.mkv",所以循环会运行它的主体(echo $i)两次,每次一个:

echo fileName1.mkv
echo fileName2.mkv

默认情况下,echo命令在输出完所要输出的内容后会换行,这样你就会得到预期的输出结果,每个文件名都是独立一行。
然而,当你使用单引号而不是反引号时,单引号中间的内容不会被评估;也就是说,bash不会将其视为命令(或任何特殊内容;单引号告诉bash,“这段文本不是特殊内容;不要尝试对其进行评估或执行任何操作”)。因此,你所运行的内容如下:
for i in 'ls -v *.mkv'; do echo $i; done

这个循环只有一个令牌,即字面字符串“ls -v *.mkv”,因此循环体只运行一次:

echo ls -v *.mkv

...但就在bash运行那个echo之前,它会扩展"*.mkv"。

我上面简单提到了这一点,但是当你执行像ls *.mkv这样的操作时,实际上并不是ls*.mkv转换为所有.mkv文件名的列表;而是bash执行这项任务。 ls从未看到*.mkv;在ls运行时,bash已经用"fileName1.mkv fileName2.mkv..."替换了它。

同样对于echo:在运行此行之前,bash会扩展*.mkv,因此实际运行的是:

echo ls -v fileName1.mkv fileName2.mkv

输出以下文本:

ls -v fileName1.mkv fileName2.mkv

(*注:还有一件事我没有提到,那就是文件名中的空格。反引号之间ls的输出是一个文件名列表,每行一个。问题在于,bash将任何空格(包括空格和换行符)都视为分隔符,所以如果您的文件名是:

)
file 1.mkv
file 2.mkv

你的循环会运行四次("file", "1.mkv", "file", "2.mkv")。另一种循环形式 for i in *.mkv; do ... 没有这个问题。为什么?因为当bash扩展“*.mkv”时,它在幕后做了一个聪明的事情,并将每个文件名作为一个单元处理,就好像你用引号说“file 1.mkv”“file 2.mkv”。在使用ls的情况下,它不能这样做,因为当它将扩展的文件名列表传递给ls后,bash无法知道返回的是同一文件名列表。ls可以是任何命令。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接