私の関数にパイプエラーが発生したため、一度失敗した後にコマンドは無視されます。

私の関数にパイプエラーが発生したため、一度失敗した後にコマンドは無視されます。

コンテキスト:ファイルをコピーする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が返されるため、パイプの右側のプロセスが終了し、それがパイプの読み取り端を閉じます。 2番目のエラーメッセージが出されると、閉じたcpパイプに書き込もうとします。SIGPIPE信号。標準エラーはラインバッファリングされているため(デフォルトではcp変更しようとしません)、バッファリングは効果がありません。

すべての入力行を処理するには繰り返す必要がありますread

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

私もread通話を修正しました。IFS= read -r実際に1行を読んでください。空行の特別な処理を削除しましたが、それは無意味でした(入力に空行はありません)。入力が空の場合(入力0行)を処理するために入れたようですが、これを処理する正しい方法は、コマンドの戻り状態を確認することですread。また、logエラーメッセージの処理に使用される標準エラー印刷を修正しました。

バラよりコマンド出力の各行の前にタイムスタンプを追加します。これを行う他の方法について学びます。

パイプの左側にコマンドを配置すると、大きな欠点が1つあります。終了ステータスは無視されます。。したがって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

おすすめ記事