为什么bash“echo ”导致“t”不是“”

这发生在字符t和值的根。 相当困惑

$ echo [s] [s] $ echo [t] t $ echo [ t ] [ t ] $ echo [root] t 

不是shellhabitué(而且不愿意成为)我发现文件名扩展如何被devise为当没有find匹配时performance的令人惊讶。 我会报告Bash参考

Bash扫描每个单词的字符* ? ,和[ 。 如果出现这些字符中的一个,则该单词被认为是一个模式,并replace为与该模式匹配的按字母顺序排列的文件名列表。 如果找不到匹配的文件名称:

  • 如果shell选项nullglob被禁用,则该字保持不变
  • 如果shell选项nullglob被设置,则该字被删除
  • 如果设置了failglob shell选项,则会打印一条错误消息,并且不会执行该命令

好消息是这个东西是可configuration的。 不好的一个是脚本可能会以很多方式失败,至less我没有这么做,至less我没有这么做,而且我花了一些时间来理解为什么echo会按照你发布的方式行事,只是为了发现它是因为奇怪的文件名组合(谁曾经想要命名文件?),隐藏configuration( nullglob禁用,默认选项,但仍然隐藏)和一个无害的命令。

我说无害,因为这是你得到的,例如,当目标是ls (因为找不到文件而失败):

 raffaele@Aldebaran:~$ mkdir test raffaele@Aldebaran:~$ cd test raffaele@Aldebaran:~/test$ touch t raffaele@Aldebaran:~/test$ ls [t] t raffaele@Aldebaran:~/test$ ls [v] ls: cannot access [v]: No such file or directory 

[]表示一个字符类,并且在当前目录中有一个名为t的文件。

以下应该进一步解释:

 $ ls $ echo [t] [t] $ touch t $ echo [t] t $ echo [root] t $ touch r $ echo [root] rt 

如果您想在[]回显某个内容,请将[

 echo \[$var] 

现在观察差异:

 $ echo \[root] [root] 

或者,正如格伦·杰克曼 ( Glenn Jackman)所指出的那样:

 $ echo '[root]' [root] $ echo "[root]" [root] 

Shell命令语言告诉下面的字符对于shell是特殊的,取决于上下文:

 * ? [ # ~ = % 

此外,如果要performance自己,则必须引用以下字符:

 | & ; < > ( ) $ ` \ " ' <space> <tab> <newline> 

如果不引用参数,也可以使用printf确定给定input中的哪些字符需要转义。 对于你的例子,即[s]

 $ printf "%q" "[s]" \[s\] 

另一个例子:

 $ printf "%q" "[0-9]|[az]|.*?$|1<2>3|(foo)" \[0-9\]\|\[az\]\|.\*\?\$\|1\<2\>3\|\(foo\) 

[]表示一个字符类。 简而言之,字符类是一种表示一组字符的方式,使得该字符集中的一个字符匹配。 [AZ]是一个很常见的例子 – 它匹配从AZ所有字母。

以下是新目录中命令的结果:

 $ echo [s] [s] $ echo [t] [t] $ echo [ t ] [ t ] $ echo [root] [root] 

正如你所看到的, echo显示它们。 为什么? 阅读下一节。

扩张

每次你在terminal上input一个命令,然后按下回车键,bash在将结果输出到shell之前会执行很多操作。 最简单的例子是*的扩展:

 $ echo * evil_plans.txt dir1 dir2 

它不是显示文字*作为输出,而是打印目录的内容。 为什么会发生? 因为*有特殊含义 – 它是一个通配符,可以匹配文件名的任何字符。 需要注意的是, echo命令实际上并没有看到* ,只有扩展的结果。

有不同种类的扩展:

  • 支撑扩展
  • 蒂尔德扩张
  • 参数扩展
  • 命令扩展
  • 算术扩展
  • 进程替代
  • 文件名称扩展

…也许更多。 在这种情况下,文件名扩展是相关的扩展types。

文件名称扩展

当你input一个echo命令并按下回车键时,bash会处理这个命令并将其分解成单词。 一旦完成,它会扫描以下字符的单词: ?*[ 。 所有这些都是元字符,具有特殊的意义。 如果bash发现这些字符中的任何一个出现,它会将提供的字视为模式。

例如,请考虑以下情况:

 $ touch foobar foobak fooqux barbar boofar $ echo foo* foobar foobak fooqux 

正如你所看到的, *展开并列出了匹配的文件名。 (在这种情况下,那些以foo开头的)

现在让我们试试另一个例子:

 $ touch gray.txt grey.txt $ echo gr?y.txt gray.txt grey.txt 

? 匹配单个字符。 而已。 在这种情况下, gray.txtgrey.txt都匹配模式,所以都打印出来。

另一个例子:

 $ touch hello hullo hallo $ echo h[aeu]llo hallo hello hullo 

这里发生了什么? 如你所知, [aeu]是一个angular色类。 一个字符类恰好匹配其中的一个字符; 它永远不会匹配多个字符。 在这种情况下,字符类中的字符可以正确匹配文件名,所以结果被打印出来。

如果找不到匹配的文件名,则保持不变。

解释你的具体情况

 $ echo [s] 

[s]是一个字符类,它只匹配一个字符 – 字面s 。 但没有find匹配的文件,所以它被返回原样。

 $ echo [t] 

[t]也是一个angular色类。 它匹配一个单一的字符。 在你的目录中有一个名为t的文件,这意味着有一个匹配。 所以它返回了find的文件名的名字。

 $ echo [root] 

[root]匹配以下字符: rot 。 正如你可能猜到的那样,在angular色类中出现两次在这里没有什么区别。 一个字符类只能匹配一个字符。 所以echo [root]会尝试查找具有匹配字符的文件名。 由于名为t的文件存在于您的目录中,因此列出。

如何避免这种怪癖?

在需要的地方总是使用引号和转义。 它们使您能够全面掌控parsing,扩展和扩展的结果。

为了补充devnull的有用答案 :

在bash中使用不带引号的string(在大多数情况下)会导致它被解释为模式 (通配符expression式;松散地说,是一个正则expression式的远程原始相对)。

在你的情况下,该模式与当前工作文件夹中的文件和子文件夹的名称相匹配 ,如@devnull所示: [root]意思是:匹配任何名称只包含一个 r o字符的文件(指定o两次是多余的) 或者 t 。 模式与文件名的匹配称为path名扩展

请注意,它也适用于未加引号的variables引用 ,所以下面的结果会相同:

 s='[t]' # Assign string _literal_ (since quoted) to variable. echo $s # Content of $s, since unquoted, is now subject to pathname expansion. 

字面上 (有select地)处理string,您必须使用引号

bash三种引用string的方法 ,用你的例子:


\ – 引用具有特殊含义的单个字符 (所谓的元字符 ):

echo \[t\]

这确保这些特殊字符被视为文字


单引号括起string( '...' ):

echo '[t]'

这可以保护string免于shell的任何扩展(解释)。
警告 :你不能在一个单引号的string中包含一个'本身”(甚至没有转义)。


将该string用双引号括起来"..." ):

echo "[t]"

这保护了一些扩展的string的shell,而有select地允许他人。

在这种情况下, '[t]'"[t]"行为是一致的。

但是, 使用"允许您引用variables (参数扩展),执行命令replace ,并在string内执行计算 (算术扩展) ,例如:

echo "Home, sweet $HOME." # reference to variable $HOME; parameter expansion

echo "Today's date and the current time are: $(date)" # command substitution

echo "Let's put $(( 2 + 2 )) together." # arithmetic expansion

有关bash执行的所有扩展列表 ,请查找man bash EXPANSION部分或访问https://www.gnu.org/software/bash/manual/html_node/Shell-Expansions.html