仅用空格而不是 ;分号;或 && 或 &&

Chaining bash or dash commands with just a space instead of ; semicolon ; or && or &&

提问人:snovotill 提问时间:9/24/2023 最后编辑:snovotill 更新时间:9/30/2023 访问量:101

问:

正常的命令链接方法可以正常工作,例如:

$ (SCRATCH='heya'; echo $SCRATCH)
heya

或者使用逻辑运算符 && 产生可预测的结果:

$ (SCRATCH='heya' && echo $SCRATCH)
heya

但是,如果我们使用空格来链接命令,那么事情就会变得很奇怪:

$ (SCRATCH='heya' echo $SCRATCH)
<<just a blank line prints here>>

尽管有上述情况,但变量实际上设置了 WAS,我可以用这一行来证明它:

$ (DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u $USER)/bus" grdctl status)
RDP:
        Status: enabled
        TLS certificate: /home/guest/.local/share/gnome-remote-desktop/rdp-tls.crt
        TLS key: /home/guest/.local/share/gnome-remote-desktop/rdp-tls.key
        View-only: no
        Username: (hidden)
        Password: (hidden)

上面没有分号分隔两个命令,奇怪的是,它实际上以某种方式读取变量并起作用。

更奇怪的是,我可以通过插入正常的必要条件来破坏它 分号;这真的应该在那里,就像这样:

$ (DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u $USER)/bus"; grdctl status)
Failed to lookup legacy VNC password schema: Cannot autolaunch D-Bus without X11 $DISPLAY
Failed to lookup RDP credentials: Cannot autolaunch D-Bus without X11 $DISPLAY

接下来,将冒犯的分号留在原地...... 我可以通过导出变量来使它恢复生机, 这让我认为 grdctl 命令将自己放入一个子 shell 中:

$ (export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u $USER)/bus"; grdctl status)
RDP:
        Status: enabled
        TLS certificate: /home/guest/.local/share/gnome-remote-desktop/rdp-tls.crt
        TLS key: /home/guest/.local/share/gnome-remote-desktop/rdp-tls.key
        View-only: no
        Username: (hidden)
        Password: (hidden)

然而,如果我们直接运行以下空格分隔的命令,那么变量就会消失在以太中,无影无踪,甚至没有任何错误!

$ SCRATCH='heya' echo $SCRATCH
<<just a blank line prints here>>

如果我导出变量,那么情况会变得更糟,甚至不会打印空行!

$ export SCRATCH='heya' echo $SCRATCH
<<nothing shows here, not even a blank line now>>

#And again for good measure, same result:
$ export SCRATCH='heya' echo $SCRATCH
<<still nothing, same as before>>

#But yeah, the variable did export to my shell because:
$ echo $SCRATCH
heya

因此,分号 send 不仅使 echo 无法访问变量,而且还会阻止 echo 看到父环境!

而且不仅仅是内置的 echo 命令被灌注,printf 也生病了:

$ EPHEMERAL='blink' printf "$EPHEMERAL\n"
<<again, a suffocated blank line here>>

但是 grdctl 命令是一个二进制文件(既不是脚本也不是内置文件),非常乐意摄取我的变量(不带分号),并正确输出:

$ unset DBUS_SESSION_BUS_ADDRESS
$ DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u $USER)/bus" grdctl status
RDP:
        Status: enabled
        TLS certificate: /home/guest/.local/share/gnome-remote-desktop/rdp-tls.crt
        TLS key: /home/guest/.local/share/gnome-remote-desktop/rdp-tls.key
        View-only: no
        Username: (hidden)
        Password: (hidden)

上述行为似乎非常不一致,因为 echo 命令的行为方式是另一种方式,而 grdctl 的行为方式是另一种方式。

我不停地用谷歌搜索并咨询人工智能,但没有人能解释命令与空格的链式连接,这绝对是“一回事”,因为它经常有效。

