小ネタ。ちょっとしたBashスクリプトを書いている時によくやっています。
背景
「Bashスクリプトを書いたけど、実行するとファイル削除されたりするから勇気がいる…」といったことがあります。ファイルを削除せず一旦mvにするなどして、やり直しがきくやり方を考えるとかもありますが、人生やり直しがきくことばかりでもありません。
そのため「スクリプトを実行したら何が起きるか?」を確認できるようにしたいことがあります。これをちょっとした手間で対応してみましょうという話です。
例
以下がちょっとした手間を含むスクリプトです。
#!/bin/bash
# myscript.sh
set -ue
# コマンドをDRYRUNに対応!
function run(){
if [ "${DRYRUN:-}" == "" ]; then # 変数 "DRYRUN" が定義されていない場合は本当に実行する
"$@"
else # 定義されている場合はコマンドを表示
{
echo -n "[DRYRUN]"
printf ' "%s"' "$@"
echo
} >&2
fi
}
# 気になるコマンドの前に "run" をつけよう!
run rm -rf ./* "space ok"
echo done
実行例
# 実行例
export DRYRUN=1
bash myscript.sh
# 実行例 (環境変数を汚したくない場合)
DRYRUN=1 bash myscript.sh
echo "実際に実行"
unset DRYRUN
bash myscript.sh
出力例
[DRYRUN] "rm" "-rf" "./myscript.sh" "./prog.sh" "space ok"
done
[DRYRUN] "rm" "-rf" "./myscript.sh" "./prog.sh" "space ok"
done
実際に実行
done
環境変数DRYRUNを渡すとrmコマンドの部分はコンソール出力されるだけになります。
もちろん、実際にコマンドは実行しなくなるので、副作用がある前提のコマンドの実行には注意しましょう。
上記の例では環境変数がないときはコマンドを実行してしまいます。本当に危険な操作の場合はデフォルトはDRYRUNにしたいという事もあると思いますが、それはちょっと書き換えて対応してください。
関数名や変数名は自由に置き換えて使ってください。
解説
解説することもないですが。
Bashのパラメータ展開
${DRYRUN:-}
がミソです。Bashは環境変数と変数を同じ記述で参照できます。しかしset -u
が効いている場合は未定義な変数を参照することとなってしまいます。その場合${変数名:-デフォルト値}
というパラメータ展開が使えます。これは変数名
に対応する変数もしくは環境変数が未定義の場合、デフォルト値
を返します。
環境変数の渡し方
Bashではコマンドを実行する前に Key1=value1 Key2=value2 コマンド 引数...
というような形で実行することで、そのコマンドだけに環境変数を渡して実行することができます。コマンド
はbashに限りません。コマンドには環境変数"Key1"と環境変数"Key2"が設定されます。環境変数が汚れないのでお勧めです。
Bashで実行中に環境変数を設定するには export Key1=value1
という表記も使えますが、これは以降の操作時の環境変数にも影響します。環境変数を削除したい場合はunset Key1
とします。export Key1=
としてしまうと、空文字列のKey1が環境変数として定義されるので注意です。
また、スクリプトが複数に分かれていて入れ子構造のように呼び出していても環境変数で渡されていくので、便利です。
応用として if [ "$DEBUG" != "" ] set -x
みたいなことをスクリプトの先頭でやっておくと簡易トレースができます。bash -x myscript.sh
だとスクリプト内でbash any.sh
とやられるとset -x
が途切れますが、先頭に書いておくと同じ理由で対応できます(どこかのBlogで見かけていいなと思って真似ていたのですが、出典を失念してしまった...)
あと引数をswitchでパースするスクリプトを見ますが、環境変数だとパースの必要がなく簡単です。
printf ' "%s"' "$@"
とは
手を抜いて echo "$@"
でもいいのですが、この場合、引数の区切りがわかりにくくなるのでこういう指定にしています。上記の例だと space ok
の引数が1引数か2引数か区別できなくなるので、それを区別できるようにしています。
printfコマンドはprintfっぽい挙動をしますが、引数が多すぎる場合、formatを繰り返し評価して連結します。今回の場合、format部分の書式は%s
の1つだけなので、単純に引数の数だけformatが評価されます。例えばprintf "%s,%s" 1 2 3
とした場合、1,23,
という出力になります。引数が不足している書式部分については、書式に基づいた処理になります。例えば%s
は空になりますが、%04d
だと"0000"のようになります。
dry-runとはなにか
予行練習です。例えば「盛大に花火を打ち上げる」という作業を実際にやってしまうと取り返しがつかないコストが掛かります。なので やったものとして 作業を進めます。実際には行いませんが、この通しをするだけでも安心できるでしょう。
なので、dry-runをサポートするときは副作用がないようにするべきです(例えばファイルを作ったり消したりするような操作を全て今回のやり方でdry-runに対応するようにする)
実際は副作用がある操作を絡めてスクリプトにしたくなることもあるのでなかなか難しいとは思いますが…