web-dev-qa-db-ja.com

スクリプト出力をトラップして収集します。「入力ファイルは出力ファイルです」エラー?

現在のスクリプトの出力をアップロードする必要があるため、trapset -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

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

2
daisy

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

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

BSD catはGNU 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éphaneChazelasからのコメント を考慮に入れる:

#!/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トラップから削除しますこの場合、set -eが有効であるため、rmは実行されません):

#!/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

2番目の例は機能しますが、ログファイルでcatを使用していないためであり、コピーのためではありません。


マイナーな落とし穴:コマンドラインのURLは、シェルが特別と解釈する可能性のある文字(たとえば、?)を含む傾向があるため、おそらく常に少なくとも二重引用符で囲む必要があります。

6
Kusalananda