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


13

我有一个脚本,该脚本使用从SO上的Q&A采取的以下方法将文件名解析为数组:

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=$?使用类似的代码捕获退出代码,然后使用它来决定如何继续。这似乎不适用于上面的流程替换。

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

Answers:


5

您可以很容易地从任何带壳的进程中获得返回值,只需在其标准输出上回显其返回值即可。流程替换也是如此:

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定期执行(您不限制NUL的定期执行),在\0刚刚读入的数据未以结尾的情况下,也将返回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=...部分不是严格必需的-仅用于演示概念。


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)

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

mkfifo find_pipe
find  >find_pipe &
find_pid=$!
 <find_pipe
wait $find_pid
find_status=$?

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


感谢您的回答。您描述的方法可以正常工作,但我必须承认,它们比我预期的要复杂一些。就我而言,我在问题中显示的那个循环访问所有参数之前就解决了一个循环,如果其中一个不是文件或文件夹,则会打印一个错误。尽管这不能处理替代过程中可能发生的其他类型的错误,但对于这种特定情况已经足够了。如果在这种情况下我需要更复杂的错误处理方法,那么我一定会回复您的答案。
谷氨酰胺

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_PIDwait调用之前将其未设置。在我可以等待coproc之前,请参阅Bash unsets * _PID变量以获取详细信息。


1
我很好奇一个人何时会使用<&“ $ LS”与读取-u $ LS?-谢谢
布赖恩·克里斯曼

1
@BrianChrisman在这种情况下,可能永远不会。read -u应该也一样。该示例是通用的,展示了如何将协过程的输出传递到另一个命令中。
Feuermurmel

1

一种方法是:

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中处理它的痛苦可能最小。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.