スクリプト自体の中でシェルスクリプト全体の出力をリダイレクトするにはどうすればよいですか?


205

Bourneシェルスクリプトのすべての出力をどこかにリダイレクトすることは可能ですが、スクリプト自体の中にシェルコマンドを使用しますか?

単一のコマンドの出力をリダイレクトするのは簡単ですが、次のようなものが必要です。

#!/bin/sh
if [ ! -t 0 ]; then
    # redirect all of my output to a file here
fi

# rest of script...

意味:スクリプトが非対話的に実行される場合(cronなど)、すべての出力をファイルに保存します。シェルからインタラクティブに実行する場合は、出力を通常どおりstdoutに送信します。

FreeBSDの定期的なユーティリティによって通常実行されるスクリプトに対してこれを実行したいと思います。これは毎日の実行の一部であり、通常は毎日メールで確認する必要がないため、送信しません。ただし、この1つの特定のスクリプト内の何かが失敗した場合、それは私にとって重要であり、毎日のジョブのこの一部の出力をキャプチャして電子メールで送信できるようにしたいと考えています。

更新:ジョシュアの答えは正解ですが、スクリプト全体のstdoutとstderrを保存して復元したかったので、次のように行いました。

# save stdout and stderr to file descriptors 3 and 4, then redirect them to "foo"
exec 3>&1 4>&2 >foo 2>&1

# ...

# restore stdout and stderr
exec 1>&3 2>&4

2
$ TERMのテストは、インタラクティブモードのテストに最適な方法ではありません。代わりに、stdinがttyであるかどうかをテストします(test -t 0)。
Chris Jester-Young、

