スクリプト出力の取得と収集中に「入力ファイルが出力ファイルです」エラーが発生しますか?

スクリプト出力の取得と収集中に「入力ファイルが出力ファイルです」エラーが発生しますか?

現在のスクリプトの出力をアップロードする必要があり、trap好きset -exを追加しました。

#!/bin/bash

exec &> /tmp/error.log
trap 'cat /tmp/error.log; curl http://127.0.0.1/error.php?hostname=$(hostname) -F file=@/tmp/error.log' EXIT

set -ex
wtfwtf

実行すると、常にこのエラーが発生し、PHPスクリプトはファイル全体を受け取りません。

%> cat /tmp/error.log
1.sh: line 6: wtfwtf: command not found
cat: /tmp/error.log: input file is output file

これまでの唯一の解決策は、error.logを新しいファイルにコピーしてアップロードすることです。

#!/bin/bash

exec &> /tmp/error.log
trap 'cp /tmp/error.log 123; curl http://127.0.0.1/error.php?hostname=$(hostname) -F file=@123' EXIT

set -ex
wtfwtf

これを行うより良い方法はありますか?

答え1

を使用すると、execスクリプトのすべての出力を特定のログファイルにリダイレクトできます。

トラップ内のcatすべての出力もそのファイルにリダイレクトされるため、GNUはcat入力ファイルと標準出力ストリーム(シェルから継承)が同じであることを確認し、操作の実行を拒否します。

BSD はcatGNU と同じチェックを実行せず、スクリプトが中断catされない場合に繰り返し繰り返される同じ数行を含む無限に大きなログファイルを生成します。

回避策は、元の標準出力ファイル記述子を保存し、以前と同じようにリダイレクトしてからトラップに復元することです。

#!/bin/bash

exec 3>&1                  # make fd 3 copy of original fd 1
exec >/tmp/error.log 2>&1

# in the trap, make fd 1 copy of fd 3 and close fd 3 (i.e. move fd 3 to fd 1)
trap 'exec 1>&3-; cat /tmp/error.log; curl "http://127.0.0.1/error.php?hostname=$(hostname)" -F file=@/tmp/error.log' EXIT

set -ex
wtfwtf

これにより、ログファイルにリダイレクトされる前にファイル記述子1(fd 3)のコピーが作成されます。トラップはこのコピーをfd 1に戻して出力します。

この例のトラップ内の標準エラーストリームはまだログファイルにリンクされています。したがって、curl診断メッセージが生成されると、そのメッセージは端末(または元の標準エラーストリームが接続されている場所)には表示されず、ログファイルに保存されます。


取るコメント: Stéphane Chazelas考えると:

#!/bin/sh

exit_handler () {
    # 1. Make standard output be the original standard error
    #    (by using fd 3, which is a copy of original fd 2)
    # 2. Do the same with standard error
    # 3. Close fd 3.
    exec >&3 2>&3 3>&-
    cat "$logfile"
    curl "some URL" -F "file=@$logfile"
}

logfile='/var/log/myscript.log'

# Truncate the logfile.
: >"$logfile"

# 1. Make fd 3 a copy of standard error (fd 2)
# 2. Redirect original standard output to the logfile (appending)
# 3. Redirect original standard error to the logfile (will also append)
exec 3>&2 >>"$logfile" 2>&1

# Use shell function for exit trap (for neatness)
trap exit_handler EXIT

set -ex
wtfwtf

彼の要点は、ログファイルはとにかく診断メッセージにのみ使用されるため、ログファイルを生の標準エラーストリームに出力する方が合理的であることです。

彼はまた、誰もが書くことができるディレクトリに/tmpファイルが存在しないことを確認するチェックがスクリプトにないことを指摘しています(たとえば、誰かまたはいくつかの悪意のあるプログラムがユーザーに/tmp/error.logシンボリックリンクを作成した可能性があります/etc/passwd)。~/.bashrc回避策は、次のスクリプトに専用の永続ログファイルを/var/log代わりに使用することです(ファイルは永続ですが、スクリプトが実行されると内容は消去されます)。

これに対するバリエーションは、mktemp以下を使用して一意のファイル名を作成することです$TMPDIR(次に失敗しEXITない限り、トラップからファイルを削除します。curlこの場合はrm有効なので実行されません)。set -e

#!/bin/sh

exit_handler () {
    # 1. Make standard output be the original standard error
    #    (by using fd 3, which is a copy of original fd 2)
    # 2. Do the same with standard error
    # 3. Close fd 3.
    exec >&3 2>&3 3>&-
    cat "$logfile"
    curl "some URL" -F "file=@$logfile"
    rm -f "$logfile"
}

logfile=$( mktemp )

# 1. Make fd 3 a copy of standard error (fd 2)
# 2. Redirect original standard output to the logfile (appending)
# 3. Redirect original standard error to the logfile (will also append)
exec 3>&2 >>"$logfile" 2>&1

# Use shell function for exit trap (for neatness)
trap exit_handler EXIT

set -ex
wtfwtf

cat2番目の例は機能しますが、ログファイルをコピーするのではなく、ログファイルを使用しないために機能します。


小さな問題:コマンドラインのURLは、シェルが特殊文字として解釈できる文字(たとえば)を含む傾向があるため、少なくとも二重引用符で囲む必要があります?

関連情報