web-dev-qa-db-ja.com

stdoutまたはbashのファイルにエコーするエレガントなソリューション

一部の結果を生成しているbashアプリケーションがあり、その結果をstdoutまたはユーザーが選択したファイルにエコーしたいと思います。画面に表示される他のインタラクティブメッセージもエコーするため、結果をファイルにエコーしたい場合にユーザーが>リダイレクトを明示的に使用するように要求することはオプションではありません(*)。ファイル。

今私は解決策を持っていますが、それは醜いです。

if [ -z $outfile ]
then
    echo "$outbuf"    # Write output buffer to the screen (stdout)
else
    echo "$outbuf" > $outfile  # Write output buffer to file
fi

変数$outfilestdout&1、およびおそらく他の何かと等しくなるようにしようとしましたが、実際にはstdoutではなく、その名前のファイルに書き込むだけでした。よりエレガントな解決策はありますか?

(*)その目的のためにstderrをだまして使用することもできますが、それも非常に醜いと思いませんか?

3
Bregalad

まず、 任意のデータを出力するためにechoを避ける を行う必要があります。

Linuxベース以外のシステムでは、以下を使用できます。

logfile=/dev/stdout

Linuxの場合、一部のタイプのstdoutで機能しますが、stdoutがソケットである場合、またはそれより悪い場合、stdoutが通常のファイルの場合、stdoutがファイル内の現在の位置に書き込む代わりにそのファイルを切り捨てます。

それ以外は、Bourneのようなシェルでは、evalを使用することはできますが、条件付きリダイレクトを行う方法はありません。

eval 'printf "%s\n" "$buf" '${logfile:+'> "$logfile"'}

変数の代わりに、専用のファイル記述子を使用できます:

exec 3>&1
[ -z "$logfile" ] || exec 3> "$logfile"

 printf '%s\n' "$buf" >&3

(小さな)欠点は、kshを除いて、fd 3がスクリプトで実行されるすべてのコマンドにリークされることです。 zshを使用すると、sysopen -wu 3 -o cloexec -- "$logfile" || exit 代わりに exec 3> "$logfile"ですが、bashには同等のものはありません。

別の一般的なイディオムは、次のような関数を使用することです。

log() {
  if [ -n "$logfile" ]; then
    printf '%s\n' "$@" >> "$logfile"
  else
    printf '%s\n' "$@"
  fi
}

log "$buf"
5
  1. outfile"/dev/stdout"に設定します。
  2. ファイル名を選択してoutfileを上書きするか、デフォルト値のままにします。
  3. printf '%s\n' "$outbuf" >"$outfile"

printfがechoよりも優れているのはなぜですか のため、printfを使用しました。

このソリューションの警告については、 StéphaneChazelasの回答 を参照してください。

5
Kusalananda

これを試して

#!/bin/bash
TOSCREEN="1" # empty OR 0 to log to file
if [[ ! -z "$TOSCREEN" && $TOSCREEN == "1" ]]; then
    echo "$outbuf"    # Write output buffer to the screen (stdout)
else
    if [[ ! -f $outfile ]]; then
      #echo -e "Making file "
      touch "$outfile"
    fi  
    echo "$outbuf" >> $outfile  # Write output buffer to file
fi
0