使用進程替換時如何正確捕捉退出程式碼/處理錯誤?

使用進程替換時如何正確捕捉退出程式碼/處理錯誤?

我有一個腳本,它使用以下方法將文件名解析為數組:關於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 中處理它的最不痛苦的方法。

相關內容