我的函數的管道錯誤導致命令在 1 次失敗後被忽略

我的函數的管道錯誤導致命令在 1 次失敗後被忽略

上下文:我有一個複製文件的 Bash 腳本

function log () {
    read IN
    if [ "$IN" == "" ]; then
        :
    else

        echo "$datetime"$'\t'"$IN" | tee -a logfile
    fi
}

function copy () {
    command cp -L --parents $@
}

...
copy -R /etc . 2>&1 | log
...

問題:當cp -L --parents -R /etc 2>&1手動運行時,我遇到了大約 10 次失敗(符號鏈結損壞,這是預料之中的),並且整個 /etc 已被複製。

但是當腳本運行時,只報告 1 次失敗,並且 /etc 只複製到 1 次失敗發生的位置。

在嘗試排除故障時,我所做的就是2>&1從腳本中刪除,然後複製按預期進行。

問題:是我的log函數造成了麻煩,還是腳本編寫方式有語法問題(儘管不是破壞腳本)?

答案1

你的log功能是罪魁禍首。它的作用是:讀取一行,如果該行不為空,則列印時間戳,然後列印該行內容。這就是它的全部作用:一旦處理完一行,它就會回來。

cp發出第一條錯誤訊息時,log函數會讀取並處理它。由於log函數隨後返回,因此管道右側的進程退出,這導致管道的讀取端關閉。當cp發出第二個錯誤訊息時,它會嘗試寫入關閉的管道,這會導致它因SIGPIPE訊號。標準錯誤是行緩衝的(預設情況下,並且cp不會嘗試更改它),因此緩衝不起作用。

要處理所有輸入行,您需要read循環。

log () {
    while IFS= read -r IN; do
        echo "$datetime"$'\t'"$IN"
    done | tee -a logfile >&2
}

我還修復了read呼叫IFS= read -r實際讀取一行。我刪除了對空白行的特殊處理,這是毫無意義的(輸入中不會有空行)。我認為您將其放入是為了處理輸入為空(零行輸入)時的情況,但處理該問題的正確方法是檢查命令的返回狀態read。我還修復了log列印其標準錯誤,因為它用於處理錯誤訊息。

在命令的每一行輸出前添加時間戳了解其他方法來執行此操作。

請注意,將命令放在管道的左側有一個主要缺點:它導致退出狀態被忽略。因此,如果cp失敗,您的腳本將愉快地繼續。錯誤將記錄在某處,但後續命令將正常執行,並且不會提醒您應該去閱讀日誌。在 bash、ksh 或 zsh 中,您可以設定pipefail選項除此之外,set -e一旦任何命令失敗,即使在管道的左側,您的腳本也會以錯誤狀態退出。

set -o errexit -o pipefail
copy … |& log

或者,使用流程替代而不是透過另一個程序透過管道傳輸錯誤輸出的管道。進程替代的注意事項與管道略有不同;中的錯誤log會被有效地忽略,並且命令可能會在log完成之前返回(在 bash 中,log在後台運行)。

set -e
copy … 2> >(log)

幾乎,您不需要IFS= read -r IN閱讀更多內容,也不可能破壞該行。

相關內容