使用进程替换时如何正确捕获退出代码/处理错误?

使用进程替换时如何正确捕获退出代码/处理错误?

我有一个脚本,它使用以下方法将文件名解析为数组:关于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常常避免添加额外的\newline - 这很重要,因为即使read定期执行 - 不以\0NUL 分隔 - 也会在刚刚读入的数据不以 结尾的情况下返回 0 以外的值一条\n线。因此,如果您的最后一行不以\newline 结尾,则读入变量中的最后一个值将是您的返回值。

运行上面的命令,然后:

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 中处理它的最不痛苦的方法。

相关内容