为什么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]
是一个很常见的例子 – 它匹配从A
到Z
所有字母。
以下是新目录中命令的结果:
$ 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.txt
和grey.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]
匹配以下字符: r
, o
, t
。 正如你可能猜到的那样,在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 。