スミッコ基盤チームのスミッコで、こっそりとシェルを書いている。最近、仕事で実装することはほとんどなかったため、なかなかにして楽しい。営業利益率は、いったん考えないことにする。
で、(今さら)シェルスクリプトを使って、ファイルから1行ずつテキストを読み込み処理したくなった。まぁよくある話だろう。久々のシェル、どうやるのか覚えていなかったため、ちょいと調べたら、次のサンプルが出てきた。
BUFIFS=$IFS IFS= exec 3< 入力ファイル名 while read FL 0<&3 do 処理 done exec 3<&- IFS=$BUFIFS
《【 ファイルからの読み込み 】 | 日経 xTECH(クロステック)より》
これは、とてもお行儀のよい、正しいスクリプトだ。でも最初見たときは、「何コレ?そんなバカな。ファイルディスクリプタの3番?やめやめ、read コマンドをループで使えばよいのね。」と思ってしまった。時間がない(と勝手に思っている)時はこういう逃げの思考がすぐに働く。そして結局、時間をとられることになる。
で、書いてしまったダメなスクリプトが次*1。
#### うまく動作しない!! # 処理中にエラーが発生した項目を格納する変数 proc_fail_items="" # _target_file から _rec_mark で始まる行を取得し、 # 定義されている項目に対して、処理を実行する。 grep "^${_rec_mark}" ${_target_file} | \ sed "s/^${_rec_mark}//; s/[[:blank:]]//g" | \ while read _target_item do _some_proc ${_target_item} done if [ "x${proc_fail_items}" = "x" ]; then exit ${NORMAL_END} else proc_fail_items=`echo ${proc_fail_items} | sed "s/^,//"` : # ここで、proc_fail_items をログに出力する。 exit ${ERROR_END} fi _some_proc (){ _item=${1} : # ここで、_item に対する処理を実行する。 if [ ${?} -eq 0 ]; then return ${NORMAL_END} else proc_fail_items="${proc_fail_items},${_item}" return ${ERROR_END} fi }
このスクリプトは、処理に失敗した項目を、変数 proc_fail_items に格納しておいて、ループ中に1回でも処理が失敗していたら(proc_fail_items が空でなかったら)エラーで終了しようと目論んだモノ。でもこの変数は、while文の内外で共有されない。つまり、18行目 proc_fail_items の値は、6行目のままである。これは、パイプラインの処理が別プロセスで実行される(そしてエクスポートされていない変数が引き継がれない)ことを考えていないダメな例なのだ。ようやくつまずいた。
もっと一般的には、パイプを使わなくっても、while 〜 done が別プロセスになることがある*2らしい。従って、先に引用したスクリプトが最も安全となる。今後、3回くらいのエントリに分けて、先の正しいスクリプトを説明してみようと思う。
2010/05/10追記
「で、今度こそシェルでテキストファイルから1行ずつ読み込む - あしのあしあと」にまとめを書いた。