シェルスクリプトのエラークリーンアップロジックを含む最良の方法は何ですか?
具体的には、以下を実行するスクリプトがあります。
mount a x
mount b y
setup_thing
mount c z
do_something
umount z
cleanup_thing
umount y
umount x
すべてのマウントとdo_something
それ自体が失敗する可能性があります。たとえば、mount c z
インストールが失敗した場合は、終了する前に正常にインストールされたインストールを削除するスクリプトが必要です。
クリーンアップコードを何度も繰り返したくないし、すべてをif-nestでラップしたくありません(マウントを追加すると、すべてを再インデントする必要があるためです)。
上記の内容を次のように作成できるように最終スタックなどを生成する方法はありますか?
set -e
mount a x && finally umount x
mount b y && finally umount y
setup_thing && finally cleanup_thing
mount c z && finally umount z
do_something
"finally"コマンドが登録されると、終了、通過、または失敗時に(逆順)実行されます。ただし、正常に設定されていないものはクリーンアップされません(インストールが失敗するとクリーンアップが安全ではない可能性があるため)。実行)。
つまり、すべてが成功すれば、、、、umount z
-失敗だけする場合でも同様の順序で実行されます。ただし、失敗した場合にのみ実行されます。cleanup_thing
umount y
umount x
do_something
mount b y
umount x
(失敗した場合は正確な終了コードを維持する必要はありませんが、ゼロまたはゼロ以外の値で適切に終了する必要があるかどうかです。)
終了時にコマンドを実行できる組み込み機能があることはわかっていますが、trap
これは1つのコマンドのみをサポートし、毎回それを置き換えます。これを上記のようなスタックに拡張する方法はありますか?それともこれを行う他のきれいな方法はありますか?
Ye Olde Cコードのエラー処理パターンでも同様の結果が得られますが、x || goto cleanup_before_x
もちろんgotoはありません。
理想的には(da)shで動作しますが、単純化されている場合はbashも要求できます。 (たぶん配列を使用していますか?)
答え1
trap
一般的な手法は、操作が完了したらすべての項目を元に戻すことです。等級でなければなりません。つまり、一部の手順を完了できない場合は、正常に失敗する必要があります。
#!/bin/bash
clean_up=false
errorhandler () {
umount z || true
$clean_up && cleanup_thing
umount y || true
umount x
}
trap errorhandler ERR EXIT
mount a x
mount b y
setup_thing
clean_up=true
mount c z
do_something
トラップもトリガーされるため、EXIT
スクリプトが正常に終了する前にも実行されるため、明示的にクリーンアップする必要はありません。
私は偽のERR
信号がBash拡張だと思います。したがって、これはAsh / Dash / Legacy Bourneシェルなどでは機能しません。
答え2
既存のマウントポイントを別々に処理するように修正されました。コードがこの形式であると仮定すると、問題を引き起こす可能性がある行には1行に1つのコマンドがあります。つまり、mount
次のようになりますumount
。
mount a x
mount b y
setup_thing
mount c z
do_something
umount z
cleanup_thing
umount y
umount x
このパッチワークが機能することもあります。このコードをスクリプトの2行目にコピーします。
mount | cut -d' ' -f3 | sed 's/.*/^u\?mount [^[:space:]]\* &$/' |
grep -v -f - $0 | sed 2d | exec sh -s -- "$@"
仕組み(理論的には):
- 既存のマウントポイントのリストを作成してそのままにするには、
mount
およびを使用します。cut
- これらのマウントポイントを使用するパターンまたはコマンドに一致するパターンのリストを作成するために使用されます
sed
。grep
mount
umount
grep -v
現在のスクリプトからすべての行を検索するために使用されます。いいえ前のパターンのリストと一致しますgrep
。実行に適したすべてのコードをそのままにしてください。sed
再帰を防ぐために2番目の行を削除するために使用されます。exec sh -s -- "$@"
コマンドライン引数でコードを実行するために使用されます。その行の上のexec
項目は実行されません。
exec
変更してcat #
出力を確認して事前にテストしてください。よさそうだったらexec
もう一度入れてください。
答え3
if then
個人的には、読みやすく、簡単に入れ子にすることを選択します。ただし、入れ子のレベルが多い場合は、次のように試してみることができます(echo
プレフィックスでテストしました)。
#!/bin/bash
run1(){ echo mount a x;}
run2(){ echo mount b y;}
run3(){ echo setup_thing;}
run4(){ echo mount c z;}
run5(){ echo do_something;}
undo5(){ :;}
undo4(){ echo umount z;}
undo3(){ echo cleanup_thing;}
undo2(){ echo umount y;}
undo1(){ echo umount x;}
for i in {1..5}
do run$i
code=$?
[ $code != 0 ] && break
done
let i=i-1
while [ $i -gt 0 ]
do undo$i
let i=i-1
done
exit $code
例の順序で実行と元に戻す機能を維持しましたが、お互いの近くに配置すると利点が得られます。
run1(){ mount a x;}
undo1(){ umount x;}
run2(){ mount b y;}
undo2(){ umount y;}
run3(){ setup_thing;}
undo3(){ cleanup_thing;}
...
関数に 1,2,3... 番号を付ける代わりに、実行関数の名前を指定してから、任意の順序で名前を一覧表示できます。元に戻す機能を簡単にするには、一貫したプレフィックスを追加してください。
mnta(){ ... }
undomnta(){ ... }
mntb(){ ... }
undomntb(){ ... }
order='mnta mntb ...' toundo=
for i in $order
do $i
code=$?
[ $code != 0 ] && break
toundo="undo$i $toundo"
done
for i in $toundo
do $i
done
または、実際に使用することもできますが、trap
一度だけ設定し、trap mytrap exit
グローバル変数を使用してクリーンアップしたい項目をアーカイブして各ステップに追加することもできますclean="1 $clean"
。その後、関数はmytrap
の値のみを繰り返します$clean
。