使用 `$var && action` 将空变量视为逻辑“true”(shell 解析)

使用 `$var && action` 将空变量视为逻辑“true”(shell 解析)

考虑一种简单的调试风格,其中$debug可以设置为truefalse根据某些命令行标志:

$debug && echo "Something strange just happened" >&2

我知道有更好的方法来防止值设置,例如${debug:-false}甚至是全功能的logMessage()-type 函数,但让我们暂时搁置它。

$debug假设仍然未设置或为空的糟糕情况。我预计$debug && echo …这样的空值$debug将被解析并计算为&& echo …,这将触发随后的语法错误。但相反,它似乎被评估为类似的东西: && echo …,最终执行echo.

我已经用bashksh和进行了测试dash,并且场景保持一致。

我的理解是这两行被解析为等价的,但显然它们不是:

unset debug; $debug && echo stuff
unset debug; && echo stuff

为什么代码会表现得好像未设置的变量在逻辑上为真,而不是因诸如 之类的错误而失败syntax error near unexpected token `&&'

答案1

但扩展完成后不会检查语法。

$foo是一个语法上有效的简单命令,无论变量的值是什么以及扩展后生成的字段数如何。它是扩展之前的一个 shell“单词”,这就足够了。

等于$foo && whatever类似于&& whatever期望 shell 也解析其他语法(关键字、引号等)的扩展结果。那还没有完成。

因此:

foo="then if"
$foo

不是语法错误,而是尝试运行名为的常规命令then (该命令可能不存在并且您会收到错误,但它可以存在,并且“未找到命令”错误是不同的then if一个是由于尝试单独运行而出现的语法错误。) 。

和:

foo=
$foo

不是语法错误,而是尝试运行...好吧,它不是因为没有“命令名称”,虽然这是一种特殊情况,但它不是错误。看2.9.1 简单命令

当需要执行给定的简单命令时,以下扩展、赋值和重定向都将从命令文本的开头到结尾执行:

  1. [分配、重定向]

  2. 非变量赋值或重定向的词应扩展。如果任何字段在扩展后仍然保留,第一个字段应被视为命令名称,其余字段是命令的参数。 [...]

注意“如果”。

没有命令名的结果是变量赋值会影响当前执行环境,如果也没有命令替换,则退出状态为零。

因此,如果$foo为空或未设置:

$foo

只是不执行任何操作并将退出状态设置为零,并且

bar=asdf $foo

设置$barasdf并将退出状态设置为零。与 just 相同bar=asdf,但如果$foodid 扩展为命令名称,则分配将仅适用于该命令。

答案2

来自 Shell 命令语言规范,部分2.9.1 简单命令,在执行所有重定向、全局/参数扩展等之后:

如果有命令名称,则应按照中的描述继续执行命令搜索和执行。如果没有命令名称,但该命令包含命令替换,则该命令应以上次执行的命令替换的退出状态完成。否则,该命令应以零退出状态完成。

你属于“否则”的情况。

答案3

一个简单的命令由赋值、重定向和参数组成(第一个参数用于派生要运行的命令)。

全部都是可选的。如果没有参数,只要可以执行所有重定向并且重定向和分配目标中的最后运行命令替换(或用于生成空参数列表的命令)成功,该命令就会成功,尽管 IIRC 未指定哪个重定向和分配首先完成。

所有这些都成功了:

  • a=whatever:没有命令,没有重定向,只有赋值
  • $(true):没有命令,没有重定向,没有分配,成功的命令替换会导致空列表。
  • a=$(false) b=$(true).仅赋值,最后一个命令替换成功。
  • > /dev/null:无命令,重定向成功
  • > "$(echo /dev/null)":无命令,命令替换和重定向成功
  • empty=; a=foo $empty > /dev/null:赋值和重定向,以及导致空列表的扩展,因此没有参数/命令。
  • empty=; $empty:与上面相同,没有赋值或重定向。

这些失败了:

  • false
  • a=$(false)
  • $(false)
  • > /etc/passwd/foo
  • < /dev/null"$(false)"
  • ...

未指定:

  • $(true) > /dev/null$(false)
  • a=$(false) > /dev/null$(true)

答案4

考虑这&&是一个连接 2 个命令管道列表的运算符(3.2.4 命令列表)。

第一个命令是$debug.如果未设置变量,则不会有任何命令——就像在提示符下按 Enter 键一样:不会执行任何命令,但也不会出现非零退出状态。

由于 的左侧没有错误&&,因此右侧可以自由运行。

引用会给你你想要的错误

  • 巴什

    $ unset debug; $debug && echo hmm
    hmm
    $ unset debug; "$debug" && echo hmm
    bash: : command not found
    
  • 克什

    $ unset debug; $debug && echo hmm
    hmm
    $ unset debug; "$debug" && echo hmm
    ksh: : cannot execute [Is a directory]
    
  • 短跑

    $ unset debug; $debug && echo hmm
    hmm
    $ unset debug; "$debug" && echo hmm
    dash: 9: : Permission denied
    

相关内容