我定义了以下 shell 函数:
success() {
printf "[\033[32mSUCCESS\033[0m]\n"
}
failure() {
printf "[\033[31mFAILURE\033[0m]\n"
}
try() {
result=$($* 2>&1)
if [ $? -ne 0 ]; then
failure
echo $result
exit 1
fi
success
}
这使我能够静默执行任何命令,捕获所有输出,并显示SUCCESS
或FAILURE
消息。仅当命令失败时,才会显示命令输出以供调试:
try rm -r $tmp
但我刚刚意识到它仅限于单个命令,并且对于通过管道传输到另一个命令的命令来说会惨败|
:
try git archive --format=tar HEAD | tar xf - -C $tmp
因为try
仅在命令上执行git
,然后将 的输出try
通过管道传输到tar
,而不是 的输出git
。
是否可以将这两个命令git ... | tar ...
作为单个参数传递给try
,捕获两者的输出,并检查两者是否git
都tar
返回0
?
为了实现我的目标,还有其他考虑吗?
答案1
如果您想git … | tar …
直接将管道(它是包含两个子命令的单个命令,而不是两个单独的命令)作为参数传递给函数,则需要构建一个包含该命令的字符串,并使用eval
函数中的内置函数来将这个字符串作为 shell 命令执行。
注意正确的引用。对于参数,您正在构建一个字符串,它是一个迷你 shell 脚本,使用与封闭脚本相同的变量。特别是,如果变量包含文件名(如此tmp
处),则需要传递变量扩展,而不是变量的值,因为文件名不是 shell 片段,所以您需要'…"$tmp"…'
,而不是"…$tmp…"
。评估时,您需要将确切的字符串传递给eval
,而不是已经进行扩展的字符串。特别是,$*
几乎总是错误的;读为什么我的 shell 脚本会因为空格或其他特殊字符而卡住?
try () {
result=$(eval "$1" 2>&1)
if [ $? -ne 0 ]; then
failure
echo $result
exit 1
fi
success
}
try 'git archive --format=tar HEAD | tar xf - -C "$tmp"'
另一种方法是将复合命令填充到函数中。再次强调,注意正确引用。用于"$@"
将函数的参数计算为简单命令(别名、函数、带参数的内置或外部命令)。
try () {
result=$(eval "$1" 2>&1)
if [ $? -ne 0 ]; then
failure
echo $result
exit 1
fi
success
}
archive_to_directory () {
git archive --format=tar HEAD | tar xf - -C "$1"'
}
try archive_to_directory "$tmp"
管道的状态是其右侧的状态;左侧的状态被忽略。在 bash 中(但不在 sh 中),您可以通过以下方式访问管道中所有命令的状态:PIPESTATUS
多变的。
try () {
result=$(eval "$1" 2>&1)
if [ $? -ne 0 ]; then
failure
echo $result
exit 1
fi
success
}
archive_to_directory () {
git archive --format=tar HEAD | tar xf - -C "$1"'
[[ -n ${PIPESTATUS[*]//[0 ]/} ]]
}
try archive_to_directory "$tmp"
答案2
您必须在命令行上添加一些引用,这是无法避免的
try "git archive --format=tar HEAD | tar xf - -C $tmp"
然后在函数中,由于 shell 元字符(如|
)在存储在变量中时并不特殊,因此您需要eval
try() {
if result=$(eval "$*" 2>&1); then
success
else
failure
echo $result
exit 1
fi
}
我有一种感觉,我还没有完全正确地理解这一点。希望有人能纠正我。