对于字符t和值root,会发生这种情况。相当令人困惑
$ echo [s]
[s]
$ echo [t]
t
$ echo [ t ]
[ t ]
$ echo [root]
t
答案 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]
是一个非常常见的示例 - 它匹配从A
到Z
的所有字母表。
以下是新目录中命令的结果:
$ echo [s]
[s]
$ echo [t]
[t]
$ echo [ t ]
[ t ]
$ echo [root]
[root]
如您所见,echo
按原样显示它们。为什么?阅读下一节。
每次在终端上键入命令并按ENTER键时,bash会在将结果打印到shell之前在内部执行大量操作。最简单的例子是*
:
$ echo *
evil_plans.txt dir1 dir2
不是将文字*
显示为输出,而是打印目录的内容。为什么会这样?因为*
具有特殊含义 - 它是一个通配符,可以匹配文件名的任何字符。值得注意的是,echo
命令实际上并未真正看到*
- 只有扩展结果。
有不同类型的扩展:
......可能还有更多。在这种情况下,文件名扩展是相关的扩展类型。
当您键入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.txt
和grey.txt
都匹配了模式,因此两者都已打印出来。
另一个例子:
$ touch hello hullo hallo
$ echo h[aeu]llo
hallo hello hullo
这里发生了什么?如您所知,[aeu]
是一个字符类。字符类恰好匹配其中的一个字符;它永远不会匹配多个角色。在这种情况下,字符类中的字符可以正确匹配文件名,因此打印出结果。
如果找不到匹配的文件名,则该字保持不变。
$ echo [s]
[s]
是一个字符类,它只匹配一个字符 - 文字s
。但是没有找到匹配的文件,所以它按原样返回。
$ echo [t]
[t]
也是一个角色类。它匹配一个单个字符。您的目录中有一个名为t
的文件,表示存在匹配项。所以它返回了找到的文件名的名称。
$ echo [root]
[root]
符合以下字符:r
,o
,t
。正如您可能猜到的那样,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)
在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。