互联网上有数以万计的解释;或 && 或 ||可用于链接命令,但不能窥视空格字符。

--------------我的最终更新--------------

好的,基于下面的所有良好讨论,这是解决方法#1:

$ YUCK=pid; YUCK=stu sh -c 'echo -n $YUCK';echo $YUCK

这是解决方法#2:

$ echo 'printf $SOMEVAR"\n"' > myscript.sh; chmod u+x myscript.sh
$ SOMEVAR='blink' ./myscript.sh

显然,有些命令需要变通方法,而另一些则不需要。

bash 变量 命令 链接 子 shell

评论

3赞 Dennis Williamson 9/24/2023
echo不是外部可执行文件,也不会从其环境中读取变量值。它是一个内置的。您不是在用空格链接 - 您正在尝试在可执行文件的环境中设置变量的值。
0赞 snovotill 9/24/2023
有趣的观察,尽管如此,我认为用于呈现$SCRATCH内容的解析序列在任何情况下都是相同的......或者是吗?我非常乐意始终使用分号并在必要时添加“导出”,但是我已经看到在几个脚本中使用了“空格”连接器,因此以某种易于理解的方式记录这一点会很棒。
3赞 Dennis Williamson 9/24/2023
foo=12 some_program在 的环境中将值设置为 12。 执行相同的操作,只是变量及其值在任何后续可执行文件的环境中都可用。 是一个内置的。它不会生成,因此不会获得新环境。它也不会从环境中读取变量。foosome_programexport foo=12echo
0赞 snovotill 9/24/2023
我刚刚做了一些测试,发现 printf 与 echo 有相同的问题,但 printf 不是内置的:$ whereis printf ... /usr/bin/printf,所以其他东西潜伏在阴影中。
0赞 Ed Morton 9/24/2023
仅仅因为系统上有一个名为的命令,并不意味着您的代码没有一个名为该命令的内置命令,而不是外部命令。尝试而不是然后(调用您实际使用的版本,可能是内置的)vs(调用您可能未使用的外部命令的版本)。printfprintftype -a printfwhereis printfprintf --versionprintf/usr/bin/printf --versionprintf

答:

1赞 Sylvain 9/24/2023 #1

事实是,bash 允许在执行命令之前“动态”定义导出的临时环境变量。

SOME_VAR_TO_BE_EXPORTED="value only for the command" some_command

例:

echo 'printf "MY_ENV_VAR=%s" $MY_ENV_VAR\n' > myscript.sh
chmod a+x ./myscript.sh

现在让我们使用带前缀的 env var 定义来执行它

# just to be sure
unset MY_ENV_VAR
MY_ENV_VAR=value ./myscript.sh

将输出:

MY_ENV_VAR=value

但范围仅在调用期间存在(假设之前未设置)$MY_ENV_VAR

echo $MY_ENV_VAR

应为空

这里有一些解释:

https://www.baeldung.com/linux/set-env-variables-bash-command

官方文档在这里

https://www.gnu.org/software/bash/manual/html_node/Environment.html

该段:

任何简单命令或函数的环境都可以通过在环境前面加上参数赋值来临时扩充,如 Shell 参数中所述。这些赋值语句仅影响该命令所看到的环境。

所以是的,这是一个令人困惑的语法。这里缺少分号是故意用于此特定语法的,仅适用于 envvar 分配。

分号将产生一个不同的行为:2 条指令。所以定义变量,没有必要也导出它,然后执行命令(最终在执行第二条指令之前扩展变量)


怎么样。。。什么都打印不出来?EPHEMERAL='blink' printf "$EPHEMERAL\n"

好吧,临时 envvar 导出可以解释为以下伪代码:

old_EPHEMERAL=$EPHEMERAL
export EPHEMERAL='blink'
# assuming `printf` is any command or function to call here 
printf "$old_EPHEMERAL\n"
if [[ -n $old_EPHEMERAL ]]
then
  EPHEMERAL=$old_EPHEMERAL
