Bashでstderrとstdoutをリダイレクトする


678

プロセスのstdoutとstderrの両方を1つのファイルにリダイレクトしたい。バッシュでどうすればいいですか?


2
これは驚くほど役立つ質問だと思います。頻繁に行う必要がないため、多くの人はこれを行う方法を知りません。これはBashの文書化された最良の動作ではありません。
Robert Wm Ruedisueli 2017

2
(通常のように)出力を確認し、ファイルにリダイレクトすると便利な場合があります。以下のマルコの答えを見てください。(問題を解決するのに十分であれば最初に受け入れられた回答を見れば簡単なので、ここでこれを言いますが、他の回答はしばしば有用な情報を提供します。)
jvriesem

回答:


761

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

yourcommand &>filename

(両方stdoutstderrファイル名にリダイレクトします)。


29
Bash Hackers Wikiによると、この構文は非推奨です。それは...ですか?
Salman von Abbas

20
wiki.bash-hackers.org/scripting/obsoleteによると、これはPOSIXの一部ではないという意味で時代遅れになっているようですが、bashのmanページでは、近い将来にbashから削除されることについて言及されていません。マニュアルページでは、「>&」よりも「&>」の設定を指定していますが、それ以外の点では同等です。
chepner 2012

13
&>はPOSIXにはなく、「ダッシュ」などの一般的なシェルではサポートされていないため、使用しないでください。
Sam Watkins

27
余分なヒント:あなたがスクリプトでこれを使用する場合は、必ずそれはで始まる作る#!/bin/bashのではなく#!/bin/shbashのを必要であるため、。
Tor Klingberg、2013年

8
または、上書きする代わりに追加する&>>。
Alexander Gonchiy

448
do_something 2>&1 | tee -a some_file

これにより、stderrがstdoutおよびstdoutにリダイレクトされ、stdoutに出力さsome_file ます。


16
AIX(ksh)では、ソリューションは機能します。受け入れられた答えdo_something &>filenameはそうではありません。+1。
2013年

11
@Daniel、しかしこの質問は特にbashに関するものです
John La Rooy

3
私が取得するAmbiguous output redirect.理由すべてのアイデアを?
Alexandre Holden Daly 2014年

1
エラーメッセージを太字の赤で出力するルビスクリプト(なんらかの方法で変更したくない)があります。次に、このルビスクリプトがbashスクリプト(変更可能)から呼び出されます。上記を使用すると、エラーメッセージが書式なしのプレーンテキストで出力されます。画面上のフォーマットを保持し、ファイルに出力(stdoutとstderrの両方)を取得する方法はありますか?
アトランティス2014年

8
(デフォルトで)これには、$?の終了ステータスでdo_somethingはなく、の終了ステータスを参照するという副作用があることに注意してくださいtee
Flimm、2015年

255

stderrstdoutにリダイレクトし、stdoutをファイルにリダイレクトできます。

some_command >file.log 2>&1 

見る http://tldp.org/LDP/abs/html/io-redirection.htmlを

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

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


47
これにより、stderrは元のstdoutにリダイレクトされます。stdoutが置かれているファイルにはリダイレクトされません。「> file.log」の後に「2>&1」を置くと、機能します。

1
some_command&> file.logに対するこのアプローチの利点は何ですか?
ubermonkey 2009年

6
ファイルに追加する場合は、次のように行う必要があります。echo "foo" 2>&1 1 >> bar.txt AFAIK&>を使用して追加する方法はありません
SlappyTheFish

9
ああ、申し訳ありません。エコー "foo" 1 >> bar.txt 2>&1
SlappyTheFish

11
2>&1がstderrをstdoutにリダイレクトするという解釈は間違っていると思います。stdoutが現時点で行っているのと同じ場所にstderrを送信すると言う方がより正確だと思います。したがって、最初のリダイレクトの後に 2>&1 を配置する必要があります。
jdg

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

元の投稿の著者に、

それはあなたが達成する必要があることに依存します。スクリプトから呼び出すコマンドの内外にリダイレクトする必要があるだけの場合、回答は既に提供されています。鉱山は現在のスクリプト内でリダイレクトすることについてであり、言及されたコードスニペットの後のすべてのコマンド/組み込み(フォークを含む)に影響します。


別のクールなソリューションは、std-err / outとロガーまたはログファイルの両方に一度にリダイレクトすることです。これには、「ストリーム」を2つに分割することが含まれます。この機能は、複数のファイル記述子(ファイル、ソケット、パイプなど)に一度に書き込み/追加できる「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を指しています。これらは実際に使用されます端子STDOUTおよびSTDERRます。
  • 1>>(...)は、STDOUTを括弧内のコマンドにリダイレクトします
  • parens(サブシェル)は、execのSTDOUT(パイプ)から「ティー」読み取りを実行し、別のパイプを介して「ロガー」コマンドにリダイレクトして、サブシェルのサブシェルに移動します。同時に、同じ入力を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

1
唯一の例外。最初に書いた例では、exec 1 <> $ LOG_FILEです。元のログファイルが常に書き出されます。実際のロギンのより良い方法は次のとおりです:exec 1 >> $ LOG_FILEこれにより、ログが常に追加されます。
Znik、2014

4
それは意図に依存しますが、それは本当です。私のアプローチは、常に一意のタイムスタンプ付きのログファイルを作成することです。もう1つは追加することです。どちらの方法も「ログ回転可能」です。解析の必要が少ない別のファイルを使用することをお
勧めし

1
2番目の解決策は有益ですが、すべてのクリーンアップコードには何がありますか?それは関連性がないように思われ、もしそうなら、そうでなければ良い例を混乱させるだけです。また、FD 1と2がロガーにリダイレクトされず、3と4がこのスクリプトを呼び出すすべてのユーザーがstdout == 1の一般的な仮定の下で1と2をさらに操作できるように、少し手直ししたいと思います。そしてstderr == 2ですが、私の簡単な実験ではそれがより複雑であることを示唆しています。
JFlo 2017年