2
つまり、[[!-t 0]; 次に、exec> somefile 2>&1; fi
Chris Jester-Young

1
すべての利点については、こちらを参照してください。http//tldp.org/LDP/abs/html/io-redirection.html基本的に、ジョシュアが言ったこと。exec> fileはstdoutを特定のファイルにリダイレクトし、exec <fileはstdinをファイルに置き換えます。これは通常と同じですが、execを使用します(詳細はman execを参照)。
ロキ

あなたの更新のセクションでは、あなたはまた、近くのFD 3と4は、とても好きなはずです exec 1>&3 2>&4 3>&- 4>&-
Gurjeetシン

回答:


173

更新された質問に対処します。

#...part of script without redirection...

{
    #...part of script with redirection...
} > file1 2>file2 # ...and others as appropriate...

#...residue of script without redirection...

中括弧「{...}」は、I / Oリダイレクトの単位を提供します。中かっこは、コマンドが出現する可能性のある場所に表示する必要があります-簡単に言うと、行の先頭またはセミコロンの後です。(はい、それはより正確にすることができます。もしあなたが動揺したいなら、私に知らせてください。

元のstdoutとstderrを、示したリダイレクトで保持できることは正しいですが、上記のようにリダイレクトされたコードのスコープを設定すると、後でスクリプトを保守する必要がある人にとって、通常何が起こっているのかを理解するのが簡単になります。

Bashマニュアルの関連セクションは、グループ化コマンドI / Oリダイレクトです。POSIXシェル仕様の関連セクションは、複合コマンドI / Oリダイレクトです。Bashには追加の表記法がありますが、それ以外はPOSIXシェル仕様に似ています。


3
これは、元の記述子を保存して後で復元するよりもはるかに明確です。
スティーブマドセン

23
これが実際に何をしているのかを理解するためにググリングをする必要があったので、共有したかった 中括弧は「コードのブロック」になり、実質的には無名関数が作成されます。次に、コードブロック内のすべての出力をリダイレクトできます(そのリンクの例3-2を参照)。また、中括弧サブシェルを起動しませんが、括弧を使用サブシェルで同様のI / Oリダイレクトを実行 できます。
クリス

3
このソリューションは他のソリューションよりも優れています。I / Oリダイレクトについて最も基本的な知識しか持っていない人でも、何が起こっているのかを理解できます。さらに、それはより冗長です。そして、Pythonerとして、私は冗長が大好きです。
John Red

より良いです>>。一部の人々は習慣を持ってい>ます。追加は常に安全であり、上書きするよりも推奨されます。誰かが、標準のコピーコマンドを使用して一部のデータを同じ宛先にエクスポートするアプリケーションを作成しました。
neverMind9 2018

そのアプリはccc.bmw71(.pro)(3Cバッテリーモニターウィジェット、以前は「バッテリーモニターウィジェット」)で、常にバッテリーの履歴を/sdcard/bmw_history.txtにエクスポートします。すでに存在する場合は、何を推測しますか、上書きしてください!これにより、バッテリーの履歴が誤って失われました。日数の制限を30から非常に大きい数に設定したため、無効にして30に戻しました。既存のバックアップをインポートする前に、現在のバッテリー履歴をエクスポートしたいと思いました。それが起こった。
neverMind9 2018

175

通常、これらの1つをスクリプトの上部またはその近くに配置します。コマンドラインを解析するスクリプトは、解析後にリダイレクトを行います。

stdoutをファイルに送信する

exec > file

stderrあり

exec > file                                                                      
exec 2>&1

stdoutとstderrの両方をファイルに追加する

exec >> file
exec 2>&1

ジョナサン・レフラーは 彼のコメントで述べました

exec2つの別々のジョブがあります。1つ目は、現在実行中のシェル(スクリプト)を新しいプログラムに置き換えることです。もう1つは、現在のシェルでI / Oリダイレクトを変更することです。これは、に引数がないことで区別されexecます。


7
また、最後に2>&1を追加すると、stderrもキャッチされます。:-)
Chris Jester-Young

7
これらはどこに置きますか?スクリプトの一番上に?
コラン14

4
このソリューションでは、スクリプトが終了するリダイレクトもリセットする必要があります。ジョナサン・レフラーによる次の答えは、この意味でより「失敗の証拠」です。
チュイム

6
@JohnRed:exec2つの別々のジョブがあります。1つは、同じプロセスを使用して、現在のスクリプトを別のコマンドで置き換えることです。他のコマンドをへの引数として指定します(その際、execI / Oリダイレクトを微調整できます)。もう1つのジョブは、現在のシェルスクリプトのI / Oリダイレクトを置き換えずに変更することです。この表記は、コマンドを引数として持たないことで区別されexecます。この回答の表記は「I / Oのみ」のバリアントです。リダイレクトを変更するだけで、実行中のスクリプトを置き換えません。(setコマンドは同様に多目的です。)
ジョナサンレフラー

18
exec > >(tee -a "logs/logdata.log") 2>&1ログを画面に出力し、ファイルに書き込みます
shriyog

32

スクリプト全体を次のような関数にすることができます。

main_function() {
  do_things_here
}

次に、スクリプトの最後に次のようにします。

if [ -z $TERM ]; then
  # if not run via terminal, log everything into a log file
  main_function 2>&1 >> /var/log/my_uber_script.log
else
  # run via terminal, only output to screen
  main_function
fi

別の方法として、実行ごとにすべてをログファイルに記録し、次のようにしてstdoutに出力することもできます。

# log everything, but also output to stdout
main_function 2>&1 | tee -a /var/log/my_uber_script.log

もしかしてmain_function >> /var/log/my_uber_script.log 2>&1
フェリペアルバレス

そのようなパイプでmain_functionを使用するのが好きです。ただし、この場合、スクリプトは元の戻り値を返しません。bashの場合は、終了してから「exit $ {PIPESTATUS [0]}」を使用する必要があります。
rudimeier、2014

7

元のstdoutとstderrを保存するには、以下を使用できます。

exec [fd number]<&1 
exec [fd number]<&2

たとえば、次のコードは「walla1」と「walla2」をログファイルa.txtに出力し()、「walla3」をstdoutに出力し、「walla4」をstderrに出力します。

#!/bin/bash

exec 5<&1
exec 6<&2

exec 1> ~/a.txt 2>&1

echo "walla1"
echo "walla2" >&2
echo "walla3" >&5
echo "walla4" >&6

1
通常、出力には入力リダイレクト表記ではなく、出力リダイレクト表記を使用exec 5>&1してexec 6>&2、and を使用することをお勧めします。ターミナルからスクリプトを実行すると、標準入力も書き込み可能であり、標準出力と標準エラーの両方が、歴史的癖の美徳(またはそれが「悪」か)によって読み取ることができるため、問題はありません。読み取りと書き込み、および同じオープンファイル記述が、3つの標準I / Oファイル記述子すべてに使用されます。
ジョナサンレフラー

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