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

use*_*411 44 bash

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

$ echo [s]
[s]
$ echo [t]
t
$ echo [ t ]
[ t ]
$ echo [root]
t
Run Code Online (Sandbox Code Playgroud)

dev*_*ull 62

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

以下内容应进一步解释:

$ ls
$ echo [t]
[t]
$ touch t
$ echo [t]
t
$ echo [root]
t
$ touch r
$ echo [root]
r t
Run Code Online (Sandbox Code Playgroud)

如果你想回应内在的东西[],逃避[:

echo \[$var]
Run Code Online (Sandbox Code Playgroud)

现在观察差异:

$ echo \[root]
[root]
Run Code Online (Sandbox Code Playgroud)

或者,正如格伦杰克曼指出的那样,引用它:

$ echo '[root]'
[root]
$ echo "[root]"
[root]
Run Code Online (Sandbox Code Playgroud)

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

*   ?   [   #   ~   =   %
Run Code Online (Sandbox Code Playgroud)

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

|  &  ;  <  >  (  )  $  `  \  "  '  <space>  <tab>  <newline>
Run Code Online (Sandbox Code Playgroud)

如果您没有引用参数,也可以使用它printf确定给定输入中的哪些字符需要转义.对于你的例子,即[s]:

$ printf "%q" "[s]"
\[s\]
Run Code Online (Sandbox Code Playgroud)

另一个例子:

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

  • 'printf'%q""某事"引用+1. (3认同)
  • +1或使用引号(单引号或双引号) (2认同)

Ama*_*ali 26

[]表示一个字符类.简单地说,字符类是一种表示一组字符的方式,使得该集合中的一个字符匹配.[A-Z]是一种很常见的例子-它所有的字母从匹配A通过Z.

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

$ echo [s]
[s]
$ echo [t]
[t]
$ echo [ t ]
[ t ]
$ echo [root]
[root]
Run Code Online (Sandbox Code Playgroud)

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

扩张

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

$ echo *
evil_plans.txt dir1 dir2
Run Code Online (Sandbox Code Playgroud)

*它不打印文字作为输出,而是打印目录的内容.为什么会这样?因为*它具有特殊含义 - 它是一个通配符,可以匹配文件名的任何字符.重要的是要注意该echo命令实际上根本没有看到*- 只有扩展的结果.

有不同种类的扩展:

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

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

文件名扩展

当您键入echo命令并按ENTER键时,bash会处理该命令并将其拆分为单词.一旦做到这一点,它会扫描字以下字符:?,*[.所有这些都是元字符,具有特殊含义.如果bash发现这些字符中的任何一个出现,它会将提供的单词视为模式.

例如,请考虑以下情况:

$ touch foobar foobak fooqux barbar boofar
$ echo foo*
foobar foobak fooqux
Run Code Online (Sandbox Code Playgroud)

如您所见,*扩展并列出了匹配的文件名.(在这种情况下,那些以foo.开头的.)

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

$ touch gray.txt grey.txt
$ echo gr?y.txt
gray.txt grey.txt
Run Code Online (Sandbox Code Playgroud)

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

另一个例子:

$ touch hello hullo hallo
$ echo h[aeu]llo
hallo hello hullo
Run Code Online (Sandbox Code Playgroud)

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

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

针对您的具体案例的说明

$ echo [s]
Run Code Online (Sandbox Code Playgroud)

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

$ echo [t]
Run Code Online (Sandbox Code Playgroud)

[t]也是一个角色类.它匹配一个单个字符.t您的目录中有一个文件,意思是匹配.所以它返回了找到的文件名的名称.

$ echo [root]
Run Code Online (Sandbox Code Playgroud)

[root]符合下列字符:r,o,t.正如你可能猜到的那样,o在角色类中出现两次并没有什么不同.字符类只能匹配单个字符.因此,echo [root]将尝试查找具有匹配字符的文件名.由于t您的目录中存在一个名为的文件,因此将其列出.

如何避免这种怪癖?

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


Raf*_*ele 14

由于不是shell习惯(并且不愿意成为),我发现文件名扩展在未找到匹配项时的行为方式令人惊讶.我将报告Bash参考

猛砸扫描字符每个字*,?[.如果出现其中一个字符,则该单词被视为模式,并替换为与该模式匹配的按字母顺序排列的文件名列表.如果找不到匹配的文件名:

  • 如果nullglob禁用shell选项,则该字保持不变
  • 如果nullglob设置了shell选项,则删除该单词
  • 如果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
Run Code Online (Sandbox Code Playgroud)

  • 其他答案非常有用且具有教育意义,但这对我来说是以最短最简洁的方式解决问题的核心 - 回应无与伦比的表达以及如何控制它的奇怪之处. (2认同)

mkl*_*nt0 9

补充devnull的有用答案:

在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.
Run Code Online (Sandbox Code Playgroud)

要按字面意思处理字符串,必须使用引号.

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


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

echo \[t\]

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


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

echo '[t]'

这可以保护字符串免受shell的任何扩展(解释).
警告:你不能将'自己包含在单引号字符串中(甚至不能转义).


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

echo "[t]"

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

在手的情况下,'[t]'"[t]"相同的行为.

但是,使用"允许您引用变量(参数扩展),执行命令替换以及 在字符串内执行计算(算术扩展),例如:

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,搜索部分EXPANSIONman bash或访问https://www.gnu.org/software/bash/manual/html_node/Shell-Expansions.html.