bash中`和'有什么区别?

时间:2015-08-23 05:49:32

标签: macos bash terminal

在OS X终端中运行此语句

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

将按名称顺序成功打印目录中的所有文件名,每个文件名都在其自己的行上。

来源:This StackOverFlow answer

但是,如果我在OS X终端中运行此语句

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

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

我的问题是:

  1. bash中`和'之间有什么区别?
  2. 为什么这种差异对完全不同的输出负责?
  3. 什么键盘组合产生`? (键盘组合)

3 个答案:

答案 0 :(得分:7)

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

echo `echo 42`

将扩展为:

echo 42

这称为Command Substitution,也可以使用语法$(command)来实现。在您的情况下,以下内容:

for i in `ls -v *.mkv`; do ...

替换为类似的内容(如果您的目录包含3个名为 a.mkv b.mkv c.mkv 的文件):

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

引号双引号之间的文字只是普通的Bash字符串,其中包含 space 等字符(还有其他引用字符串的方法)在Bash中是described here):

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

使用'"之间的区别在于" interpolate个变量之间的字符串,例如:

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

i循环中的for变量取值“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

在应用globbing之后,这只是一个带有文件名的单行字符串。

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

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

更新:根据@triplee的建议,您应该检查useless use of ls,因为这被视为bash pitfall,并且有更好的方法来实现您的目标试图做。

答案 1 :(得分:1)

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

`表达式`,将执行表达式的内容并且echo输出它。

例如:

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

答案 2 :(得分:1)

反引号意味着“在反引号之间运行作为命令,然后表现得好像我在这里输入了该命令的输出”。正如其他人所说,单引号意味着只是一个字符串。所以在第一种情况下,会发生什么:

bash运行ls -v *.mkv作为命令,输出如下内容:

fileName1.mkv
fileName2.mkv

bash然后将其替换回反引号包围命令的位置,即它有效地使你的for语句成为:

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

它有两个“令牌”:“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可能是任何命令。)

相关问题