为什么 bash 条件表达式中的某些字符串比较运算符周围不需要空格?

Wei*_*hou 13 bash

今天我在编写一些 bash 脚本时发现了一些令人惊讶的事情。我已将其归结为这个最小的示例。

[[ a>b ]]; echo $?

根据我的理解,因为 周围没有空格>,所以这应该测试字符串是否a>b不为空,并且应该返回错误代码0。然而,上面的命令1在我测试的两个 bash 版本中都有回显(详细信息如下)。

我还使用旧的“good”test命令进行了测试,

[ a>b ]; echo $?echo 0(测试字符串a不为空并b在我当前的工作目录中创建一个空文件,显然>b被视为重定向,这是可以理解的)。

然后我又尝试了一些其他的事情

  • [[ b>a ]]; echo $?回声0并且没有创建文件。
  • [[ b<a ]]; echo $?回声1并且没有创建文件。
  • [[ a<b ]]; echo $?回声0并且没有创建文件。
  • [ b>a ]; echo $?echo0并创建一个空文件a
  • [ b<a ]; echo $?报告丢失文件的错误a1由于该错误而回显。
  • [ a<b ]; echo $?报告丢失文件的错误b1由于该错误而回显。
  • [[ a=b ]]; echo $?echos0因为它正在测试字符串是否a=b如我预期的那样非空。
  • [ a=b ]; echo $?0出于同样的原因回声。
  • [[ a==b ]]; echo $?0出于同样的原因回声。
  • [ a==b ]; echo $?0出于同样的原因回声。
  • [[ a!=a ]]; echo $?两者[ a!=a ]; echo $?都回显0(预期)

因此,条件表达式中似乎只能省略<and周围的空格,但如果目的是进行字符串比较,则不能省略or or 。但为什么要这样设计呢?这也可能是我的遗漏,但这似乎没有在 bash 手册中的任何地方记录。>===!=

我最初的问题是尝试使用>未转义作为条件表达式 ( ) 中模式的一部分[[ ... ]]。我最初认为,只要我不用>空格包围,我应该能够毫无问题地使用它,因为在条件内进行重定向是没有意义的(并且在一些测试后证明是不可能的)表达也可以。

然而事实证明并非如此。当然,简单的解决方案就是逃避它>并编写\>,但我不明白为什么需要它。

这是我用于测试的 bash 版本,

GNU bash, version 5.2.2(1)-release (aarch64-unknown-linux-android)
Run Code Online (Sandbox Code Playgroud)

GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu)
Run Code Online (Sandbox Code Playgroud)

我的实验是在env -i bash --norc --noprofile.

Sté*_*las 17

归根结底,像&, ;, (, |, <, >, tab 这样的字符是shell 语法中的元字符。它们形成自己的标记(或者可以组合成更多标记,例如;;, |&, ||...)。[这与、{!\xc2\xb9等字符不同-=它们类似于其中的字母\xc2\xb2 和数字。

\n

在 Korn shell 的构造内部[[...]],shell 理解一种单独的微语言,但它遵循与外部大致相同的标记规则。

\n

出于同样的原因,在 之外[[...]],您可以执行以下操作:

\n
(echo a>file|tr a b&)\n
Run Code Online (Sandbox Code Playgroud)\n

并且不必写它:

\n
( echo a > file | tr a b & )\n
Run Code Online (Sandbox Code Playgroud)\n

或者甚至是类似的东西if((1))then<file&&(uname)fi

\n

在这里,您可以执行以下操作:

\n
[[(a>b||b<d)]]\n
Run Code Online (Sandbox Code Playgroud)\n

as (>|)是 shell 元字符。

\n

元字符必须用引号引起来(无论是用\'...\', "...", \\, $\'...\'...)才能按字面意思理解。

\n

[[x]]无法工作,因为 shell 只能看到一个[[x]]令牌。并且[[启动该[[...]]构造的关键字甚至不会被识别。[[ a==b ]]与 不同[[ a == b ]],如在[[...]]微语言中一样,a==b是单个标记,因此其解释与 相同[[ -n \'a==b\' ]]

\n

[它本身只是一个普通命令,因此它的解析方式与另一个命令相同。

\n
[ a>b ]\n
Run Code Online (Sandbox Code Playgroud)\n

[使用aand]作为参数运行,其输出重定向到b, 与 相同[ a ] > b,就像与orecho a>b ]相同。echo a ] > b>b echo a ]

\n

请注意,它的内部((...))(也与 ksh)不同,它也带有自己的类似 C 的微语言,但这次标记化规则不同。例如,您可以编写((var=123+1))or ((a==b)),但不需要((var = 123 + 1))or ((a == b))

\n
\n

\xc2\xb9{涉及大括号扩展,也是 shell 保留字,但在标记化之后进行处理,[尽管在解析赋值时参与标记化。a[1 + 1]=foo在 bash 或 ksh(不是 zsh)中被解析为一个赋值词,而不是作为参数a[1运行。是一个保留字,如,但也参与历史扩展,尽管这只适用于 shell 的交互式调用。+1]=foo!{

\n

\xc2\xb2 请注意字母 和-涉及一些[[...]]运算符,例如-nt, -eq, -lt...

\n

  • 我们必须向 David Korn 询问这一点,但我想这是为了避免编写一种新语言和用户学习一种新语言并在两者之间混淆。在 `((...))` 中,它就像 C 语言一样,是 80 年代的 Unix 用户所熟悉的 Unix 语言。在 `[[...]]` 中使用字符串的类 C 语言意味着需要在不属于运算符的字符串周围加上引号,以及如何处理扩展及其转义、如何处理单引号 (就像在 C 中或像 shell 一样?),等等。 (3认同)
  • 感谢您写得非常清楚且组织良好的答案。我最初认为实现不同的标记化规则会很困难或麻烦,但在看到您在编辑中添加的最后一段之后,我现在想知道为什么不对条件表达式也应用不同的标记化规则?是否只是为了使其与旧的“[”结构保持一致,或者是因为“[”本来就不是元字符,还是有更深层次的原因,或者只是一个任意选择? (2认同)
  • 顺便说一句,您会注意到在 `(( ... ))` 中,ksh 确实像 C 中那样处理单引号,而 bash/zsh 则不然。`ksh -c "echo \$(( 'a' ))"` 输出 97,`a` 字符的值(多字节语言环境中的 wchar_t),bash、zsh 给出语法错误。 (2认同)
  • 哦,这对 `a[1 + 1]=foo` 来说是一个很好的观点!正当我以为我可以理解 shell 的命令行解析是如何工作的时...... (2认同)