Bashでstderrとstdoutをリダイレクトする
質問
プロセスのstdoutとstderrの両方を単一のファイルにリダイレクトしたい。 Bashでどのように行うのですか?
解決
他のヒント
do_something 2>&1 | tee -a some_file
これは、stderrをstdoutにリダイレクトし、stdoutを some_file
にリダイレクトし、stdoutに出力します。
stderr を stdout および stdout にファイルにリダイレクトできます:
some_command >file.log 2>&1
http://tldp.org/LDP/abs/html/ioを参照-redirection.html
この形式は、最も人気のある&>よりも優先されます。 bashでのみ機能する形式。 Bourneシェルでは、コマンドをバックグラウンドで実行していると解釈できます。また、形式は読みやすい2(STDERR)から1(STDOUT)にリダイレクトされます。
編集:コメントで指摘されているように順序を変更しました
# Close STDOUT file descriptor
exec 1<&-
# Close STDERR FD
exec 2<&-
# Open STDOUT as $LOG_FILE file for read and write.
exec 1<>$LOG_FILE
# Redirect STDERR to STDOUT
exec 2>&1
echo "This line will appear in $LOG_FILE, not 'on screen'"
現在、単純なエコーは$ LOG_FILEに書き込みます。デーモン化に役立ちます。
元の投稿の作成者へ
何を達成する必要があるかによります。スクリプトから呼び出すコマンドのイン/アウトをリダイレクトするだけでよい場合、答えはすでに与えられています。私は、上記のコードスニペットの後にすべてのコマンド/ビルトイン(フォークを含む)に影響する現在のスクリプト内で をリダイレクトすることについてです。
もう1つのクールなソリューションは、std-err / outとロガーまたはログファイルの両方に一度にリダイレクトすることです。 2つに。この機能は、複数のファイル記述子(ファイル、ソケット、パイプなど)に一度に書き込み/追加できる「tee」コマンドによって提供されます。tee FILE1 FILE2 ...&gt;(cmd1)&gt;(cmd2)...
exec 3>&1 4>&2 1> >(tee >(logger -i -t 'my_script_tag') >&3) 2> >(tee >(logger -i -t 'my_script_tag') >&4)
trap 'cleanup' INT QUIT TERM EXIT
get_pids_of_ppid() {
local ppid="$1"
RETVAL=''
local pids=`ps x -o pid,ppid | awk "\\$2 == \\"$ppid\\" { print \\$1 }"`
RETVAL="$pids"
}
# Needed to kill processes running in background
cleanup() {
local current_pid element
local pids=( "$" )
running_pids=("${pids[@]}")
while :; do
current_pid="${running_pids[0]}"
[ -z "$current_pid" ] && break
running_pids=("${running_pids[@]:1}")
get_pids_of_ppid $current_pid
local new_pids="$RETVAL"
[ -z "$new_pids" ] && continue
for element in $new_pids; do
running_pids+=("$element")
pids=("$element" "${pids[@]}")
done
done
kill ${pids[@]} 2>/dev/null
}
だから、最初から。 / dev / stdout(FD#1)と/ dev / stderr(FD#2)に端末が接続されていると仮定しましょう。実際には、パイプ、ソケットなど何でも構いません。
- FD#3および#4を作成し、同じ「場所」をポイントします。それぞれ#1と#2として。 FD#1を変更しても、今後FD#3には影響しません。現在、FD#3と#4はそれぞれSTDOUTとSTDERRを指しています。これらは、実際の端末のSTDOUTおよびSTDERRとして使用されます。
- 1&gt; &gt;(...)STDOUTを括弧内のコマンドにリダイレクトします
- parens(sub-shell)は、execのSTDOUT(pipe)から「tee」の読み取りを実行し、parensのサブシェルへの別のパイプを介して「logger」コマンドにリダイレクトします。同時に同じ入力をFD#3(端末)にコピーします
- 非常によく似た2番目の部分は、STDERRとFD#2および#4に対して同じトリックを行うことです。
上記の行とさらに次の行を含むスクリプトを実行した結果:
echo "Will end up in STDOUT(terminal) and /var/log/messages"
...は次のとおりです:
$ ./my_script
Will end up in STDOUT(terminal) and /var/log/messages
$ tail -n1 /var/log/messages
Sep 23 15:54:03 wks056 my_script_tag[11644]: Will end up in STDOUT(terminal) and /var/log/messages
より鮮明な画像を表示する場合は、次の2行をスクリプトに追加します。
ls -l /proc/self/fd/
ps xf
bash your_script.sh 1>file.log 2>&1
1&gt; file.log
は、STDOUTをファイル file.log
に送信するようシェルに指示し、 2&gt;&amp; 1
はそれを通知しますSTDERR(ファイル記述子2)をSTDOUT(ファイル記述子1)にリダイレクトします。
注: liw.fiが指摘したように順序が重要で、 2&gt;&amp; 1 1&gt; file.log
は機能しません。
奇妙なことに、これは動作します:
yourcommand &> filename
ただし、これにより構文エラーが発生します。
yourcommand &>> filename
syntax error near unexpected token `>'
使用する必要があります:
yourcommand 1>> filename 2>&1
簡単な回答:コマンド&gt; filename 2&gt;&amp; 1
またはコマンド&amp;&gt; filename
説明:
「stdout」という単語を出力する次のコードを検討してください。標準出力と「stderror」という単語stderrorに。
$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror
「&amp;」演算子は、bashに2がファイル名ではなく(stderrを指す)ファイル記述子であることを伝えます。 「&amp;」を省略した場合、このコマンドは stdout
をstdoutに出力し、&quot; 2&quot;という名前のファイルを作成します。そしてそこに stderror
を書きます。
上記のコードを試してみると、リダイレクト演算子がどのように機能するかを自分で確認できます。たとえば、2つの記述子 1,2
のどちらを / dev / null
にリダイレクトするかを変更することにより、次の2行のコードがstdoutからすべてを削除します。それぞれstderrorからすべて(残っているものを印刷)。
$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout
今、なぜ次のコードが出力を生成しないのかという解決策を説明できます:
(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1
これを本当に理解するために、を読むことを強くお勧めします。ファイル記述子テーブルのウェブページ。あなたがその読書をしたと仮定して、私たちは先に進むことができます。 Bashプロセスは左から右に処理されます。したがって、Bashは最初に&gt; / dev / null
( 1&gt; / dev / null
と同じ)を見て、ファイル記述子1が/ dev /を指すように設定します。標準出力の代わりにnull。これを実行すると、Bashは右に移動し、 2&gt;&amp; 1
が表示されます。これにより、ファイル記述子2は、ファイル記述子1と同じファイルを指すように設定されます(ファイル記述子1自体ではありません!!!!(詳細については、ポインタに関するこのリソース))。ファイル記述子1は/ dev / nullを指し、ファイル記述子2はファイル記述子1と同じファイルを指すため、ファイル記述子2も/ dev / nullを指します。したがって、両方のファイル記述子は/ dev / nullを指し、これが出力がレンダリングされない理由です。
概念を本当に理解しているかどうかをテストするには、リダイレクトの順序を切り替えたときに出力を推測してみてください:
(echo "stdout"; echo "stderror" >&2) 2>&1 >/dev/null
stderror
ここでの理由は、左から右に評価すると、Bashは2&gt;&amp; 1を認識し、ファイル記述子2がファイル記述子1と同じ場所、つまりstdoutを指すように設定するためです。次に、ファイル記述子1(&gt; / dev / null = 1&gt; / dev / nullを忘れないでください)が&gt; / dev / nullを指すように設定し、通常標準出力に送信されるすべてのものを削除します。したがって、残されているのは、サブシェルのstdout(括弧内のコード)に送信されなかったもの、つまり「stderror」だけです。
興味深いのは、1が単なる標準出力へのポインタであっても、 2&gt;&amp; 1
を介してポインタ2を1にリダイレクトしても、ポインタ2のチェーンが形成されないことです。 1-&gt;標準出力。もしそうなら、1を/ dev / nullにリダイレクトした結果、コード 2&gt;&amp; 1&gt; / dev / null
はポインターチェーン2を与えます-&gt; 1-&gt; / dev / null。したがって、上記で見たものとは対照的に、コードは何も生成しません。
最後に、これを行う簡単な方法があることに注意してください:
セクション3.6.4 こちらから、演算子&amp;&gt;
を使用して、stdoutとstderrの両方をリダイレクトできます。したがって、コマンドのstderr出力とstdout出力の両方を \ dev \ null
(出力を削除する)にリダイレクトするには、単に次のように入力します。
$コマンド&amp;&gt; / dev / null
または私の例の場合:
$ (echo "stdout"; echo "stderror" >&2) &>/dev/null
重要なポイント:
- ファイル記述子はポインターのように動作します(ただし、ファイル記述子はファイルポインターと同じではありません)
- fiのリダイレクト
LOG_FACILITY="local7.notice"
LOG_TOPIC="my-prog-name"
LOG_TOPIC_OUT="$LOG_TOPIC-out[$]"
LOG_TOPIC_ERR="$LOG_TOPIC-err[$]"
exec 3>&1 > >(tee -a /dev/fd/3 | logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_OUT" )
exec 2> >(logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_ERR" )
関連するもの:stdOutの記述&amp; stderrからsyslogへ。
ほとんど動作しますが、xintedからは動作しません;(
stdoutからの出力とstderrがログファイルに書き込まれ、stderrがまだコンソール上にあるようにするソリューションが必要でした。そのため、tee経由でstderrの出力を複製する必要がありました。
これは私が見つけた解決策です:
command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
- 最初にstderrとstdoutを交換します
- 次に、ログファイルに標準出力を追加します
- stderrをパイプでティーし、ログファイルにも追加します
状況によっては、「配管」使用できる必要があります:
|&amp;
例:
echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt
または
TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log ; done |& sort -h
このbashベースのソリューションは、STDOUTとSTDERRを別々にパイプすることができます(&quot; sort -c&quot;のSTDERRから、またはSTDERRから&quot; sort -h&quot;へ)。
&quot;最も簡単な&quot; way(bash4のみ): ls * 2&gt;&amp;-1&gt;&amp;-
。
次の関数を使用して、stdout / stderrとログファイルの間で出力を切り替えるプロセスを自動化できます。スクリプト内での使用例:
#!/bin/bash
#set -x
# global vars
OUTPUTS_REDIRECTED="false"
LOGFILE=/dev/stdout
# "private" function used by redirect_outputs_to_logfile()
function save_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
exec 3>&1
exec 4>&2
trap restore_standard_outputs EXIT
}
# Params: $1 => logfile to write to
function redirect_outputs_to_logfile {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
save_standard_outputs
exec 1>>${LOGFILE%.log}.log
exec 2>&1
OUTPUTS_REDIRECTED="true"
}
# "private" function used by save_standard_outputs()
function restore_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot restore standard outputs because they have NOT been redirected"
exit 1;
fi
exec 1>&- #closes FD 1 (logfile)
exec 2>&- #closes FD 2 (logfile)
exec 2>&4 #restore stderr
exec 1>&3 #restore stdout
OUTPUTS_REDIRECTED="false"
}
tcshの場合、次のコマンドを使用する必要があります。
command >& file
command&amp;&gt;を使用する場合file
、&quot;無効なnullコマンド&quot;エラー。
@ fernando-fabreti
あなたがしたことに加えて、機能をわずかに変更し、&amp;-クロージングを削除しました。
function saveStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
exec 3>&1
exec 4>&2
trap restoreStandardOutputs EXIT
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
}
# Params: $1 => logfile to write to
function redirectOutputsToLogfile {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
saveStandardOutputs
exec 1>>${LOGFILE}
exec 2>&1
OUTPUTS_REDIRECTED="true"
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
}
function restoreStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
exec 1>&3 #restore stdout
exec 2>&4 #restore stderr
OUTPUTS_REDIRECTED="false"
fi
}
LOGFILE_NAME="tmp/one.log"
OUTPUTS_REDIRECTED="false"
echo "this goes to stdout"
redirectOutputsToLogfile $LOGFILE_NAME
echo "this goes to logfile"
echo "${LOGFILE_NAME}"
restoreStandardOutputs
echo "After restore this goes to stdout"
exec 2&gt;&amp; 1
のようなものを使用することを検討する状況では、可能であれば次のようなbash関数を使用してコードを書き換える方が読みやすいと思います:
function myfunc(){
[...]
}
myfunc &>mylog.log