fi
# the old value, was only mentioned for the pseudo-code
unset old_EPHEMERAL

但多合一!

这意味着在评估多合一时尚不可用。export EPHEMERAL='blink'

因此,当评估(尚未调用)时,值是导出发生之前的任何值。然后,使用参数调用该命令,并且 envvar 也变为可用(在调用期间具有值)printf "$EPHEMERAL\n"$EPHEMERALblink


一个不见了,我很好奇,我终于明白了:

打印什么都没有,即使有旧值。export SCRATCH='heya' echo $SCRATCH$SCRATCH

export是一个内置命令,它以一些变量名称并更改其导出状态作为参数。它可以接受多个参数,有赋值和无赋值。它不输出任何内容,并且大多数情况下总是返回 0(成功),即使在有错误的赋值中也是如此。它只在分配失败的罕见用例中返回失败状态:例如无效的变量名称、系统故障(如内存不足)或只读变量赋值等。请参见:SC2155$?v=$(false)

在下面的伪代码中分解也是如此,仍然是一体式的。⚠️ 注意:这不会被识别为临时增强的 prefixing-with-parameter,因为它被评估为命令export SCRATCH='heya' echo $SCRATCH

# pseudo code decomposition:
# export and assign $SCRATCH
export SCRATCH='heya'
# export a variable named $echo
export echo
# export a variable from the expansion of the old value of $SCRATCH
export $SCRATCH

够棘手吗?😉

您可以使用以下额外命令查看结果:

export -p | grep -E 'echo|SCRATCH|heya'
# outputs
declare -x SCRATCH="heya"
declare -x echo

如果运行两次,它会输出什么?(留给读者作为练习)export SCRATCH='heya' echo $SCRATCH

使用 bash 版本 5.1.16 进行测试(有关官方手册部分,另请参阅上面⬆️的 bash 链接: 3.7.4 环境)


希望对您有所帮助,请继续阅读文档,并且可能会查看 shellcheck,😉您可能会学到更多技巧。

想成为一名命令行战士吗?愿本站仍供稿

https://www.commandlinefu.com/commands/browse/sort-by-votes

评论

0赞 snovotill 9/24/2023
虽然您的示例确实提供了第二个演示,说明直接在脚本(不带分号)上设置变量实际上是如何工作的。目前还没有提供确凿的解释,为什么同样的 shtick 不适用于 echo 或 printf。$ EPHEMERAL='blink' printf “$EPHEMERAL\n” ...它不打印任何内容,它与您的演示以及我的 grdctl 命令示例形成鲜明对比,在我原始帖子的末尾。这里有一些重要而微妙的东西需要学习。顺便说一句,感谢您澄清这实际上都是关于变量赋值的。
0赞 Sylvain 9/24/2023
@snovotill你是对的,这里也有点微妙。因此,让我们编辑以发布...
0赞 Sylvain 9/24/2023
有趣的是,我在这里得到了 -1 票。 🤷
0赞 snovotill 9/25/2023
我对此投了赞成票,因为它确实使 printf 按预期工作(...echo 'echo $MY_ENV_VAR' > myscript.sh ...此外),当在脚本中调用时,但应该有人解释为什么外部命令可以工作,而内置命令不能(如果这实际上是整个故事,但事实可能不是)。
0赞 Sylvain 9/25/2023
@snovotill根据我的测试和经验:内置与非内置在这里没有任何关系。这是他关于“范围”与“通过前缀临时增强”以及如何“评估”bash 中的命令。你抓住了它还是它仍然令人困惑?你现在明白为什么“SCRATCH='heya' echo $SCRATCH”打印注意或“export SCRATCH='heya' echo $SCRATCH”打印任何东西了吗?这不是出于同样的原因。将“echo”替换为“./myecho.sh”“echo 'echo $1' > ./myecho.sh”不会改变该行为。😉