为什么bash" echo [t]"导致" t"不是" [t]"

时间:2014-04-17 18:15:45

标签: bash

对于字符t和值root,会发生这种情况。相当令人困惑

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

4 个答案:

答案 0 :(得分:62)

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

以下内容应进一步解释:

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

如果您想回复[]内的内容,请转义[

echo \[$var]

现在观察差异:

$ echo \[root]
[root]

或者,正如Glenn Jackman指出的那样,引用它:

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

Shell Command Language告诉shell,以下字符是特殊的,具体取决于上下文:

*   ?   [   #   ~   =   %

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

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

如果您没有引用参数,也可以使用printf确定给定输入中的哪些字符需要转义。例如,[s]

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

另一个例子:

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

答案 1 :(得分:26)

[]表示一个字符类。简而言之,字符类是一种表示一组字符的方式,其方式是匹配集合的一个字符[A-Z]是一个非常常见的示例 - 它匹配从AZ的所有字母表。

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

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

如您所见,echo按原样显示它们。为什么?阅读下一节。

扩展

每次在终端上键入命令并按ENTER键时,bash会在将结果打印到shell之前在内部执行大量操作。最简单的例子是*

的扩展
$ echo *
evil_plans.txt dir1 dir2

不是将文字*显示为输出,而是打印目录的内容。为什么会这样?因为*具有特殊含义 - 它是一个通配符,可以匹配文件名的任何字符。值得注意的是,echo命令实际上并未真正看到* - 只有扩展结果。

有不同类型的扩展:

  • 支持扩张
  • Tilde扩张
  • 参数扩展
  • 命令扩展
  • 算术扩展
  • 流程替换
  • 文件名扩展

......可能还有更多。在这种情况下,文件名扩展是相关的扩展类型。

文件名扩展

当您键入echo命令并按ENTER键时,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]是一个字符类。字符类恰好匹配其中的一个字符;它永远不会匹配多个角色。在这种情况下,字符类中的字符可以正确匹配文件名,因此打印出结果。

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

您具体案例的说明

$ echo [s]

[s]是一个字符类,它只匹配一个字符 - 文字s。但是没有找到匹配的文件,所以它按原样返回。

$ echo [t]

[t]也是一个角色类。它匹配一个单个字符。您的目录中有一个名为t的文件,表示存在匹配项。所以它返回了找到的文件名的名称。

$ echo [root]

[root]符合以下字符:rot。正如您可能猜到的那样,o在角色类中出现两次并不会产生任何影响。字符类只能匹配单个字符。因此echo [root]将尝试查找具有匹配字符的文件名。由于您的目录中存在名为t的文件,因此将其列出。

如何避免这种怪癖?

始终在需要的地方使用引用和转义。它们可以让您全面控制解析,扩展和扩展的结果。

答案 2 :(得分:14)

不是shell习惯(并且不愿意成为)我发现奇怪的是文件名扩展设计为在没有找到匹配时表现。我会报告Bash reference

  

Bash会扫描每个单词以查找字符*?[。如果其中之一   出现这些字符,然后将该字视为一种模式,并且   替换为按字母顺序排序的文件名列表   图案。如果找不到匹配的文件名:

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

好消息是这个东西是可配置的。糟糕的是脚本可以通过多种方式失败 - 至少,我没有,并且我花了一些时间来理解为什么echo表现出你发布的方式,只是为了找到它这是因为奇怪的文件名(谁想要命名文件t?),隐藏配置(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

答案 3 :(得分:9)

补充devnull's helpful answer

在bash中使用未加引号字符串(在大多数情况下)会导致它被解释为模式 (通配符表达式;松散地说,正则表达式的一个遥远的原始亲戚。)

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

请注意,也适用于未加引号的变量引用,因此以下结果会产生相同的结果:

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

处理字符串(有选择地)字面,您必须使用引用

使用您的示例有三种引用bash 字符串的方法:


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

echo \[t\]

这可确保将这些特殊字符视为文字


将字符串括在单引号'...')中:

echo '[t]'

这可以保护字符串免受外壳的任何扩展(解释) 警告:您不能在单引号字符串中包含'本身(甚至不能使用转义)。


将字符串括在双引号"...")中:

echo "[t]"

这可以保护字符串免受shell的某些扩展,同时有选择地允许其他人。

在目前的情况下,'[t]'"[t]"的行为相同。

然而,使用"允许您引用变量(参数扩展),执行命令替换,并执行计算< / em>(算术扩展) 在字符串中,例如:

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执行的所有扩展列表,请在EXPANSION中搜索man bash部分或访问https://www.gnu.org/software/bash/manual/html_node/Shell-Expansions.html