コマンドのグループをパイプする/リダイレクトする


8

現在、次の設定を使用して複数のコマンドの出力をリダイレクトしています。

echo "Some normal commands"
(
echo "Error: something happened"
echo "Warning: this incident will be logged"
) >> logfile
echo "More normal commands"

これはかなり便利で、パイプでも機能します。

これはこれを行う最良の方法ですか?考慮すべき代替案はありますか?


それが私のやり方です。このアプローチで特定の問題が発生していますか?
Bratchley 2014年

@JoelDavis Nope、それを行うためのより良い方法があるかどうか疑問に思っています。私が受け取った回答から、あるようです!:)
wchargin 2014年

回答:


15

代わりに、括弧の代わりに中括弧を使用することもできます。この変更により、サブシェルではなく現在のシェルでコマンドが実行されます

echo "Some normal commands"
{
echo "Error: something happened"
echo "Warning: this incident will be logged"
} >> logfile
echo "More normal commands"

ref:https : //www.gnu.org/software/bash/manual/bashref.html#Command-Grouping

これは、グループ内の変数を変更する場合に特に関係があります。

$ x=5; ( x=10; echo inside: $x; ); echo outside: $x
inside: 10
outside: 5

$ x=5; { x=10; echo inside: $x; }; echo outside: $x
inside: 10
outside: 10

すばらしい、ありがとう!これは、私のインデントでもうまく機能します。(Vimのインデント。{ }ただし、そうではありません( )。)
wchargin '27年

2
どちらの方法でも、コマンドグループを1行に折りたたむことができます。例:(echo msg1; echo msg2)–ただし、中かっこを使用する場合は{ echo msg1; echo msg2;}、の後にスペース{を入れ、セミコロン(;)またはアンパサンド(&)の前に置く必要があり}ます。
G-Manは 'Reinstate Monica'を

4

グレンの答えは良いものです-の区別( ... ){ ... }が重要です。

あなたの質問にあるようなエラー出力によく使用する1つの戦略は、teeコマンドです。あなたはこのようなことをすることができます:

echo "Normal output"
{
  printf "[%s] %s\n" "$(date '+%Y-%m-%d %T')" "Warning text"
  printf "[%s] %s\n" "$(date '+%Y-%m-%d %T')" "This event is logged."
} | tee -a $logfile >&2
echo "More normal output"

teeコマンドは、2つの場所に出力を送信します。-aオプションは、指定されたファイルに出力を「追加」し、コマンドは入力をstdoutに渡します。>&2ラインリダイレクトの終了時にtee(すなわち、cronジョブに)異なる方法で処理することができる標準エラーへのSTDOUT、。

シェルスクリプトでよく使用するもう1つのヒントは、スクリプトが端末で実行されているか、-vオプションが提供されているかに基づいて、デバッグまたは詳細出力の動作を変更することです。例えば:

#!/bin/sh

# Set defaults
if [ -t 0 ]; then
  Verbose=true; vflag="-v"
else
  Verbose=false; vflag=""
fi
Debug=false; AskYN=true; Doit=true

# Detect options (altering defaults)
while getopts vdqbn opt; do
  case "$opt" in
    v)  Verbose=true; vflag="-v" ;;             # Verbose mode
    d)  Debug=true; Verbose=true; vflag="-v" ;; # Very Verbose
    q)  Verbose=false; vflag="" ;;              # quiet mode (non-verbose)
    b)  AskYN=false ;;                          # batch mode
    n)  Doit=false ;;                           # test mode
    *)  usage; exit 1 ;;
  esac
done

# Shift our options for further processing
shift $(($OPTIND - 1))

$Verbose && echo "INFO: Verbose output is turned on." >&2
$Debug && echo "INFO: In fact, expect to be overrun." >&2

# Do your thing here
if $AskYN; then
  read -p "Continue? " choice
  case "$choice" in
    Y|y) $Doit && somecommand ;;
    *) echo "Done." ;;
  esac
fi

スクリプトは、このような一般的なものから始め、VerboseとDebugの出力がスクリプト全体に散らばっています。それはそれを行うための1つの方法にすぎません-多くの人がいて、さまざまな人々がすべてこのことを処理するための独自の方法を持っています(特に、彼らがしばらくの間いた場合)。:)

もう1つのオプションは、出力を「ハンドラー」で処理することです。これは、よりインテリジェントな処理を行うシェル関数です。例えば:

#!/bin/bash

logme() {
  case "${1^^}" in
    [IN]*)  level=notice ;;
    W*)     level=warning ;;
    A*)     level=alert ;;
    E*)     level=emerg ;;
    *)      level=notice ;;
  esac
  if [[ "$#" -eq 1 ]]; then
    # Strip off unnecessary prefixes like "INFO:"
    string="${1#+([A-Z])?(:) }"
  else
    shift
    string="$@"
  fi
  logger -p "${facility}.${level}" -t "$(hostname -s)" "$string"
}

echo "Normal output"
logme INFO "Here we go..."
somecommand | logme
echo "Additional normal output"

(これ${var^^}はbashのみであることに注意してください。)

これにより、システムのsyslog関数を使用できるシェル関数が作成されます(loggerコマンド) to send things to system logs. Thelogme() `関数を使用すると、ログデータの1行を生成するオプション、またはstdinで処理される複数行の入力を使用できます。魅力的だ。

これは一例であり、理解し、必要なことを正確に行うことがわかっている場合を除いて、そのままコピーしないでください。より良いアイデアは、ここで概念を取り、独自のスクリプトでそれらを自分で実装することです。


詳しい回答ありがとうございます!私は実際にを使用していますがfunction log () { cat >> $logfile }、これは基本的にはのより単純なバージョンですlogme
wchargin 2014年

また、興味があるかもしれませんts(1)。使用法:{ printf '%s\n' "Warning text"; printf '%s\n' "This event will be logged"; } | ts '[%Y-%m-%d %T]' | tee -a "$logfile" >&2
wchargin 16

@wchargin- ts(1)私が使用しているシステム(FreeBSD、OSX、古いUbuntuボックス)にはインストールされていません。それを提供するものを教えていただけますか?
ghoti

これはmoreutilsの一部であり、sponge(1)(stdinが閉じられた後にのみファイルに書き込むため、リダイレクトによるsomething < foo | sponge fooクローバなしで実行できるfoo)やvipe(1)(テキストエディターをパイプに挿入する)などの便利なツールも含まれています。
wchargin 2016

1

これを行うより適切な方法は、{ command; }ではなくを使用すること(command)です。その理由は、コマンドが()サブシェルでグループ化されると、それらのコマンドを実行するために開かれるため、そのブロック中に初期化された変数は、スクリプトの他のセクションで使用できないためです。

代わりに{}、コマンドのグループ化に使用する場合、コマンドは同じシェル内で実行されるため、スクリプトの他のセクションで変数を使用できます。

echo "Some normal commands"

{
    var=1
    echo "Error: something happened"
    echo "Warning: this incident will be logged"
} >> logfile

echo "The value of var is: $var"
echo "More normal commands"

ここで、このセクションが実行されると、$var変数はその値を保持しますが、他の場合とは異なります。


また、1行で使用する場合は、間にスペースを入れ、コマンドをセミコロンで終了することを忘れないでください。例:{ command; }
CMCDragonkai 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.