让我为你画一幅画。您编写一些部署脚本或构建脚本或其他东西等,它们在远程运行命令来执行诸如创建用户、安装/更新包等操作,因此您可能会担心您可能忘记将其添加--gecos ''
到您的adduser
或没有添加不会DEBIAN_FRONTEND=noninteractive
为您的所有公寓或您可能不知道的东西提供这一点。现在,此类脚本往往是非交互式的。您不希望收到任何提示,并且如果由于某种原因发生提示,您希望整个操作失败。然而,我在这种情况下使用 bash 看到的是,它不是失败并返回非 0 错误代码(即使我set -e
),而是做了最糟糕的事情。它会中断并返回 0。这意味着调用者无法知道脚本未正确完成,但实际上已被中断。
这是一个愚蠢的例子,可以轻松地说明和解决这个问题。你可以想象围绕这个脚本发生的很多事情。也许这段代码实际上是在远程使用 ssh 执行的,安装了一些软件包而不是read line
等,并且在这个块之后,您期望所有代码都成功运行。
set -e
# Do stuf...
/bin/bash <<SH
echo gonna try to be interactive
read line #tries to read a line from "user"
echo you wont see me #Will not be executed as Bash will halt execution on previous failing line, this could be vital bit of code to execute, but it's now silently skipped
SH
echo return value $?
#I want to stop here if the above bit of bash failed because some code tried to get user input
输出
gonna try to be interactive
return value 0
我已经注意到,如果我使用bin/sh
而不是 bash,我会得到不同的行为。它不会在读取时自动立即停止执行并返回 0。事实上,默认情况下它会继续前进,我需要明确地set -e
使其“短路”,这样它实际上就会给我返回值 1。但是有什么办法可以让 bash 正常工作吗?
答案1
与任何其他命令一样,您需要显式检查 的返回状态read
或使用set -e
.set -e
不是万能药,但总比不使用好。
没有任何语言功能可以可靠地保护您免受有意义但不执行您想要的操作的代码的侵害。您的代码不会发出错误信号,因为计算机像往常一样,正在执行您告诉它执行的操作,而不是您希望它执行的操作。
/bin/bash <<SH
Bash 的标准输入连接到包含此处文档内容的文件(临时文件或管道,具体取决于运行的 shell)。
read line #tries to read a line from "user"
不,它不会“从用户读取一行”。它从标准输入读取一行。这是此处的文档。效果取决于 shell 如何解析脚本:这就是替换/bin/bash
by/bin/sh
改变行为的原因。对于这个特定的脚本和这个特定版本的 bash,bash 一次只读取一行脚本。因此,当您告诉它读取一行时,它会执行此操作并设置line
为echo you wont see me #Will not be executed as Bash will halt execution on previous failing line, this could be vital bit of code to execute, but it's now silently skipped
。命令成功完成后read
,bash 将执行下一行。由于已到达脚本末尾,因此它返回最后一个命令的状态,即 0。
要确认发生了什么,请添加另一行代码:
set -e
# Do stuf...
/bin/bash <<'SH'
echo gonna try to be interactive
read line #tries to read a line from "user"
echo you wont see me #Will not be executed as Bash will halt execution on previous failing line, this could be vital bit of code to execute, but it's now silently skipped
echo "Previous command='$_'; status=$?; line='$line'"
SH
echo return value $?
输出:
gonna try to be interactive
Previous command='line'; status=0; line='echo you wont see me #Will not be executed as Bash will halt execution on previous failing line, this could be vital bit of code to execute, but it's now silently skipped'
return value 0
命令不正确。该行没有失败,bash 也没有停止执行。该行被跳过,因为您告诉 bash 将其作为数据读取。
您在 dash 中观察到不同的行为,因为它在执行之前读取了整个脚本。例如,如果代码位于函数中,您还会在 bash 中观察到这一点。
如果您希望子 shell 从父 shell 的标准输入读取,请不要重定向子 shell 的标准输入,或者使父 shell 的标准输入在另一个文件描述符上可用。
您编写一些部署脚本或构建脚本或其他东西等,它们在远程运行命令来执行诸如创建用户、安装/更新包等操作,因此您可能会担心您可能忘记添加 --gecos ''到您的 adduser 或没有为您的所有 apt 提供 DEBIAN_FRONTEND=noninteractive 或您甚至可能不知道的东西。现在,此类脚本往往是非交互式的。您不希望收到任何提示,并且如果由于某种原因发生提示,您希望整个操作失败。
这是一个完全不同的问题。确保部署脚本的标准输入已连接到/dev/null
.这样,如果有任何东西尝试从标准输入读取,读取将会失败
如果部署程序尝试从错误的位置读取,或者对读取失败没有正确反应,则这是部署程序中的错误,环境准备无法修复。
¹无法证明代码符合规范,但如果您想这样做,您就不会使用脚本语言。