2022-08-21

シェルスクリプトに「dry run」機能を持たせる方法

ちょっと複雑な機能を持つシェルスクリプトを作成する場合、いきなり実行させて大失敗になるのは問題です。場合によっては、スクリプト自身を書き換えてデバッグ情報を出力させて動作を確認し、うまくいくことが確認されたら、デバッグ情報を出力している箇所を削除したりコメントアウトしたりして対処するという方法が考えられます。しかしこの方法では、デバッグするたびにスクリプトの書き換えを伴うので、デバッグに関係ない箇所まで書き換えてしまう可能性があり、あまりスマートではありません。

 

シェルスクリプトでデバッグをおこなう際のテクニックはいろいろあり、Webを検索すれば「シェルスクリプトのデバッグ」のような情報がみつかります。ここで注意しておきたいのは、何度実行しても差し支えないようなコマンドなら気にする事もないのですが、システムに永続的な影響を及ぼすようなコマンド(ファイルを作成したり、削除したりするなど)を実行する場合は、気軽に何度も実行できないという事です。どのようなコマンドが実行されようとしているのかを確認する仕組みが必要になります。このような機能を「dry run」と呼ぶことがあります。


例えば、単純な例にはなりますが、foobarコマンドを実行しようとするスクリプトがあるとしましょう。このコマンドを実行してしまうとシステムに永続的な変更が加えられてしまうので、デバッグ中に気軽に実行するわけにはいきません。しかし思わぬバグが潜んでいるかもしれませんから、シェルスクリプトの中で本当にfoobarコマンドを呼び出しているのか確認しておきたいところです。このような場合に手っ取り早い方法は、コマンドの前にechoをつけておくことです。「foobar」ではなく「echo foobar」としておけば、コマンドが実行されてしまうことは防げます。


デバッグが済み、シェルスクリプトが完成したと「思った」ら、そのechoを削除すれば万々歳かもしれません。ところが不運なことに、思わぬバグが発見され、再びechoを入れなければならない事態に陥ったとしましょう。デバッグが済めば再びechoを削除できますが、もう今後一切バグが発見されないという保証はありません。その度にechoを入れたり外したりするのは、あまり良い方法ではありません。

 

このような問題を解決する方法として、変数名はなんでも良いのですが、「DRYRUN=echo」のような定義をすることです。そして「echo foobar」とする代わりに「$DRYRUN foobar」とするのです。もし変数が定義されていなければ、$DRYRUNは空ですから、「$DRYRUN foobar」は「foobar」になります。もし変数が定義されていれば、「$DRYRUN foobar」は「echo foobar」になります。こうすることで、変数定義をするか否かだけに注意すればよいので、スクリプトを書き換える際の手間が減ります。さらに変数定義をシェルスクリプトの外部からコントロールしたり、他の方法もありますが、うまく制御することで、シェルスクリプトの書き換え自体を無くすことも可能です。


上述したテクニックがあれば万事解決かと言うと、実はそうでもありません。例えば、awkやsedで、もしくはpythonでも何でも良いのですが、コマンド列を生成し、それをシェルに渡すことで何らかの処理をおこなう場合、単純に「DRYRUN=echo」という定義があっても、あまり役に立たないのです。

 

具体的に、つぎのような場合を考えましょう。パイプの前段のawkで何らかのコマンド列を生成し、それを後段のシェルで実行させようとしています。

awk '何らかのawkスクリプト' | sh

 

ここでawkの処理が万全なら良いのですが、もし不具合があったとすると、いきなりシェルで実行させるのは憚られます。そこで確認のために、以下のようにしたいところです。

awk '何らかのawkスクリプト' | cat

 

もし問題ない事が確認できたら、catをshに変更して、実際に実行をするというのが意図です。しかしそのためにシェルスクリプトを書き換えるのは、スマートではないわけです。この問題を解決するために「DRYRUN=cat」と定義してみましょう。そしてシェルスクリプトでは次のように記述します。こうすることで、もしDRYRUNという変数が定義されていれば「${DRYRUN:-sh}」はcatになります。もしDRYRUNという変数が定義されていなければ、「${DRYRUN:-sh}」はshになります。これで問題は解決できそうな気がします。若干アクロバット的かもしれませんが。

 awk '何らかのawkスクリプト' | ${DRYRUN:-sh}

 

ここで最初の問題に戻ります。「DRYRUN=echo」だった筈の定義を「DRYRUN=cat」に変更してしまった訳ですから、「$DRYRUN foobar」が「echo foobar」ではなく「cat foobar」になってしまいます。これでは最初の問題が解決できません。ここで変数名を別にするという解決策もあるかもしれませんが、アクロバット的ですが、「${DRYRUN:+echo} foobar」でも良いのではないかと思います。こうすればDRYRUNという変数の中身が何であろうと、「echo foobar」になってくれます。

0 件のコメント:

コメントを投稿