シェルスクリプトのパスとパイプ
考えてみるとここ1年はJavaのコードはほとんど書いていなくてそれよりはシェルスクリプトを書いている機会の方が多かった。
なのでここら辺でシェルスクリプトを書いていてハマったところというかちょっとしたTipsをメモっておこうと思う。前提としてBashである。
まずはパスについての話。
シェルスクリプトでパスをべた書きしているとポータビリティが失われてしまい別のマシンに移行する場合に困るケースが多い。
なのでこれをスマートに解決したいわけである。
かといって単純に相対パスを使っていると困るケースがある。
例えばある基準となるディレクトリ${BASE_DIR}があってその下にシェルスクリプトをおくディレクトリscriptsとログをおくディレクトリlogという構成にしたいとしよう。
scripts/ log/
この場合にシェルスクリプトの中で
echo ... > ../log/log.txt
とかやるとシェルスクリプトを実行する場所がscriptsに固定されてしまう。
それで良いケースもあるだろうが、もうちょっとスマートにやる場合は基準となるディレクトリ${BASE_DIR}の絶対パスを求めてそれを使うことである。
どう求めるかというとこんな感じ。
BASE_DIR=$(cd $(dirname $0);pwd)
説明はまるっと借用させていただくw
$0は実行中のシェルスクリプトのファイル名。
UNIXシェルスクリプトメモ(Hishidama's UNIX shell script Memo)
dirnameを使うことで、シェルスクリプトのディレクトリーが取得できる。
ただしこれは相対パスかもしれないので、cdでそのディレクトリーに移動し、pwdでその場所(絶対パス)を取得している。
このように設定した${BASE_DIR}を使ってパスを作ればよい。
echo ... > ${BASE_DIR}/log/log.txt
この手の共通的な設定は別ファイル、例えばconfig.shにくくりだして読み込むのがよろしい。
こんな感じにね。
cwd=`dirname $0` . ${cwd}/config.sh
つづいてはパイプ周りの話。
例えばあるディレクトリ${WORK_DIR}にあるデータファイル全てを1つ1つ処理したい。
ただし仕様としてある1つのファイルでエラーが起きた場合も処理を続行したい。
しかし1ファイルでも異常があったら最終的には異常終了にしたい。
全てのファイルを正常に処理できたときのみ正常終了とする。
言葉だと伝わりづらいと思うが、シェルスクリプトで書くとこんな感じになるだろう。
#!/bin/sh cwd=`dirname $0` . ${cwd}/config.sh exit_code=0 ls ${WORK_DIR} | while read datafile do ${cwd}/process.sh ${WORK_DIR}/${datafile} if [ $? -ne 0 ]; then exit_code=1 fi done exit ${exit_code}
しかしこの方法では仕様を満たさない。
ループの外側のexit_codeが1になることは無いから。
つまり1つのファイルでエラーが起きた場合でも全体としては正常終了する。
何故かというと | (パイプ)処理から先は別プロセスで起動していて、ループの外側と内側のexit_code変数は別物だから。
解決策はリダイレクトを使う。
#!/bin/sh cwd=`dirname $0` . ${cwd}/config.sh exit_code=0 ls ${WORK_DIR} > datafile.list while read datafile do ${cwd}/process.sh ${WORK_DIR}/${datafile} if [ $? -ne 0 ]; then exit_code=1 fi done < datafile.list exit ${exit_code}
これならOK。
リダイレクトするとそのファイルの処理を考えないといけないのが南天のど飴なのであるがこれは仕方ない。
似たような話だが、下記のようにすると多重ループをexitで抜けられない。
#!/bin/sh cwd=`dirname $0` . ${cwd}/config.sh ls ${WORK_DIR} | while read dir do ls ${WORK_DIR}/${dir} | while read datafile do ${cwd}/process.sh ${WORK_DIR}/${dir}/${datafile} if [ $? -ne 0 ]; then exit 1 fi done done
これはさっきと違って1ファイルでも異常があったら処理を続行せずに終了したいパターン。
これもリダイレクトすればOK。
#!/bin/sh cwd=`dirname $0` . ${cwd}/config.sh ls ${WORK_DIR} > dirs.txt while read dir do ls ${WORK_DIR}/${dir} > dir.txt while read datafile do ${cwd}/process.sh ${WORK_DIR}/${dir}/${datafile} if [ $? -ne 0 ]; then exit 1 fi done < dir.txt done < dirs.txt
もしくはこんな感じでdoneの直後でexitする。
#!/bin/sh cwd=`dirname $0` . ${cwd}/config.sh ls ${WORK_DIR} | while read dir do ls ${WORK_DIR}/${dir} | while read datafile do ${cwd}/process.sh ${WORK_DIR}/${dir}/${datafile} if [ $? -ne 0 ]; then exit 1 fi done if [ $? -ne 0 ]; then exit 1 fi done
いじょ。