トラップの使用は常にオプションであるとは限りません。たとえば、エラー処理を必要とし、任意のスクリプトから呼び出せる(ヘルパー関数でファイルをソースした後)ある種の再利用可能な関数を記述している場合、その関数は外部スクリプトの終了時間について何も想定できません。トラップの使用が非常に難しくなります。トラップを使用することのもう1つの欠点は、呼び出し性のチェーンで以前に設定された可能性のある以前のトラップを上書きするリスクがあるため、構成性が悪いことです。
トラップなしで適切なエラー処理を行うために使用できる小さなトリックがあります。他の答えからすでに知っているかもしれset -e
ません||
が、サブシェルで実行した場合でも、その後に演算子を使用すると、コマンド内では機能しません。たとえば、これは機能しません:
#!/bin/sh
# prints:
#
# --> outer
# --> inner
# ./so_1.sh: line 16: some_failed_command: command not found
# <-- inner
# <-- outer
set -e
outer() {
echo '--> outer'
(inner) || {
exit_code=$?
echo '--> cleanup'
return $exit_code
}
echo '<-- outer'
}
inner() {
set -e
echo '--> inner'
some_failed_command
echo '<-- inner'
}
outer
ただし、||
演算子は、クリーンアップの前に外部関数から戻るのを防ぐために必要です。秘訣は、内部コマンドをバックグラウンドで実行し、すぐにそれを待つことです。wait
組み込みは、内部コマンドの終了コードを返し、今は使っている||
の後にwait
そうではなく、内部機能、set -e
後者の内部で適切に動作します:
#!/bin/sh
# prints:
#
# --> outer
# --> inner
# ./so_2.sh: line 27: some_failed_command: command not found
# --> cleanup
set -e
outer() {
echo '--> outer'
inner &
wait $! || {
exit_code=$?
echo '--> cleanup'
return $exit_code
}
echo '<-- outer'
}
inner() {
set -e
echo '--> inner'
some_failed_command
echo '<-- inner'
}
outer
これは、このアイデアに基づいて構築された一般的な関数です。local
キーワードを削除すると、すべてのPOSIX互換シェルで機能するはずです。つまり、すべてlocal x=y
を次のように置き換えますx=y
。
# [CLEANUP=cleanup_cmd] run cmd [args...]
#
# `cmd` and `args...` A command to run and its arguments.
#
# `cleanup_cmd` A command that is called after cmd has exited,
# and gets passed the same arguments as cmd. Additionally, the
# following environment variables are available to that command:
#
# - `RUN_CMD` contains the `cmd` that was passed to `run`;
# - `RUN_EXIT_CODE` contains the exit code of the command.
#
# If `cleanup_cmd` is set, `run` will return the exit code of that
# command. Otherwise, it will return the exit code of `cmd`.
#
run() {
local cmd="$1"; shift
local exit_code=0
local e_was_set=1; if ! is_shell_attribute_set e; then
set -e
e_was_set=0
fi
"$cmd" "$@" &
wait $! || {
exit_code=$?
}
if [ "$e_was_set" = 0 ] && is_shell_attribute_set e; then
set +e
fi
if [ -n "$CLEANUP" ]; then
RUN_CMD="$cmd" RUN_EXIT_CODE="$exit_code" "$CLEANUP" "$@"
return $?
fi
return $exit_code
}
is_shell_attribute_set() { # attribute, like "x"
case "$-" in
*"$1"*) return 0 ;;
*) return 1 ;;
esac
}
使用例:
#!/bin/sh
set -e
# Source the file with the definition of `run` (previous code snippet).
# Alternatively, you may paste that code directly here and comment the next line.
. ./utils.sh
main() {
echo "--> main: $@"
CLEANUP=cleanup run inner "$@"
echo "<-- main"
}
inner() {
echo "--> inner: $@"
sleep 0.5; if [ "$1" = 'fail' ]; then
oh_my_god_look_at_this
fi
echo "<-- inner"
}
cleanup() {
echo "--> cleanup: $@"
echo " RUN_CMD = '$RUN_CMD'"
echo " RUN_EXIT_CODE = $RUN_EXIT_CODE"
sleep 0.3
echo '<-- cleanup'
return $RUN_EXIT_CODE
}
main "$@"
例を実行する:
$ ./so_3 fail; echo "exit code: $?"
--> main: fail
--> inner: fail
./so_3: line 15: oh_my_god_look_at_this: command not found
--> cleanup: fail
RUN_CMD = 'inner'
RUN_EXIT_CODE = 127
<-- cleanup
exit code: 127
$ ./so_3 pass; echo "exit code: $?"
--> main: pass
--> inner: pass
<-- inner
--> cleanup: pass
RUN_CMD = 'inner'
RUN_EXIT_CODE = 0
<-- cleanup
<-- main
exit code: 0
このメソッドを使用するときに注意する必要があるのはrun
、コマンドがサブシェルで実行されるため、渡したコマンドから実行されたシェル変数のすべての変更が呼び出し元の関数に伝達されないことです。