如何使 bash 在内联执行错误时中止命令

如何使 bash 在内联执行错误时中止命令

Bash 中的命令替换(或者我所说的内联执行)$()允许文本返回;在执行主命令之前。如果 $() 返回错误代码,如何中止主命令

更具体地说,我想停止/防止尝试进行辅助内联执行。

Echo $(ls /devb/*) is better than $(ls /dev/*)

即如果“ls /devb/”返回非零退出代码,我不想运行 /dev 列表,

我的真实示例变得相当冗长,具体细节对于问题来说并不是非常重要,但简化版本是

Connect_to_foreign_system 1.1.1.1 /u:$(zenity <prompt for user>) /p:$(zenity <prompt for password>)

如果取消用户提示,zenity 返回退出代码为 1,但主命令继续,然后提示输入密码。

如果第一次提示错误我真的不想提示输入密码,最好不要运行 Connect_to_foreign_system。

我没有在脚本中运行它,这是纯命令行,但即使我将它放在带有set -e第二个内联命令和主命令的脚本中,仍然会运行。

我知道我可以编写一个脚本来获取每个文本字符串,然后运行命令并测试退出代码。我问如何将其作为独立声明来执行。

答案1

我会做:

cmd1_output=$(cmd1) && cmd2_output=$(cmd2) &&
  printf '%s\n' "Both cmd1 and cmd2 succeeded and their output was respectively $cmd1_output and $cmd2_output"

所以在你的情况下:

user=$(zenity <prompt for user>) &&
  password=$(zenity <prompt for password>) &&
  Connect_to_foreign_system 1.1.1.1 /u:"$user" /p:"$password"

请注意,虽然$(...)上面的 s 不需要在 bash 中引用,因为它们用于标量变量赋值,但与上面的$user和一样$password,它们在命令参数中使用时确实需要引用,因为它们会受到 split+glob 的影响如果不加引号。

请注意,一般来说,在命令行上传递密码是非常糟糕的做法,因为命令行参数通常在系统内是公共的(ps -Af例如在输出中显示)。对于大多数命令,通常有一种更安全的方法来向它们传递机密,无论是通过环境变量还是通过某些文件或文件描述符。

$user为了避免那些只使用一次的and变量污染变量命名空间$password,您可以在子 shell ( ) 中运行整个 and-list (...)

(user=$(zenity <prompt for user>) &&
  password=$(zenity <prompt for password>) &&
  Connect_to_foreign_system 1.1.1.1 /u:"$user" /p:"$password")

或者,如果可以选择从 bash 切换到 zsh,则可以使用匿名函数的位置参数而不是变量:

() {
  1=$(zenity <prompt for user>) &&
    2=$(zenity <prompt for password>) &&
    Connect_to_foreign_system 1.1.1.1 /u:$1 /p:$2
}

(在 zsh 中,split+glob 不会在未加引号的参数扩展上隐式执行,因此不需要引用这些$1/ )。$2


如果只是在失败cmd2时不运行cmd1,但仍然使用 的输出cmd1,在某些 shell 中,包括 ksh93、zsh 和 bash,但不是 dash、busybox sh、yash 或 mksh,您也可以这样做:

printf '%s\n' "output of cmd1: $(cmd1) and of cmd2 (not run if cmd1 failed) $([ "$?" -eq 0 ] && cmd2)"

答案2

这种内联执行称为命令替换。要检查某个命令的退出状态,您需要将其放在没有其他命令的命令上,而只是一个用于捕获输出的分配。例如:

if a=$(foo); then
    echo >&2 "first command failed, exiting"
    exit 1
fi
b=$(bar)
echo "output of first command: '$a', output of second: '$b'"

请注意,这$(ls /dev/*)有点愚蠢,因为 shell 扩展了 glob /dev/*,而ls要做的就是打印 shell 提供的文件名。 (除非您也使用-l-F来获取其他信息。)尽管ls会列出 glob 匹配的任何子目录的内容。不管怎样,类似的事情可能值得重新考虑。如果您只想要 的第一级内容/dev,则只需使用ls /dev, 或echo /dev/*代替。

相关内容