また grep+while か

なかなかシェルになじめない。せめて例外機能があればなぁと思う。その点、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:“#”から始まる行は無視する。スペース、タブ、改行のみの行は無視する。みたいな。