最近業務でシェルスクリプトを書く機会があり、これまでbashについて勉強していた知識が生きたことがいくつかあった。
それらのシェルスクリプト小技を軽いシリーズにして記録していく。
今回はシェルスクリプトの実行ログを/var/log/syslog
(/var/log/messages
)と同じようなフォーマットで保存する方法を書く。
本気でやろうと思ったら rsyslog.conf
をいじって宛先のファイルを定義して、 logger
コマンドでログを出力すれば良いのだろうが、ちょっとしたスクリプトを書くだけならそこまでやるのは面倒。
というわけで、自前で「そのスクリプトの標準出力をデフォルトで指定ファイルに流し、先頭に出力日時等の情報をつける」というやり方にしようと思う。
/var/log/syslog
(/var/log/messages
) は多くの場合下記のようになっていると思うので、同じようなフォーマットにする。
Mar 5 12:00:00 vm-host systemd[1]: message.........
スクリプト
bashでないと動かないが、下記のブロックを処理前に書き込むとうまく動いてくれる
LOG_FILE="script.log"
exec > >(
while read line
do
echo "$(LANG=C date "+%b %e %H:%M:%S") $(hostname -s) ${0##*/}[$$]: $line"
done >> $LOG_FILE
)
動作詳細
このスクリプトの重要な要素は exec
によるリダイレクトにある。
exec
文でリダイレクト定義をすると、その後のデフォルトリダイレクト先が指定されたものになる。
下記の場合、今後の標準出力は(他に明示しない限り) file
に書き込まれる。
exec > file
また、今回はリダイレクト先にプロセス置換を用いている。
下記のように書くことで、ファイル入出力と同様にプロセスの標準入出力を与えることができる
実態は /dev/fd/63
のようにファイルディスクリプタが展開されている。
<(command) # コマンド実行結果をファイル入力のように扱う
>(command) # ファイル出力先がコマンド入力のようになる
上記を組み合わせることで、スクリプト内の標準出力がデフォルトで指定コマンドの入力として与えられるようになる。
exec > >(command)
あとは出力整形をして、 $LOG_FILE
に追記している。
echo "$(LANG=C date "+%b %e %H:%M:%S") $(hostname -s) ${0##*/}[$$]: $line"
LANG=C date "+%b %e %H:%M:%S"
は時刻の展開、 hostname -s
はホスト名の取得をしている。
${0##*/}
は自身のコマンド名 $0
から末尾のスラッシュ以前の文字列を取り除く。 /usr/local/bin/script.sh
というファイルなら script.sh
が得られることを期待している。
最後に [$$]
でプロセス番号を展開し、 $line
でログ内容を書き出す。
while文を使っていることからもわかるように、標準出力に大量に吐き出すような使い方は想定していない。
ただこのやり方でも1行あたりの文字数が現実的なら、複数プロセスで取り合っても割とアトミックに書き込んでくれるらしい
参考: マルチプロセス/マルチスレッドによる write とアトミック性 – Togetter
動作例
$ ls
sample.sh
$ cat sample.sh
#!/bin/bash
LOG_FILE="script.log"
exec > >(
while read line
do
echo "$(LANG=C date "+%b %e %H:%M:%S") $(hostname -s) ${0##*/}[$$]: $line"
done >> $LOG_FILE
)
echo "Hello, world!"
touch {001..010}.txt
ls
$ ./sample.sh
$ cat script.log
Mar 10 21:32:11 Ryoto-MacBook-Air sample.sh[43500]: Hello, world!
Mar 10 21:32:11 Ryoto-MacBook-Air sample.sh[43500]: 1.txt
Mar 10 21:32:11 Ryoto-MacBook-Air sample.sh[43500]: 10.txt
Mar 10 21:32:11 Ryoto-MacBook-Air sample.sh[43500]: 2.txt
Mar 10 21:32:11 Ryoto-MacBook-Air sample.sh[43500]: 3.txt
Mar 10 21:32:11 Ryoto-MacBook-Air sample.sh[43500]: 4.txt
Mar 10 21:32:11 Ryoto-MacBook-Air sample.sh[43500]: 5.txt
Mar 10 21:32:11 Ryoto-MacBook-Air sample.sh[43500]: 6.txt
Mar 10 21:32:11 Ryoto-MacBook-Air sample.sh[43500]: 7.txt
Mar 10 21:32:11 Ryoto-MacBook-Air sample.sh[43500]: 8.txt
Mar 10 21:32:11 Ryoto-MacBook-Air sample.sh[43500]: 9.txt
Mar 10 21:32:11 Ryoto-MacBook-Air sample.sh[43500]: sample.sh
Mar 10 21:32:11 Ryoto-MacBook-Air sample.sh[43500]: script.log