트랩을 사용하는 것이 항상 옵션은 아닙니다. 예를 들어, 오류 처리가 필요하고 스크립트에서 호출 할 수있는 일종의 재사용 가능한 함수를 작성하는 경우 (도우미 함수로 파일을 소싱 한 후), 해당 함수는 외부 스크립트의 종료 시간에 대해 아무 것도 가정 할 수 없습니다. 함정 사용이 매우 어렵습니다. 트랩을 사용하는 또 다른 단점은 호출자 체인에서 이전에 설정되었을 수있는 이전 트랩을 덮어 쓸 위험이 있으므로 구성 성이 나쁘다는 것입니다.
트랩없이 적절한 오류 처리를 수행하는 데 사용할 수있는 약간의 트릭이 있습니다. 이미 다른 답변에서 알 수 있듯이 하위 쉘에서 실행하더라도 연산자 뒤에 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
이 아이디어를 기반으로하는 일반적인 기능은 다음과 같습니다. 당신이 제거하면 그것은 모든 POSIX 호환 쉘에서 작동합니다 local
키워드를 즉, 모든 교체, 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
할 것은 명령이 서브 쉘에서 실행되기 때문에 전달한 명령에서 수행 된 쉘 변수의 모든 수정 사항이 호출 함수로 전파되지 않는다는 것입니다.