なかなかシェルになじめない。せめて例外機能があればなぁと思う。その点、PowerShell は書きやすい。でも、シェルも PowerShell も(特に Java と比べると)ファイルを扱う単体テストコードは、とっても書きやすい。最初にアサートの関数とか書くのがちょっと面倒なくらい。それはまぁよい。
で、またまた「grep+while」で見事にはまる。。パイプ後の while 文って、別プロセスで動作していることを意識しにくいから、勘違いしやすいなぁと。
今回は、設定ファイルっぽいファイル*1を1行ずつ読むシェルスクリプトを作成していた時。ちなみに、変数 NORMAL_END と ERROR_END には整数値が設定されており、正常時には NORMAL_END で、異常時には ERROR_END で終わらせなければならない(というルールを自分で作った)。
まず最初に書いてしまったダメなスクリプトは次。パッと見、よさそうではないですか!
#“ダメな”シェルスクリプトの最後の部分 # 必ず NORMAL_END で終了してしまう grep -v "^#" "${CONFIG}" | \ grep -v "^[[:blank:]]*$" | \ while read _line do my_function ${_line} [ ${?} -ne ${NORMAL_END} ] && exit ${ERROR_END} done exit ${NORMAL_END}
ここで、変数 CONFIG にはファイルのパスが設定されているとする(変数 IFS にも適切な値が設定されているとする)。また、関数 my_function は、NORMAL_END または ERROR_END を返すとする。
で、この“ダメな”シェルスクリプトは、必ず NORMAL_END で終了する。これは、while 文中の exit で終了するのが、パイプによるプロセスだからだ。「シェルで1行ずつファイルから読み込む - あしのあしあと」で反省したのではなかったのか?
ならば、while 文の終了コードを判定しようと、何も考えず次のスクリプトに修正。
#“ダメな”シェルスクリプトの最後の部分 # ほぼほぼ ERROR_END で終了してしまう grep -v "^#" "${CONFIG}" | \ grep -v "^[[:blank:]]*$" | \ while read _line do my_function ${_line} [ ${?} -ne ${NORMAL_END} ] && exit ${ERROR_END} done [ ${?} -ne ${NORMAL_END} ] && exit ${ERROR_END} exit ${NORMAL_END}
でも、このシェルスクリプトは、(NORMAL_END の値が 1 でない限り)必ず ERROR_END で終了する。正常にループが終わった場合には、“[ ${?} -ne ${NORMAL_END} ]”が最後に実行され、while 文の戻り値が 1 になるからだ。
で、最終的には次のように修正した。while 文が正常に流れれば、“:”が実行され(つまり、何も実行されないのだが)、while 文全体の戻り値が 0 になる。
#“ダサいけどたぶんOKな”シェルスクリプトの最後の部分 grep -v "^#" "${CONFIG}" | \ grep -v "^[[:blank:]]*$" | \ while read _line do my_function ${_line} [ ${?} -ne ${NORMAL_END} ] && exit ${ERROR_END} : done [ ${?} -ne 0 ] && exit ${ERROR_END} exit ${NORMAL_END}
あぁ、かっこ悪いなぁ。
*1:“#”から始まる行は無視する。スペース、タブ、改行のみの行は無視する。みたいな。