交互式、非交互式外壳和扩展

交互式、非交互式外壳和扩展

[[ ! (-z "") ]]如果我以交互方式或非交互方式执行测试bash -c '[[ ! (-z "") ]]',我会获得相同的结果,即echo $?给我1

但如果我忘记了上述表达式中的一个空格,得到[[ !(-z "") ]],我就不再得到相同的结果了。更准确地说,

set -x; [[ !(-z "") ]]; echo $?

给我

+ [[ -n !(-z ) ]]
+ echo 0
0

尽管

bash -c 'set -x; [[ !(-z "") ]]; echo $?'

给我

+ [[ -z '' ]]
+ echo 1
1

所以我的问题是:为什么交互式和非交互式 shell 不会给出相同的扩展,并且在这种情况下(或其他类似情况下)不会给出相同的结果?

答案1

这相当复杂。

首先请注意,通配符在 内部不起作用[[ ]]。如果你运行类似

set -x; [[ / == /* ]]; echo $?

然后你会看到/*那里并没有像 那样展开echo /*

现在有extglobshell 选项。看起来在你的情况下它在交互式 shell 中启用,在非交互式 shell 中禁用。(比较在我的交互式 shell 中,bash 的“shopt extglob”在哪里打开?

如果extglob使用内置命令启用了 shell 选项shopt,则可以识别多个扩展模式匹配运算符。[…]

!(pattern-list)
匹配除给定模式之一之外的任意内容。

来源

运算符内部[[ ]]被识别,但是没有展开。我怎么知道的?我可以让 shell 不识别它:

shopt -u extglob
set -x; [[ !(-z "") ]]; echo $?

现在我得到了-bash: !: event not found。这表明!有不同的含义:

!
开始历史替换,除非后面跟着空格、制表符、行尾=((当extglob使用内置命令启用 shell 选项时shopt)。

来源

交互式和非交互式 shell 之间存在以下差异:

当 shell 以交互方式运行时,它会通过多种方式改变其行为。

[…]
7. 命令历史记录[…]和历史记录扩展[…]默认启用。

来源

下一步是禁用历史记录:

shopt -u extglob
set +o history
set -x; [[ !(-z "") ]]; echo $?

在这里,结果就像在非交互式 shell 中一样。


我们可以反过来做吗?我们能让非交互式 shell 表现得像交互式 shell 吗?

首先让我们检查一下当强制新 shell 处于交互状态时会发生什么:

bash -ic 'set -x; [[ !(-z "") ]]; echo $?'

它的行为与预期一致,就像您的交互式 shell 一样。现在让我们启用extglob非交互式 shell:

bash -c 'shopt -s extglob; set -x; [[ !(-z "") ]]; echo $?'

但不起作用!它仍然表现得像extglob禁用了的非交互式 shell。原因很简单,但并不明显:在shopt执行其工作之前,对整行执行了通配。当选项发生变化时已经太晚了。让我们将命令拆分为两行:

bash -c 'shopt -s extglob
         set -x; [[ !(-z "") ]]; echo $?'

现在它的行为就像你的交互式 shell 一样。


最后一个谜团是:

+ [[ -n !(-z ) ]]

交互式 shell 中的是-n从哪里来的?我的假设是基于[[ "random-string" ]]被视为 的观察结果[[ -n "random-string" ]](用 进行检查set -x)。我猜测当你运行[[ !(-z "") ]]extglob启用时,操作符被识别但未被扩展的事实!( )使得整个!(-z "")字符串作为一个单词保留在那里,即你的命令变成了

set -x; [[ "!(-z )" ]]; echo $?

此命令(在交互式或非交互式 shell 中)的行为与您在交互式 shell 中运行的命令类似。

相关内容