web-dev-qa-db-ja.com

シェルスクリプト内の画面とファイルの両方にテキストを出力する方法

現在、次のようなログファイルにメッセージを記録するシェルスクリプトがあります。

log_file="/some/dir/log_file.log"
echo "some text" >> $log_file
do_some_command
echo "more text" >> $log_file
do_other_command

このスクリプトを実行しても画面に出力されず、PuTTY経由でサーバーに接続しているため、別の接続を開いて「tail -f log_file_path.log」を実行する必要があります。実行を終了できないためです。スクリプトと私はリアルタイムで出力を見たいです。

明らかに、私が欲しいのは、テキストメッセージが画面上とファイルに出力されることですが、2行ではなく1行で実行したいのですが、そのうちの1つはファイルにリダイレクトされません。

これを達成する方法は?

49
jurchiks

これは機能します:

command | tee -a "$log_file"

teeは、入力をファイルに保存します(-a(上書きではなく追加する)、入力も標準出力にコピーします。

73
l0b0

ヒアドキュメントとを使用できます。効率的でPOSIXに適した一般的なコレクターモデルのソースにします。

. 8<<-\IOHERE /proc/self/fd/8

command
… 
fn() { declaration ; } <<WHATEVER
# though a nested heredoc might be finicky
# about stdin depending on Shell
WHATEVER
cat -u ./stdout | ./works.as >> expect.ed
IOHERE

ヒアドキュメントを開くと、IOHERE入力トークンでシェルに信号を送り、リミッタートークンのもう一方の端に到達するまで、入力を指定したファイル記述子にリダイレクトする必要があります。見回しましたが、ヒアドキュメントオペレーターと組み合わせて上記で示したように、リダイレクトfd番号の使用例は多くありませんが、その使用法はPOSIX基本シェルコマンドガイドラインで明確に指定されています。ほとんどの人はstdinを指定して撮影するだけですが、この方法でソーススクリプトを作成すると、stdinが無料になり、構成アプリがブロックされたI/Oパスについて文句を言うことがなくなります。

ヒアドキュメントのコンテンツは、指定したファイル記述子にストリーミングされます。次に、シェル記述子として解釈され、によって実行されます。組み込みですが、の特定のパスを指定する必要があります。 。/proc/selfパスで問題が発生する場合は、/ dev/fd/nまたは/ proc/$$を試してください。ちなみに、この同じ方法はパイプに対して機能します。

cat ./*.sh | . /dev/stdin

おそらく見た目と同じくらい賢くないでしょう。もちろん、shでも同じことができますが、。の目的は、現在のシェル環境で実行することです。これはおそらく望んでいることであり、シェルによっては、ヒアドキュメントよりもはるかに動作する可能性が高くなります。標準の匿名パイプを使用します。

とにかく、お気づきかもしれませんが、私はまだあなたの質問に答えていません。しかし、それについて考えると、ヒアドキュメントがすべてのコードを。の中にストリーミングするのと同じ方法で、単一の単純なアウトポイントも提供します。

. 5<<EOIN /dev/fd/5 |\ 
    tee -a ./log.file | cat -u >$(tty)
script
… 
more script
EOIN

したがって、ヒアドキュメントで実行されたコードのすべての端末stdoutは、からパイプアウトされます。もちろん、1本のパイプから簡単にT字型にすることができます。現在のstdoutの方向が不明なため、バッファリングされていないcat呼び出しを含めましたが、おそらく冗長であり(ほとんどの場合それはとにかく書かれているとおりです)、パイプラインはおそらくtで終了できます。

2番目の例で欠落しているバックスラッシュの引用に疑問を投げかけることもできます。この部分は、ジャンプインする前に理解することが重要であり、それをどのように使用できるかについていくつかのアイデアを提供するかもしれません。引用されたヒアドキュメントリミッター(これまではIOHEREとEOINを使用しましたが、最初にバックスラッシュで引用しましたが、「単一」または「二重」引用符は同じ目的に役立ちます)は、シェルでパラメーター拡張を実行できないようにします。内容ですが、引用符で囲まれていないリミッターを使用すると、その内容を拡張できます。ヒアドキュメントがである場合のこれの結果。ソースは劇的です:

. 3<<HD ${fdpath}/3
: \${vars=$(printf '${var%s=$((%s*2))},' `seq 1 100`)} 
HD
echo $vars
> 4,8,12… 
echo $var1 $var51
> 4 104

ヒアドキュメントのリミッターを引用しなかったので、シェルは内容を読み込んで、結果のファイル記述子をに提供する前に内容を拡張しました。実行します。これにより、基本的にコマンドが2回解析されることになります-とにかく拡張可能なコマンドです。 $ varsパラメータ展開をバックスラッシュで引用しているため、シェルは最初のパスでその宣言を無視し、バックスラッシュのみを取り除いたため、printfの展開されたコンテンツ全体がnullで評価されました。 2番目のパスでスクリプトを入手しました。

この機能は、引用がヘラドキュメントでevalよりもはるかに簡単に処理でき、同等に危険である場合でも、基本的には、危険なeval Shellビルトインが提供できる機能です。注意深く計画しない限り、「EOF」リミッターを習慣の問題として引用するのがおそらく最善です。ただ言って。

編集:ええと、私はこれを振り返って、それが少し長すぎると思います。複数の出力を1つのパイプに連結する必要がある場合、最も簡単な方法は次のものを使用することです。

{ or ( command ) list ; } | tee -a ea.sy >>pea.sy

中括弧は現在のシェルでコンテンツを実行しようとしますが、括弧は自動的にサブアウトします。それでも、誰でもあなたにそれを伝えることができ、少なくとも私の意見では、ヒアドキュメントの解決策は、シェルが実際にどのように機能するかを理解したい場合は特に、はるかに価値のある情報です。

楽しんで!

8
mikeserv

インストールスクリプトを変更してインストールログを書き込むときに、この回答を見つけました。

私のスクリプトはすでに次のようなエコーステートメントでいっぱいです。

echo "Found OLD run script $oldrunscriptname"
echo "Please run OLD tmunsetup script first!"

そして、私はそれを実行するTステートメント(またはTを使用して既存のものを呼び出す別のスクリプト)を望んでいないので、私はこれを書きました。

#!/bin/bash
# A Shell subroutine to echo to screen and a log file

LOGFILE="junklog"

echolog()
(
echo $1
echo $1 >> $LOGFILE
)


echo "Going"
echolog "tojunk"

# eof

したがって、元のスクリプトで、「echo」をログファイルの出力が必要な「echolog」に変更するだけです。

3
AndruWitta