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

Glu*_*ate 17 bash exit error-handling process-substitution

我有一个脚本,使用从SO 上的问答中获取的以下方法将文件名解析为数组:

unset ARGS
ARGID="1"
while IFS= read -r -d $'\0' FILE; do
    ARGS[ARGID++]="$FILE"
done < <(find "$@" -type f -name '*.txt' -print0)
Run Code Online (Sandbox Code Playgroud)

这很好用,可以完美地处理所有类型的文件名变化。但是,有时我会将一个不存在的文件传递给脚本,例如:

$ findscript.sh existingfolder nonexistingfolder
find: `nonexistingfile': No such file or directory
...
Run Code Online (Sandbox Code Playgroud)

在正常情况下,我会让脚本用类似的东西捕获退出代码,RET=$?并用它来决定如何继续。这似乎不适用于上面的过程替换。

在这种情况下,正确的程序是什么?如何捕获返回码?是否有其他更合适的方法来确定替换过程中是否出现问题?

mik*_*erv 7

通过在其标准输出上回显其返回,您可以很容易地从任何子壳进程中获得返回。进程替换也是如此:

while IFS= read -r -d $'\0' FILE || 
    ! return=$FILE
do    ARGS[ARGID++]="$FILE"
done < <(find . -type f -print0; printf "$?")
Run Code Online (Sandbox Code Playgroud)

如果我运行它,那么最后一行 - (或\0视情况而定的分隔部分)将是find的返回状态。read当它获得 EOF 时将返回 1 - 所以唯一的时间$return设置为$FILE读入的最后一位信息。

我过去常常printf避免添加额外的\newline - 这很重要,因为即使是read定期执行的 - 一个您不以\0NUL分隔的字符串 - 在它刚刚读入的数据没有结束的情况下,将返回 0 以外的值一条\n线。因此,如果您的最后一行不以\newline 结尾,则读入变量中的最后一个值将是您的返回值。

运行上面的命令,然后:

echo "$return"
Run Code Online (Sandbox Code Playgroud)

输出

0
Run Code Online (Sandbox Code Playgroud)

如果我更改流程替换部分...

...
done < <(! find . -type f -print0; printf "$?")
echo "$return"
Run Code Online (Sandbox Code Playgroud)

输出

1
Run Code Online (Sandbox Code Playgroud)

更简单的演示:

printf \\n%s list of lines printed to pipe |
while read v || ! echo "$v"
do :; done
Run Code Online (Sandbox Code Playgroud)

输出

pipe
Run Code Online (Sandbox Code Playgroud)

事实上,只要你想要的返回是你从进程替换中写入 stdout 的最后一件事 - 或者你以这种方式读取的任何子进程 - 那么$FILE它总是你想要的返回状态是通过。因此该|| ! return=...部分并不是绝对必要的 - 它仅用于演示概念。


Gil*_*il' 5

进程替换中的进程是异步的: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)
Run Code Online (Sandbox Code Playgroud)

另一种方法是使用命名管道和后台进程(您可以wait使用)。

mkfifo find_pipe
find … >find_pipe &
find_pid=$!
… <find_pipe
wait $find_pid
find_status=$?
Run Code Online (Sandbox Code Playgroud)

如果这两种方法都不适合,我认为您需要转向功能更强大的语言,例如 Perl、Python 或 Ruby。


Feu*_*mel 5

使用协进程。使用coproc内置函数,您可以启动子进程,读取其输出并检查其退出状态:

coproc LS { ls existingdir; }
LS_PID_=$LS_PID
while IFS= read i; do echo "$i"; done <&"$LS"
wait "$LS_PID_"; echo $?
Run Code Online (Sandbox Code Playgroud)

如果目录不存在,wait将以非零状态代码退出。

当前需要将 PID 复制到另一个变量,因为$LS_PIDwait调用之前将被取消设置。在等待 coproc 之前,请参阅Bash 取消设置 *_PID 变量以获取详细信息。

  • 这将随机失败,因为当协进程退出时,“$LS”(即“$LS[0]”)未设置(因此它不仅仅是“LS_PID”)。在循环之前添加一个“sleep 1”,您将看到(“read::invalid file描 述符规范”)。远离“coproc”,它对于不熟悉的人来说是一个陷阱。 (3认同)