1
クリーンアップコードの方が好きです。コアの例から少し注意をそらすかもしれませんが、それを取り除くと例が不完全になります。ネットはすでにエラー処理のない例でいっぱいです、または少なくとも100行のコードが安全に使用できるようにする必要があるという友好的なメモ。
Zoltan K.17年

1
クリーンアップコードについて詳しく説明したかったのです。エルゴをデーモン化するスクリプトの一部であり、ハングアップ信号の影響を受けなくなります。「tee」と「logger」は同じPPIDによって生成されたプロセスであり、メインのbashスクリプトからHUPトラップを継承します。したがって、メインプロセスが終了すると、それらはinit [1]に継承されます。彼らはゾンビになりません(defunc)。クリーンアップコードにより、メインスクリプトが停止した場合、すべてのバックグラウンドタスクが強制終了されます。また、バックグラウンドで作成されて実行されている可能性のある他のプロセスにも適用されます。
キザック

41
bash your_script.sh 1>file.log 2>&1

1>file.logSTDOUTをファイルに送信するようにシェルに指示しますfile.log2>&1 STDOUT(ファイルディスクリプタ1)にSTDERR(ファイルディスクリプタ2)をリダイレクトするように指示します。

注: liw.fiが指摘するように、順序は重要であり、2>&1 1>file.log機能しません。


21

奇妙なことに、これはうまくいきます:

yourcommand &> filename

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

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

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

yourcommand 1>> filename 2>&1

10
&>>BASH 4で動作するようです$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
user272735

15

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


説明:

「stdout」という単語をstdoutに出力し、「stderror」という単語をstderrorに出力する次のコードを考えてみます。

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

「&」演算子は、2がファイル記述子ではなく(stderrを指す)ファイル記述子であることをbashに通知することに注意してください。「&」stdoutを省略した場合、このコマンドはstdoutに出力し、「2」という名前のファイルを作成して次のように書き込みます。stderrorそこにます。

上記のコードを試してみると、リダイレクト演算子がどのように機能するかを正確に確認できます。たとえば、次の2行のコードに1,2リダイレクトされる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

これを本当に理解するには、ファイル記述子テーブルに関するこのWebページを読むことを強くお勧めします。あなたがその読みを終えたとすれば、私たちは先へ進むことができます。Bashは左から右に処理することに注意してください。したがって、Bashは>/dev/null最初に(これはと同じ1>/dev/null)を参照し、ファイル記述子1をstdoutではなく/ 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を指すように設定して、通常は標準出力に送信されるものをすべて削除します。したがって、残っているのは、サブシェル(括弧内のコード)のstdoutに送信されなかったもの、つまり「stderror」だけです。1が単なるstdoutへのポインタであっても、ポインタ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です。1つは出力を生成し、もう1つは生成しません。

最後に、次のすばらしいリソースをご覧ください。

リダイレクトのBashドキュメントファイル記述子テーブルの説明ポインタの概要


ファイル記述子(0、1、2)は、単にテーブルへのオフセットです。2>&1を使用すると、効果はスロットFD [2] = dup(1)になるため、FD [1]が指していた場所はどこでもFD [2]が指すようになります。FD [1]を/ dev / nullを指すように変更すると、FD [1]は変更されますが、FD [2]スロット(標準出力を指す)は変更されません。ファイル記述子を複製するために使用されるシステムコールであるため、dup()という用語を使用します。
PatS 2018

11
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からは機能しません;(


「/ dev / fd / 3 Permission denied」が原因で機能しないと思います。>&3に変更すると役立つ場合があります。
キザック2014

6

stdoutとstderrからの出力をログファイルに書き込み、stderrをコンソールに残すためのソリューションが必要でした。だから私はティーを介してstderr出力を複製する必要がありました。

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

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • 最初にstderrとstdoutを交換する
  • 次に、stdoutをログファイルに追加します
  • stderrをtにパイプし、ログファイルにも追加する

ところで、これは私にはうまくいきませんでした(ログファイルは空です)。|ティーは効果がありません。その代わりに、stackoverflow.com
questions / 692000 /

4

状況については、「パイピング」が必要なときに使用できます。

|&

例えば:

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から、またはSTDERRから「sort -h」へ)。



1

次の関数を使用して、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"

私はあなたの関数を使用すると、標準出力を復元しようとしたとき、私は、getエコー:書き込みエラー:不正なファイル番号リダイレクトが完璧に動作...復元はしていないようです
トム・シューマッハ

スクリプトを機能させるために、これらの行をコメント化する必要があり、順序を変更しました。#exec 1>&-#closes FD 1(logfile)#exec 2>&-#closes FD 2(logfile); exec 1>&3 #restore stdout exec 2>&4 #restore stderr
thom schumacher

申し訳ありません。CentOS 7、bash 4.2.46で実行してもエラーは発生しません。これらのコマンドを取得した参照に注釈を付けました。それは:Ref:logan.tw/posts/2016/02/20/open-and-close-files-in-bash
Fernando Fabreti

これらのコマンドをAIXで実行しているのは、おそらくそのためです。私が行った修正の投稿を追加しました。
トムシューマッハ2018

1

@ fernando-fabreti

あなたがしたことに加えて、私は関数を少し変更して&-クロージングを削除しましたが、それは私にとってはうまくいきました。

    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"

1

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

function myfunc(){
  [...]
}

myfunc &>mylog.log

0

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

command >& file

を使用command &> fileすると、「無効なnullコマンド」エラーが発生します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.