![使用进程替换时如何正确捕获退出代码/处理错误?](https://linux22.com/image/36389/%E4%BD%BF%E7%94%A8%E8%BF%9B%E7%A8%8B%E6%9B%BF%E6%8D%A2%E6%97%B6%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E6%8D%95%E8%8E%B7%E9%80%80%E5%87%BA%E4%BB%A3%E7%A0%81%2F%E5%A4%84%E7%90%86%E9%94%99%E8%AF%AF%EF%BC%9F.png)
我有一个脚本,它使用以下方法将文件名解析为数组:关于SO的问答:
unset ARGS
ARGID="1"
while IFS= read -r -d $'\0' FILE; do
ARGS[ARGID++]="$FILE"
done < <(find "$@" -type f -name '*.txt' -print0)
这非常有效,可以完美处理所有类型的文件名变体。然而,有时我会将一个不存在的文件传递给脚本,例如:
$ findscript.sh existingfolder nonexistingfolder
find: `nonexistingfile': No such file or directory
...
在正常情况下,我会让脚本捕获类似的退出代码RET=$?
,并用它来决定如何继续。这似乎不适用于上面的过程替换。
在这种情况下,正确的程序是什么?如何捕获返回码?是否有其他更合适的方法来确定替换过程中是否出现问题?
答案1
您可以通过在其标准输出上回显任何子 shell 进程的返回值来轻松获取其返回值。进程替换也是如此:
while IFS= read -r -d $'\0' FILE ||
! return=$FILE
do ARGS[ARGID++]="$FILE"
done < <(find . -type f -print0; printf "$?")
如果我运行它,那么最后一行 -(或\0
定界部分,视情况而定)将是find
的返回状态。read
当它收到 EOF 时将返回 1 - 因此唯一$return
设置为 的时间$FILE
是读入信息的最后一位。
我过去printf
常常避免添加额外的\n
ewline - 这很重要,因为即使read
定期执行 - 不以\0
NUL 分隔 - 也会在刚刚读入的数据不以 结尾的情况下返回 0 以外的值一条\n
线。因此,如果您的最后一行不以\n
ewline 结尾,则读入变量中的最后一个值将是您的返回值。
运行上面的命令,然后:
echo "$return"
输出
0
如果我改变进程替换部分......
...
done < <(! find . -type f -print0; printf "$?")
echo "$return"
输出
1
更简单的演示:
printf \\n%s list of lines printed to pipe |
while read v || ! echo "$v"
do :; done
输出
pipe
事实上,只要您想要的返回是您从进程替换中写入 stdout 的最后一个内容(或者您以这种方式读取的任何子外壳进程),那么$FILE
当它发生时,它总是会是您想要的返回状态是通过了。因此该|| ! return=...
部分并不是绝对必要的 - 它仅用于演示概念。
答案2
用一个协进程。使用coproc
内置函数,您可以启动子进程,读取其输出并检查其退出状态:
coproc LS { ls existingdir; }
LS_PID_=$LS_PID
while IFS= read i; do echo "$i"; done <&"$LS"
wait "$LS_PID_"; echo $?
如果该目录不存在,wait
将以非零状态代码退出。
目前需要将 PID 复制到另一个变量,因为在调用$LS_PID
之前将被取消设置。wait
看在我可以等待 coproc 之前,Bash 取消设置 *_PID 变量了解详情。
答案3
进程替换中的进程是异步的:shell 启动它们,然后不提供任何方式来检测它们何时终止。因此您将无法获得退出状态。
您可以将退出状态写入文件,但这通常很笨拙,因为您无法知道文件何时写入。在这里,文件是在循环结束后不久写入的,因此等待它是合理的。
… < <(find …; echo $? >find.status.tmp; mv find.status.tmp find.status)
while ! [ -e find.status ]; do sleep 1; done
find_status=$(cat find.status; rm find.status)
另一种方法是使用命名管道和后台进程(您可以wait
使用)。
mkfifo find_pipe
find … >find_pipe &
find_pid=$!
… <find_pipe
wait $find_pid
find_status=$?
如果两种方法都不合适,我认为您需要使用一种功能更强大的语言,例如 Perl、Python 或 Ruby。
答案4
一种方法是:
status=0
token="WzNZY3CjqF3qkasn" # some random string
while read line; do
if [[ "$line" =~ $token:([[:digit:]]+) ]]; then
status="${BASH_REMATCH[1]}"
else
echo "$line"
fi
done < <(command; echo "$token:$?")
echo "Return code: $status"
这个想法是在命令完成后回显退出状态以及随机令牌,然后使用 bash 正则表达式来查找并提取退出状态。该标记用于创建要在输出中查找的唯一字符串。
从一般编程意义上来说,这可能不是最好的方法,但它可能是在 bash 中处理它的最不痛苦的方法。