シェルスクリプト ログ出力関数あれこれ
スクリプトの実行ログ関係
シェルスクリプトを実行した際、デバッグするなら bash -x xxxx.sh という感じで、デバッグ実行することが多い。
でも、一部の管理職には不評なんだよね。
実行ログで出してくれと。
そんな理由で、スクリプトログを出力する関数を使うことが多いため、自分で使いやすい関数を汲んでおきたくなった。
毎回、現場に合わせて作るのも結構辛くなってきたので。
仕様
・指定のディレクトリにスクリプトログを置く。
・ログファイル名は、スクリプト名の拡張子を .log に置き換え、日付を含めたもの。
・ログは、yyyy/mm/dd hh:mm:ss のあとにメッセージを入れる。
・出力先は、変数指定がなければ自動で決める。
この仕様から、細かい条件を各環境に合わせれば良い。
自分の趣味を色濃く反映するものとして、
・関数名は fnLogger とする。
・出力先変数は pLogFile とする。
関数は英語で function と言うので、関数定義には fn を付けている。
変数なら、特に例外規定がなければ Parameter とか Variable なので p を。
fXxxx だと、フラグ関係に。
nXxxx だと、数値関係に。
それから、使い慣れているキャメルケースで変数名を定義する。
(スネークケースは好きではないが、使わないとは言っていない)
他のスクリプト等で変数が重複する確率が低いので、特に規定がない場合はこれを使っていたりする。
word とかの MS-Office 製品で、説明資料を作るときに下手に _ が入っていると、波線で見えなくなることがあるんだよね。
いや、全部大文字にしていれば問題ないんだけど、スネークケースを使う人って、なぜか小文字にしたがる……。
ということで、シェルスクリプトの変数は、会社で使うなら全部大文字が良いです。
スクリプト(関数)
基本形となる関数が、こちら。
fnLogger() { pLogFile="${pLogFile:=$(/usr/bin/basename ${0} .sh)_$(date +'%Y%m%d').log}" echo "$(date +'%Y/%m/%d %H:%M:%S') $*" >> ${pLogFile} }
上記 pLogFile という変数を、関数外で定義しておけば、ログ出力先はその指定に従う。
フラグ次第で、出力先を変えるタイプ。
### Parameter and Variable ## Directory pLogDir=/var/log/script ## File pLogFile=$(/usr/bin/basename ${0} .sh)_$(date +'%Y%m%d').log ## Path pLogPath=${pLogDir}/${pLogFile} ## Flag # 0 = Script log # 1 = /var/log/messages fOutput=0 ### Function fnLogger() { if [ "${fOutput}" -eq "0" ]; then echo "$(date +'%Y/%m/%d %H:%M:%S') $*" >> ${pLogPath} else logger "$*" fi }
作りこもうとすると、ログを出力したいだけでもこんなに長くなってしまうんだよね……
まぁ、他のファイルで定義しておいて、それを . で読み込むことが多くなってきたから気にならないんだけど。
いくつか出力先を選びたいなら、こんな手法も。
### Parameter and Variable ## Parameter pID=$(/usr/bin/basename ${0} .sh) pYYYYMMDD=$(date +'%Y%m%d') ## Directory pLogDir=/var/log/script ## File pLogStdFile=${pID}_${pYYYYMMDD}.log pLogErrFile=${pID}_${pYYYYMMDD}_Err.log ## Path pLogStdPath=${pLogDir}/${pLogStdFile} pLogErrPath=${pLogDir}/${pLogErrFile} ## Flag # 0 = Script log - Standard # 1 = Script log - Error # 2 = /var/log/messages fOutput=0 ### Function fnLogger() { p1st=$1 shift case ${p1st} in 0 ) echo "$(date +'%Y/%m/%d %H:%M:%S') $*" >> ${pLogStdPath};; 1 ) echo "$(date +'%Y/%m/%d %H:%M:%S') $*" >> ${pLogErrPath};; 2 ) logger "$*";; esac } ### Main # 0 Standard fnLogger 0 "Message" # 1 Error fnLogger 1 "Error Message" # 2 /var/log/messages fnLogger 2 "/var/log/messages output message"
パッと見て、0~2 の数字も一緒に出力されそうに見えるけど、実行結果はこんな感じ。
$ bash ./sample.sh $ $ cat sss_20190519.log 2019/05/19 14:04:13 Message $ $ cat sss_20190519_Err.log 2019/05/19 14:04:13 Error Message $ $ tail -1 /var/log/messages May 19 14:04:13 (ホスト名) (ユーザ名): /var/log/messages output message
shift コマンドが引数をひとつ消してくれる効果を持っている。
それから、コードを利用したログ出力方法もこんな一案。
コード自体を変数名としたもの。
$ cat mcode.txt A000E="Sample essage." A001E="Message 1st" A002E="Message 2nd" $ $ cat sample2.sh #!/bin/bash . ./mcode.txt fnLogCode() { echo "${1}: $(eval echo '$'${1})" } fnLogCode A000E $ $ bash ./sample2.sh A000E: Sample essage. $
コードは csv 形式で記載されているもの。
$ cat code.csv I0000,Sample message I0001,Information message. W0001,Warning message. E0001,Error message.
これを抽出する方法を考えてみる。まずはシンプルに grep で。
$ grep I0000 code.csv I0000,Sample message $ $ grep I0000 code.csv | cut -d "," -f 1 I0000 $ $ grep I0000 code.csv | cut -d "," -f 2 Sample message $
### Parameter and Variable ## Parameter pID=$(/usr/bin/basename ${0} .sh) pYYYYMMDD=$(date +'%Y%m%d') ## Directory pLogDir=/var/log/script ## File pLogCSVFile=code.csv pLogStdFile=${pID}_${pYYYYMMDD}.log ## Path pLogStdPath=${pLogDir}/${pLogStdFile} pLogCSVPath=${pLogDir}/${pLogCSVFile} ### Function fnLogger() { [ -r "${pLogCSVPath}" ] || return 1 pMessage=$(grep $1 ${pLogCSVPath} | cut -d "," -f 2) [ "${pBaseMessage}" == "" ] && return 1 echo "$(date +'%Y/%m/%d %H:%M:%S') ${pMessage}" >> ${pLogStdPath};; }
これだとメッセージ内容に , を含められないので、含められるようにしたバージョン。
### Parameter and Variable ## Parameter pID=$(/usr/bin/basename ${0} .sh) pYYYYMMDD=$(date +'%Y%m%d') ## Directory pLogDir=/var/log/script ## File pLogCSVFile=code.csv pLogStdFile=${pID}_${pYYYYMMDD}.log ## Path pLogStdPath=${pLogDir}/${pLogStdFile} pLogCSVPath=${pLogDir}/${pLogCSVFile} ### Function fnLogger() { [ -r "${pLogCSVPath}" ] || return 1 tMessage=$(grep $1 ${pLogCSVPath}) [ "${tBaseMessage}" == "" ] && return 1 pMessage=${tMessage#*,} echo "$(date +'%Y/%m/%d %H:%M:%S') ${pMessage}" >> ${pLogStdPath};; }
考えていくと、いろいろとやり方はあるもんだね。