Archive

シェルスクリプトが '> $logfile 2>&1' だらけにならなくて済んだ話

※ 2014-04-26
追記並びに一部コマンド部分の修正を行いました。( > => >> に変更 )
個人用のチラシの裏のつもりが予想以上に反響いただいていたようで非常にびっくりしております。
ちょっとしたバッチ処理的なものはさくっとシェルスクリプトでやっています。
で、ログをとっておくべくリダイレクトを噛ますわけですが、
スマートに書く方法を調べたのでメモ。
本当に参考になりました。ありがとうございます。

今までは

こんなことやってたわけです。

#!/bin/bash
LOGFILE=/tmp/script-log

command1 >> $LOGFILE 2>&1
command2 >> $LOGFILE 2>&1
...      >> $LOGFILE 2>&1
...      >> $LOGFILE 2>&1
...      >> $LOGFILE 2>&1
...      >> $LOGFILE 2>&1

まあ1,2行くらいならいいのですが、これが5行超えてくるともう編集するのも読むのも嫌になってきます。 この辺調べてみると、 exec コマンド によるプロセス置換 で で出力を変更してあげると良いようです。

2014-04-26 追記
下の例で出しています awk によるフィルタリングはプロセス置換にあたるようですが、 単純なログのリダイレクト部分については bash の組み込みコマンド exec によるリダイレクト指定の際の挙動のようで プロセス置換とは別物のようです。勉強不足ですみません。。

execで解決

#!/bin/bash
LOGFILE=/tmp/script-log

exec 1> >(cat >> $LOGFILE)
exec 2> >(cat >> $LOGFILE)

command1
command2
...
...
...
こんな感じで書けます。
さらに awk も使って以下のようにすると、ログの各行の先頭に
[YYYY-mm-dd HH:MM:SS] のようにタイムスタンプも付けられます。
#!/bin/bash
LOGFILE=/tmp/script-log

exec 1> >(awk '{print strftime("[%Y-%m-%d %H:%M:%S] "),$0 } { fflush() } ' >> $LOGFILE)
exec 2> >(awk '{print strftime("[%Y-%m-%d %H:%M:%S] "),$0 } { fflush() } ' >> $LOGFILE)

command1
command2
...
...
...
tee コマンドを使えば出力を保ちながらロギングとかもできそうです。
ただしこの出力の変更は bash の機能らしく、シバンを #!/bin/sh にすると
Syntax error: redirection unexpected が返って来てうまく処理が動かないので
#!/bin/bash と書く必要が有ります。

最後に

ちょこちょこ処理が増えてきてしまうと すぐに >> $logfile 2>&1 まみれになってしまっていたシェルスクリプトがようやくスッキリ書けるようになりました。 exec によるプロセス置換は他にも応用が効きそうなのでもっと調べると色々捗りそうです。

2014-04-26 追記

コメントにて更に素敵な方法を紹介していただけました。 exec 利用でもさらにシンプルな記載ができ、 また {} を使った記載方法もあるようです。

  • もっとシンプルな exec

    awk などと組み合わせてフィルタリングするのは厳しいですが、単純に出力先を指定するにはこちらがよさそうです。

    #!/bin/bash
    
    LOGFILE=/tmp/script-log
    exec >>"$LOGFILE"
    exec 2>&1
    
    command1
    command2
    ...
    ...
    ...
    
  • {} を使った方法

    この方法はコメントでご指摘いただくまで全く知りませんでした。。 こちらの記載方法のほうが汎用性が高そうで使いやすそうですね。

    #!/bin/bash
    
    LOGFILE=/tmp/script-log
    
    {
        command1
        command2
        ...
        ...
        ...
    } >> "$LOGFILE" 2>&1
    

状況に応じて使い分けていければシェルスクリプトがもっと捗りそうです。