web-dev-qa-db-ja.com

Bashで標準エラーと標準出力をリダイレクトする

プロセスの標準出力と標準エラー出力の両方を単一のファイルにリダイレクトしたい。 Bashではどうしたらいいですか?

607
flybywire

見てください ここ 。する必要があります:

yourcommand &>filename

stdoutstderrの両方をfilenameにリダイレクトします)。

703
dirkgently
do_something 2>&1 | tee -a some_file

これは、stderrをstdoutに、stdoutをsome_file にリダイレクトし、 - それをstdoutに出力します。

420
Marko

stderr stdout 、および stdout をファイルにリダイレクトできます。

some_command >file.log 2>&1 

http://tldp.org/LDP/abs/html/io-redirection.html を参照してください。

この形式は、bashでしか機能しない最も一般的な&>形式よりも優先されます。 Bourne Shellでは、コマンドをバックグラウンドで実行していると解釈できます。また、フォーマットは読みやすく2(STDERR)で1(STDOUT)にリダイレクトされます。

編集:コメントで指摘されているように順序を変更

222
f3lix
# 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に書き込みます。デーモン化に役立ちます。

元の投稿の作者に、

それはあなたが達成する必要があるものによって異なります。あなたがあなたのスクリプトからあなたが呼び出すコマンドに出入りすることだけをリダイレクトする必要があるならば、答えはすでに与えられています。私のの目的は、currentスクリプト内でリダイレクトすることです。これは、上記のコードスニペットの後のすべてのコマンド/組み込みコマンド(フォークを含む)に影響します。


もう1つの優れた解決策は、「ストリーム」を2つに分割することを含む、std-err/outとloggerまたはlogファイルの両方へのリダイレクトです。この機能は、一度に複数のファイル記述子(ファイル、ソケット、パイプなど)に書き込み/追加できる 'tee'コマンドによって提供されます。tee FILE1 FILE2 ...>(cmd1)>(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を指しています。これらはreal端末のSTDOUTおよびSTDERRとして使用されます。
  • 1>>(...)は、STDOUTをコマンドペアにリダイレクトします。
  • parens(sub-Shell)はexecのSTDOUT(pipe)から 'tee'の読み込みを実行し、別のパイプを経由して 'logger'コマンドにsub-shellへリダイレクトします。同時に同じ入力を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
180
quizac
bash your_script.sh 1>file.log 2>&1

1>file.logはシェルにSTDOUTをファイルfile.logに送信するように指示し、2>&1はSTDERR(ファイル記述子2)をSTDOUT(ファイル記述子1)にリダイレクトするように指示します。

注: liw.fiが指摘しているように、2>&1 1>file.logは機能しません。

36
Guðmundur H

不思議なことに、これは動作します:

yourcommand &> filename

しかし、これは構文エラーになります。

yourcommand &>> filename
syntax error near unexpected token `>'

あなたが使用する必要があります:

yourcommand 1>> filename 2>&1
21
Mark

短い答え:Command >filename 2>&1またはCommand &>filename


説明:

次のコードで、 "stdout"という単語をstdoutに、 "stderror"という単語をstderrorに出力します。

$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror

'&'演算子は、2がファイル記述子(標準エラー出力を指す)であり、ファイル名ではないことをbashに伝えます。 '&'を省略した場合、このコマンドはstdoutをstdoutに出力し、 "2"という名前のファイルを作成してそこにstderrorを書き込みます。

上記のコードを試してみることで、リダイレクト演算子がどのように機能するのかを自分で確認できます。たとえば、次の2行のコードでは、2つの記述子1,2のどちらを/dev/nullにリダイレクトするかを変更することによって、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は最初に>/dev/nullを見(これは1>/dev/nullと同じです)、ファイル記述子1を標準出力の代わりに/ dev/nullを指すように設定します。これを行った後、Bashは右に移動して2>&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>&1を見て、ファイルディスクリプタ2がファイルディスクリプタ1と同じ場所、すなわちstdoutを指すように設定することです。次にファイル記述子1(>/dev/null = 1>/dev/nullを指す)を>/dev/nullを指すように設定し、通常標準出力に送信されるすべてのものを削除します。したがって、残っているのは、サブシェル内の標準出力(括弧内のコード)に送信されなかったもの、つまり "stderror"だけでした。興味深い点は、1は標準出力への単なるポインタですが、2>&1を介してポインタ2を1にリダイレクトしても、ポインタ2 - > 1 - > stdoutのチェーンが形成されないことです。もしそうであれば、1を/ dev/nullにリダイレクトした結果、コード2>&1 >/dev/nullはポインタチェーン2 - > 1 - >/dev/nullを与えることになり、その結果コードは何も生成しないでしょう。 。


最後に、これを実行するためのより簡単な方法があることに注意してください。

セクション3.6.4 ここ から、演算子&>を使用してstdoutとstderrの両方をリダイレクトできることがわかります。したがって、任意のコマンドのstderrとstdoutの両方の出力を(出力を削除する)\dev\nullにリダイレクトするには、単に$ command &> /dev/nullを入力するか、私の例では、

$ (echo "stdout"; echo "stderror" >&2) &>/dev/null

主な要点

  • ファイルディスクリプタはポインタのように動作します(ファイルディスクリプタはファイルポインタと同じではありません)。
  • ファイル記述子 "a"をファイル "f"を指すファイル記述子 "b"にリダイレクトすると、ファイル記述子 "a"はファイル記述子b - ファイル "f"と同じ場所を指すようになります。それはポインタの連鎖a - > b - > fを形成しない
  • 上記のため、順番は重要です、2>&1 >/dev/nullは!= >/dev/null 2>&1です。一方は出力を生成し、もう一方は生成しません。

最後に、これらの素晴らしいリソースを見てください。

リダイレクトに関するBashのドキュメントファイルディスクリプタテーブルの説明ポインタの紹介

12
Evan Rosica
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" )

それは関連しています:syslogへのstdOutとstderrの書き方。

それはほとんど動作しますが、xintedからは動作しません。

10
log-control

私は標準出力と標準エラー出力をログファイルに書き出し、標準エラー出力をコンソールに表示させる解決策を望んでいました。だから私はtee経由で標準エラー出力を複製する必要がありました。

これは私が見つけた解決策です:

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • 最初のスワップ標準エラー出力と標準出力
  • 次に、ログファイルに標準出力を追加します
  • ティーにパイプ標準エラー出力し、それをログファイルにも追加する
6
bebbo

状況によっては、「配管」が必要なときに使用できます。

|&

例えば:

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を別々に( "sort -c"のSTDERRから、または "sort -h"へのSTDERRから)パイプ処理できます。

4

最も簡単な方法(bash4のみ):ls * 2>&- 1>&-

1
reim

以下の関数は、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"
    }

スクリプト内の使用例

echo "this goes to stdout"
redirect_outputs_to_logfile /tmp/one.log
echo "this goes to logfile"
restore_standard_outputs 
echo "this goes to stdout"
1

exec 2>&1のようなものを使うことを検討している状況では、可能であればこのようなbash関数を使ってコードを書き直すほうが読みやすいと思います。

function myfunc(){
  [...]
}

myfunc &>mylog.log
0
user2761220

Tcshの場合は、次のコマンドを使用する必要があります。

command >& file

command &> fileを使用すると、 "Invalid null command"エラーが発生します。

0
einstein6

@フェルナンドファブレティ

あなたがしたことに加えて、私は機能をわずかに変えて、& - クロージングを取り除きました、そして、それは私のために働きました。

    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"
0
thom schumacher