쉘 스크립트를 작성 중이며 터미널 앱이 설치되어 있는지 확인해야합니다. 더 깔끔한 방법이 없다면 TRY / CATCH 명령을 사용 하여이 작업을 수행하고 싶습니다.
help test
것이 문제의 해결책을 찾는 데 도움이 될 것 같습니다 .
trap
명령을 사용하여 잡을 수 있습니다 .
쉘 스크립트를 작성 중이며 터미널 앱이 설치되어 있는지 확인해야합니다. 더 깔끔한 방법이 없다면 TRY / CATCH 명령을 사용 하여이 작업을 수행하고 싶습니다.
help test
것이 문제의 해결책을 찾는 데 도움이 될 것 같습니다 .
trap
명령을 사용하여 잡을 수 있습니다 .
답변:
Bash에 TRY CATCH 명령이 있습니까?
아니.
배쉬에는 많은 프로그래밍 언어에서 찾을 수있는만큼 사치가 없습니다.
try/catch
bash 에는 없습니다 . 그러나, 하나는 사용하여 비슷한 동작을 달성 할 수있다 &&
나 ||
.
사용 ||
:
경우 command1
실패 후 command2
다음과 같이 실행
command1 || command2
마찬가지로, 사용 &&
, command2
경우에 실행 command1
성공
가장 가까운 근사값은 try/catch
다음과 같습니다.
{ # try
command1 &&
#save your output
} || { # catch
# save log for exception
}
또한 bash에는 몇 가지 오류 처리 메커니즘이 포함되어 있습니다.
set -e
간단한 명령이 실패하면 스크립트가 중지됩니다.
또한 왜 안되나요 if...else
? 가장 친한 친구입니다.
#save your output
가 실패하지 않거나 "catch"블록이 계속 실행되도록주의해야합니다.
if...else
구문 을 사용하라는 제안이 있습니다 . 이것은 bash 명령이 성공적으로 실행되면 "truthy"로, 실패하면 "falsy"로 해석됨을 의미합니까?
set -e
반드시 일을하는 가장 좋은 방법은 아닙니다. 다음은 반론 / 특별 사례입니다. mywiki.wooledge.org/BashFAQ/105
여기에서 찾은 몇 가지 답변을 바탕으로 내 프로젝트의 소스로 사용할 작은 도우미 파일을 만들었습니다.
#!/bin/bash
function try()
{
[[ $- = *e* ]]; SAVED_OPT_E=$?
set +e
}
function throw()
{
exit $1
}
function catch()
{
export ex_code=$?
(( $SAVED_OPT_E )) && set +e
return $ex_code
}
function throwErrors()
{
set -e
}
function ignoreErrors()
{
set +e
}
다음은 사용중인 모습의 예입니다.
#!/bin/bash
export AnException=100
export AnotherException=101
# start with a try
try
( # open a subshell !!!
echo "do something"
[ someErrorCondition ] && throw $AnException
echo "do something more"
executeCommandThatMightFail || throw $AnotherException
throwErrors # automaticatly end the try block, if command-result is non-null
echo "now on to something completely different"
executeCommandThatMightFail
echo "it's a wonder we came so far"
executeCommandThatFailsForSure || true # ignore a single failing command
ignoreErrors # ignore failures of commands until further notice
executeCommand1ThatFailsForSure
local result = $(executeCommand2ThatFailsForSure)
[ result != "expected error" ] && throw $AnException # ok, if it's not an expected error, we want to bail out!
executeCommand3ThatFailsForSure
echo "finished"
)
# directly after closing the subshell you need to connect a group to the catch using ||
catch || {
# now you can handle
case $ex_code in
$AnException)
echo "AnException was thrown"
;;
$AnotherException)
echo "AnotherException was thrown"
;;
*)
echo "An unexpected exception was thrown"
throw $ex_code # you can rethrow the "exception" causing the script to exit if not caught
;;
esac
}
||
애프터 catch
과 이전 {}
블록? 나는 그것이라고 생각했을 것이다&&
if False or run_if_failed()
단락이 발생했거나 true를 반환하지 않은 첫 번째 명령문을 시도했으며 이제 다음 명령문으로 넘어 가고 있음을 의미합니다. &&
첫 번째 문장 ( try
)이 거짓을 낳았 기 때문에 작동하지 않을 것인데, 이는 해당 catch
문장이 타우 톨 로지 규칙에 필요하지 않음 을 의미합니다 false&any equals false
. 단락이 아닌 AND / OR 만 둘 다 실행합니다.
bash에서 거의 완벽한 try & catch 구현을 개발하여 다음과 같은 코드를 작성할 수 있습니다.
try
echo 'Hello'
false
echo 'This will not be displayed'
catch
echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
try-catch 블록을 자체 안에 중첩시킬 수도 있습니다!
try {
echo 'Hello'
try {
echo 'Nested Hello'
false
echo 'This will not execute'
} catch {
echo "Nested Caught (@ $__EXCEPTION_LINE__)"
}
false
echo 'This will not execute too'
} catch {
echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
}
코드는 내 배쉬 보일러 플레이트 / 프레임 워크 의 일부입니다 . 또한 역 추적 및 예외를 포함한 오류 처리 (및 기타 유용한 기능)를 사용하여 try & catch 아이디어를 확장합니다.
try & catch를 담당하는 코드는 다음과 같습니다.
set -o pipefail
shopt -s expand_aliases
declare -ig __oo__insideTryCatch=0
# if try-catch is nested, then set +e before so the parent handler doesn't catch us
alias try="[[ \$__oo__insideTryCatch -gt 0 ]] && set +e;
__oo__insideTryCatch+=1; ( set -e;
trap \"Exception.Capture \${LINENO}; \" ERR;"
alias catch=" ); Exception.Extract \$? || "
Exception.Capture() {
local script="${BASH_SOURCE[1]#./}"
if [[ ! -f /tmp/stored_exception_source ]]; then
echo "$script" > /tmp/stored_exception_source
fi
if [[ ! -f /tmp/stored_exception_line ]]; then
echo "$1" > /tmp/stored_exception_line
fi
return 0
}
Exception.Extract() {
if [[ $__oo__insideTryCatch -gt 1 ]]
then
set -e
fi
__oo__insideTryCatch+=-1
__EXCEPTION_CATCH__=( $(Exception.GetLastException) )
local retVal=$1
if [[ $retVal -gt 0 ]]
then
# BACKWARDS COMPATIBILE WAY:
# export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-1)]}"
# export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[@]}-2)]}"
export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[-1]}"
export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[-2]}"
export __EXCEPTION__="${__EXCEPTION_CATCH__[@]:0:(${#__EXCEPTION_CATCH__[@]} - 2)}"
return 1 # so that we may continue with a "catch"
fi
}
Exception.GetLastException() {
if [[ -f /tmp/stored_exception ]] && [[ -f /tmp/stored_exception_line ]] && [[ -f /tmp/stored_exception_source ]]
then
cat /tmp/stored_exception
cat /tmp/stored_exception_line
cat /tmp/stored_exception_source
else
echo -e " \n${BASH_LINENO[1]}\n${BASH_SOURCE[2]#./}"
fi
rm -f /tmp/stored_exception /tmp/stored_exception_line /tmp/stored_exception_source
return 0
}
my_output=$(try { code...; } catch { code...; })
당신은 사용할 수 있습니다 trap
:
try { block A } catch { block B } finally { block C }
다음과 같이 번역됩니다.
(
set -Ee
function _catch {
block B
exit 0 # optional; use if you don't want to propagate (rethrow) error to outer shell
}
function _finally {
block C
}
trap _catch ERR
trap _finally EXIT
block A
)
-E
내가 생각하는 플래그 를 원 하기 때문에 함정이 기능으로 전파됩니다
아마도 비슷한 많은 솔루션이 있습니다. 아래는 주석에 설명과 함께 시도 / 캐치를 수행하는 간단하고 효과적인 방법입니다.
#!/bin/bash
function a() {
# do some stuff here
}
function b() {
# do more stuff here
}
# this subshell is a scope of try
# try
(
# this flag will make to exit from current subshell on any error
# inside it (all functions run inside will also break on any error)
set -e
a
b
# do more stuff here
)
# and here we catch errors
# catch
errorCode=$?
if [ $errorCode -ne 0 ]; then
echo "We have an error"
# We exit the all script with the same error, if you don't want to
# exit it and continue, just delete this line.
exit $errorCode
fi
bash
( -e
플래그 를 설정하지 않는 한) 오류 상태를 감지 한 경우 실행중인 실행을 중단하지 않습니다 . 이러한 특수 상황 ( "일반적으로"예외 ")으로 인해"베일 아웃 " try/catch
을 방지 하기 위해 이를 제공하는 프로그래밍 언어가이를 수행합니다 .
에서 bash
, 대신, 문제의 유일한 명령은 오류 상태를 나타내는, 0보다 종료 코드의 큰 함께 종료됩니다. 당신은 물론 그 확인할 수 있지만 자동 없기 때문에 구제 아무것도를하는 시도 / 캐치는 이해가되지 않습니다. 그 맥락이 부족합니다.
그러나 결정한 시점에서 종료 될 수있는 하위 쉘을 사용하여 제거 를 시뮬레이션 할 수 있습니다.
(
echo "Do one thing"
echo "Do another thing"
if some_condition
then
exit 3 # <-- this is our simulated bailing out
fi
echo "Do yet another thing"
echo "And do a last thing"
) # <-- here we arrive after the simulated bailing out, and $? will be 3 (exit code)
if [ $? = 3 ]
then
echo "Bail out detected"
fi
대신의 some_condition
와 함께 if
당신은 또한 단지 명령을 시도 할 수 있고, 경우에 그것은 실패 (0이 아닌 종료 코드의 이상을 가지고), 구제 :
(
echo "Do one thing"
echo "Do another thing"
some_command || exit 3
echo "Do yet another thing"
echo "And do a last thing"
)
...
불행히도이 기술을 사용하면 255 개의 다른 종료 코드 (1..255)로 제한되며 적절한 예외 개체를 사용할 수 없습니다.
시뮬레이션 된 예외와 함께 전달하기 위해 더 많은 정보가 필요한 경우 서브 쉘의 stdout을 사용할 수 있지만 약간 복잡하고 또 다른 질문입니다 ;-)
위에서 언급 한 -e
플래그를 쉘에 사용하면 명시 적 exit
진술을 제거 할 수도 있습니다 .
(
set -e
echo "Do one thing"
echo "Do another thing"
some_command
echo "Do yet another thing"
echo "And do a last thing"
)
...
모두가 말했듯이 bash는 언어 지원 try / catch 구문이 없습니다. 명령에 종료 코드가 0이 아닌 경우 -e
인수 와 함께 bash를 시작 하거나 set -e
스크립트 내에서 사용 하여 전체 bash 프로세스를 중단 할 수 있습니다. ( set +e
실패한 명령을 일시적으로 허용 할 수도 있습니다 .)
따라서 try / catch 블록을 시뮬레이트하는 한 가지 기술은 하위 프로세스를 시작하여 -e
활성화 된 작업을 수행하는 것입니다 . 그런 다음 기본 프로세스에서 하위 프로세스의 리턴 코드를 확인하십시오.
Bash는 heredoc 문자열을 지원하므로이를 처리하기 위해 두 개의 별도 파일을 작성할 필요가 없습니다. 아래 예제에서 TRY heredoc은 -e
활성화 된 별도의 bash 인스턴스에서 실행 되므로 명령이 0이 아닌 종료 코드를 반환하면 하위 프로세스가 중단됩니다. 그런 다음 주 프로세스로 돌아가서 catch 블록을 처리하기 위해 리턴 코드를 확인할 수 있습니다.
#!/bin/bash
set +e
bash -e <<TRY
echo hello
cd /does/not/exist
echo world
TRY
if [ $? -ne 0 ]; then
echo caught exception
fi
올바른 언어 지원 try / catch 블록은 아니지만 비슷한 가려움증이 생길 수 있습니다.
그리고 당신은 트랩이 http://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html 같지 않지만,이 목적으로 사용할 수있는 다른 기술
내가 사용하는 매우 간단한 것 :
try() {
"$@" || (e=$?; echo "$@" > /dev/stderr; exit $e)
}
||
에 ()
, 그것은 출구의 주요 쉘을 유발하지 않고, 서브 쉘 종료에 실행됩니다. { }
대신 그룹화를 사용하십시오 .
아래는 다른 답변에 사용 된 단순화 된 스크립트의 전체 사본입니다 . 추가 오류 검사 외에도 사용자가 기존 별칭의 이름을 변경할 수있는 별칭이 있습니다. 구문은 다음과 같습니다. 상기 중간 new_alias
변수를 생략하고 별명을 제거한다.
ChangeAlias old_alias [new_alias]
완전한 스크립트는 다음과 같습니다.
common.GetAlias() {
local "oldname=${1:-0}"
if [[ $oldname =~ ^[0-9]+$ && oldname+1 -lt ${#FUNCNAME[@]} ]]; then
oldname="${FUNCNAME[oldname + 1]}"
fi
name="common_${oldname#common.}"
echo "${!name:-$oldname}"
}
common.Alias() {
if [[ $# -ne 2 || -z $1 || -z $2 ]]; then
echo "$(common.GetAlias): The must be only two parameters of nonzero length" >&2
return 1;
fi
eval "alias $1='$2'"
local "f=${2##*common.}"
f="${f%%;*}"
local "v=common_$f"
f="common.$f"
if [[ -n ${!v:-} ]]; then
echo "$(common.GetAlias): $1: Function \`$f' already paired with name \`${!v}'" >&2
return 1;
fi
shopt -s expand_aliases
eval "$v=\"$1\""
}
common.ChangeAlias() {
if [[ $# -lt 1 || $# -gt 2 ]]; then
echo "usage: $(common.GetAlias) old_name [new_name]" >&2
return "1"
elif ! alias "$1" &>"/dev/null"; then
echo "$(common.GetAlias): $1: Name not found" >&2
return 1;
fi
local "s=$(alias "$1")"
s="${s#alias $1=\'}"
s="${s%\'}"
local "f=${s##*common.}"
f="${f%%;*}"
local "v=common_$f"
f="common.$f"
if [[ ${!v:-} != "$1" ]]; then
echo "$(common.GetAlias): $1: Name not paired with a function \`$f'" >&2
return 1;
elif [[ $# -gt 1 ]]; then
eval "alias $2='$s'"
eval "$v=\"$2\""
else
unset "$v"
fi
unalias "$1"
}
common.Alias exception 'common.Exception'
common.Alias throw 'common.Throw'
common.Alias try '{ if common.Try; then'
common.Alias yrt 'common.EchoExitStatus; fi; common.yrT; }'
common.Alias catch '{ while common.Catch'
common.Alias hctac 'common.hctaC -r; done; common.hctaC; }'
common.Alias finally '{ if common.Finally; then'
common.Alias yllanif 'fi; common.yllaniF; }'
common.Alias caught 'common.Caught'
common.Alias EchoExitStatus 'common.EchoExitStatus'
common.Alias EnableThrowOnError 'common.EnableThrowOnError'
common.Alias DisableThrowOnError 'common.DisableThrowOnError'
common.Alias GetStatus 'common.GetStatus'
common.Alias SetStatus 'common.SetStatus'
common.Alias GetMessage 'common.GetMessage'
common.Alias MessageCount 'common.MessageCount'
common.Alias CopyMessages 'common.CopyMessages'
common.Alias TryCatchFinally 'common.TryCatchFinally'
common.Alias DefaultErrHandler 'common.DefaultErrHandler'
common.Alias DefaultUnhandled 'common.DefaultUnhandled'
common.Alias CallStack 'common.CallStack'
common.Alias ChangeAlias 'common.ChangeAlias'
common.Alias TryCatchFinallyAlias 'common.Alias'
common.CallStack() {
local -i "i" "j" "k" "subshell=${2:-0}" "wi" "wl" "wn"
local "format= %*s %*s %-*s %s\n" "name"
eval local "lineno=('' ${BASH_LINENO[@]})"
for (( i=${1:-0},j=wi=wl=wn=0; i<${#FUNCNAME[@]}; ++i,++j )); do
name="$(common.GetAlias "$i")"
let "wi = ${#j} > wi ? wi = ${#j} : wi"
let "wl = ${#lineno[i]} > wl ? wl = ${#lineno[i]} : wl"
let "wn = ${#name} > wn ? wn = ${#name} : wn"
done
for (( i=${1:-0},j=0; i<${#FUNCNAME[@]}; ++i,++j )); do
! let "k = ${#FUNCNAME[@]} - i - 1"
name="$(common.GetAlias "$i")"
printf "$format" "$wi" "$j" "$wl" "${lineno[i]}" "$wn" "$name" "${BASH_SOURCE[i]}"
done
}
common.Echo() {
[[ $common_options != *d* ]] || echo "$@" >"$common_file"
}
common.DefaultErrHandler() {
echo "Orginal Status: $common_status"
echo "Exception Type: ERR"
}
common.Exception() {
common.TryCatchFinallyVerify || return
if [[ $# -eq 0 ]]; then
echo "$(common.GetAlias): At least one parameter is required" >&2
return "1"
elif [[ ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -lt 1 || 10#$1 -gt 255 ]]; then
echo "$(common.GetAlias): $1: First parameter was not an integer between 1 and 255" >&2
return "1"
fi
let "common_status = 10#$1"
shift
common_messages=()
for message in "$@"; do
common_messages+=("$message")
done
if [[ $common_options == *c* ]]; then
echo "Call Stack:" >"$common_fifo"
common.CallStack "2" >"$common_fifo"
fi
}
common.Throw() {
common.TryCatchFinallyVerify || return
local "message"
if ! common.TryCatchFinallyExists; then
echo "$(common.GetAlias): No Try-Catch-Finally exists" >&2
return "1"
elif [[ $# -eq 0 && common_status -eq 0 ]]; then
echo "$(common.GetAlias): No previous unhandled exception" >&2
return "1"
elif [[ $# -gt 0 && ( ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -lt 1 || 10#$1 -gt 255 ) ]]; then
echo "$(common.GetAlias): $1: First parameter was not an integer between 1 and 255" >&2
return "1"
fi
common.Echo -n "In Throw ?=$common_status "
common.Echo "try=$common_trySubshell subshell=$BASH_SUBSHELL #=$#"
if [[ $common_options == *k* ]]; then
common.CallStack "2" >"$common_file"
fi
if [[ $# -gt 0 ]]; then
let "common_status = 10#$1"
shift
for message in "$@"; do
echo "$message" >"$common_fifo"
done
if [[ $common_options == *c* ]]; then
echo "Call Stack:" >"$common_fifo"
common.CallStack "2" >"$common_fifo"
fi
elif [[ ${#common_messages[@]} -gt 0 ]]; then
for message in "${common_messages[@]}"; do
echo "$message" >"$common_fifo"
done
fi
chmod "0400" "$common_fifo"
common.Echo "Still in Throw $=$common_status subshell=$BASH_SUBSHELL #=$# -=$-"
exit "$common_status"
}
common.ErrHandler() {
common_status=$?
trap ERR
common.Echo -n "In ErrHandler ?=$common_status debug=$common_options "
common.Echo "try=$common_trySubshell subshell=$BASH_SUBSHELL order=$common_order"
if [[ -w "$common_fifo" ]]; then
if [[ $common_options != *e* ]]; then
common.Echo "ErrHandler is ignoring"
common_status="0"
return "$common_status" # value is ignored
fi
if [[ $common_options == *k* ]]; then
common.CallStack "2" >"$common_file"
fi
common.Echo "Calling ${common_errHandler:-}"
eval "${common_errHandler:-} \"${BASH_LINENO[0]}\" \"${BASH_SOURCE[1]}\" \"${FUNCNAME[1]}\" >$common_fifo <$common_fifo"
if [[ $common_options == *c* ]]; then
echo "Call Stack:" >"$common_fifo"
common.CallStack "2" >"$common_fifo"
fi
chmod "0400" "$common_fifo"
fi
common.Echo "Still in ErrHandler $=$common_status subshell=$BASH_SUBSHELL -=$-"
if [[ common_trySubshell -eq BASH_SUBSHELL ]]; then
return "$common_status" # value is ignored
else
exit "$common_status"
fi
}
common.Token() {
local "name"
case $1 in
b) name="before";;
t) name="$common_Try";;
y) name="$common_yrT";;
c) name="$common_Catch";;
h) name="$common_hctaC";;
f) name="$common_yllaniF";;
l) name="$common_Finally";;
*) name="unknown";;
esac
echo "$name"
}
common.TryCatchFinallyNext() {
common.ShellInit
local "previous=$common_order" "errmsg"
common_order="$2"
if [[ $previous != $1 ]]; then
errmsg="${BASH_SOURCE[2]}: line ${BASH_LINENO[1]}: syntax error_near unexpected token \`$(common.Token "$2")'"
echo "$errmsg" >&2
[[ /dev/fd/2 -ef $common_file ]] || echo "$errmsg" >"$common_file"
kill -s INT 0
return "1"
fi
}
common.ShellInit() {
if [[ common_initSubshell -ne BASH_SUBSHELL ]]; then
common_initSubshell="$BASH_SUBSHELL"
common_order="b"
fi
}
common.Try() {
common.TryCatchFinallyVerify || return
common.TryCatchFinallyNext "[byhl]" "t" || return
common_status="0"
common_subshell="$common_trySubshell"
common_trySubshell="$BASH_SUBSHELL"
common_messages=()
common.Echo "-------------> Setting try=$common_trySubshell at subshell=$BASH_SUBSHELL"
}
common.yrT() {
local "status=$?"
common.TryCatchFinallyVerify || return
common.TryCatchFinallyNext "[t]" "y" || return
common.Echo -n "Entered yrT ?=$status status=$common_status "
common.Echo "try=$common_trySubshell subshell=$BASH_SUBSHELL"
if [[ common_status -ne 0 ]]; then
common.Echo "Build message array. ?=$common_status, subshell=$BASH_SUBSHELL"
local "message=" "eof=TRY_CATCH_FINALLY_END_OF_MESSAGES_$RANDOM"
chmod "0600" "$common_fifo"
echo "$eof" >"$common_fifo"
common_messages=()
while read "message"; do
common.Echo "----> $message"
[[ $message != *$eof ]] || break
common_messages+=("$message")
done <"$common_fifo"
fi
common.Echo "In ytT status=$common_status"
common_trySubshell="$common_subshell"
}
common.Catch() {
common.TryCatchFinallyVerify || return
common.TryCatchFinallyNext "[yh]" "c" || return
[[ common_status -ne 0 ]] || return "1"
local "parameter" "pattern" "value"
local "toggle=true" "compare=p" "options=$-"
local -i "i=-1" "status=0"
set -f
for parameter in "$@"; do
if "$toggle"; then
toggle="false"
if [[ $parameter =~ ^-[notepr]$ ]]; then
compare="${parameter#-}"
continue
fi
fi
toggle="true"
while "true"; do
eval local "patterns=($parameter)"
if [[ ${#patterns[@]} -gt 0 ]]; then
for pattern in "${patterns[@]}"; do
[[ i -lt ${#common_messages[@]} ]] || break
if [[ i -lt 0 ]]; then
value="$common_status"
else
value="${common_messages[i]}"
fi
case $compare in
[ne]) [[ ! $value == "$pattern" ]] || break 2;;
[op]) [[ ! $value == $pattern ]] || break 2;;
[tr]) [[ ! $value =~ $pattern ]] || break 2;;
esac
done
fi
if [[ $compare == [not] ]]; then
let "++i,1"
continue 2
else
status="1"
break 2
fi
done
if [[ $compare == [not] ]]; then
status="1"
break
else
let "++i,1"
fi
done
[[ $options == *f* ]] || set +f
return "$status"
}
common.hctaC() {
common.TryCatchFinallyVerify || return
common.TryCatchFinallyNext "[c]" "h" || return
[[ $# -ne 1 || $1 != -r ]] || common_status="0"
}
common.Finally() {
common.TryCatchFinallyVerify || return
common.TryCatchFinallyNext "[ych]" "f" || return
}
common.yllaniF() {
common.TryCatchFinallyVerify || return
common.TryCatchFinallyNext "[f]" "l" || return
[[ common_status -eq 0 ]] || common.Throw
}
common.Caught() {
common.TryCatchFinallyVerify || return
[[ common_status -eq 0 ]] || return 1
}
common.EchoExitStatus() {
return "${1:-$?}"
}
common.EnableThrowOnError() {
common.TryCatchFinallyVerify || return
[[ $common_options == *e* ]] || common_options+="e"
}
common.DisableThrowOnError() {
common.TryCatchFinallyVerify || return
common_options="${common_options/e}"
}
common.GetStatus() {
common.TryCatchFinallyVerify || return
echo "$common_status"
}
common.SetStatus() {
common.TryCatchFinallyVerify || return
if [[ $# -ne 1 ]]; then
echo "$(common.GetAlias): $#: Wrong number of parameters" >&2
return "1"
elif [[ ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -lt 1 || 10#$1 -gt 255 ]]; then
echo "$(common.GetAlias): $1: First parameter was not an integer between 1 and 255" >&2
return "1"
fi
let "common_status = 10#$1"
}
common.GetMessage() {
common.TryCatchFinallyVerify || return
local "upper=${#common_messages[@]}"
if [[ upper -eq 0 ]]; then
echo "$(common.GetAlias): $1: There are no messages" >&2
return "1"
elif [[ $# -ne 1 ]]; then
echo "$(common.GetAlias): $#: Wrong number of parameters" >&2
return "1"
elif [[ ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -ge upper ]]; then
echo "$(common.GetAlias): $1: First parameter was an invalid index" >&2
return "1"
fi
echo "${common_messages[$1]}"
}
common.MessageCount() {
common.TryCatchFinallyVerify || return
echo "${#common_messages[@]}"
}
common.CopyMessages() {
common.TryCatchFinallyVerify || return
if [[ $# -ne 1 ]]; then
echo "$(common.GetAlias): $#: Wrong number of parameters" >&2
return "1"
elif [[ ${#common_messages} -gt 0 ]]; then
eval "$1=(\"\${common_messages[@]}\")"
else
eval "$1=()"
fi
}
common.TryCatchFinallyExists() {
[[ ${common_fifo:-u} != u ]]
}
common.TryCatchFinallyVerify() {
local "name"
if ! common.TryCatchFinallyExists; then
echo "$(common.GetAlias "1"): No Try-Catch-Finally exists" >&2
return "2"
fi
}
common.GetOptions() {
local "opt"
local "name=$(common.GetAlias "1")"
if common.TryCatchFinallyExists; then
echo "$name: A Try-Catch-Finally already exists" >&2
return "1"
fi
let "OPTIND = 1"
let "OPTERR = 0"
while getopts ":cdeh:ko:u:v:" opt "$@"; do
case $opt in
c) [[ $common_options == *c* ]] || common_options+="c";;
d) [[ $common_options == *d* ]] || common_options+="d";;
e) [[ $common_options == *e* ]] || common_options+="e";;
h) common_errHandler="$OPTARG";;
k) [[ $common_options == *k* ]] || common_options+="k";;
o) common_file="$OPTARG";;
u) common_unhandled="$OPTARG";;
v) common_command="$OPTARG";;
\?) #echo "Invalid option: -$OPTARG" >&2
echo "$name: Illegal option: $OPTARG" >&2
return "1";;
:) echo "$name: Option requires an argument: $OPTARG" >&2
return "1";;
*) echo "$name: An error occurred while parsing options." >&2
return "1";;
esac
done
shift "$((OPTIND - 1))"
if [[ $# -lt 1 ]]; then
echo "$name: The fifo_file parameter is missing" >&2
return "1"
fi
common_fifo="$1"
if [[ ! -p $common_fifo ]]; then
echo "$name: $1: The fifo_file is not an open FIFO" >&2
return "1"
fi
shift
if [[ $# -lt 1 ]]; then
echo "$name: The function parameter is missing" >&2
return "1"
fi
common_function="$1"
if ! chmod "0600" "$common_fifo"; then
echo "$name: $common_fifo: Can not change file mode to 0600" >&2
return "1"
fi
local "message=" "eof=TRY_CATCH_FINALLY_END_OF_FILE_$RANDOM"
{ echo "$eof" >"$common_fifo"; } 2>"/dev/null"
if [[ $? -ne 0 ]]; then
echo "$name: $common_fifo: Can not write" >&2
return "1"
fi
{ while [[ $message != *$eof ]]; do
read "message"
done <"$common_fifo"; } 2>"/dev/null"
if [[ $? -ne 0 ]]; then
echo "$name: $common_fifo: Can not read" >&2
return "1"
fi
return "0"
}
common.DefaultUnhandled() {
local -i "i"
echo "-------------------------------------------------"
echo "$(common.GetAlias "common.TryCatchFinally"): Unhandeled exception occurred"
echo "Status: $(GetStatus)"
echo "Messages:"
for ((i=0; i<$(MessageCount); i++)); do
echo "$(GetMessage "$i")"
done
echo "-------------------------------------------------"
}
common.TryCatchFinally() {
local "common_file=/dev/fd/2"
local "common_errHandler=common.DefaultErrHandler"
local "common_unhandled=common.DefaultUnhandled"
local "common_options="
local "common_fifo="
local "common_function="
local "common_flags=$-"
local "common_trySubshell=-1"
local "common_initSubshell=-1"
local "common_subshell"
local "common_status=0"
local "common_order=b"
local "common_command="
local "common_messages=()"
local "common_handler=$(trap -p ERR)"
[[ -n $common_handler ]] || common_handler="trap ERR"
common.GetOptions "$@" || return "$?"
shift "$((OPTIND + 1))"
[[ -z $common_command ]] || common_command+="=$"
common_command+='("$common_function" "$@")'
set -E
set +e
trap "common.ErrHandler" ERR
if true; then
common.Try
eval "$common_command"
common.EchoExitStatus
common.yrT
fi
while common.Catch; do
"$common_unhandled" >&2
break
common.hctaC -r
done
common.hctaC
[[ $common_flags == *E* ]] || set +E
[[ $common_flags != *e* ]] || set -e
[[ $common_flags != *f* || $- == *f* ]] || set -f
[[ $common_flags == *f* || $- != *f* ]] || set +f
eval "$common_handler"
return "$((common_status?2:0))"
}
아래는 try/catch/finally
bash에서 구현하는 스크립트의 예입니다 .
이 질문에 대한 다른 답변과 마찬가지로 하위 프로세스를 종료 한 후에 예외를 포착해야합니다.
예제 스크립트는 익명의 fifo를 작성하여 시작합니다. 익명 fifo 는 가장 가까운 블록의 끝 command exception
또는 throw
끝 에서 문자열 메시지를 전달하는 데 사용됩니다 try
. 여기서 메시지는 fifo에서 제거되고 배열 변수에 배치됩니다. 상태는 return
및 exit
명령을 통해 리턴 되고 다른 변수에 배치됩니다. catch
블록 을 입력하려면 이 상태가 0이 아니어야합니다. catch
블록 을 입력하기위한 다른 요구 사항 은 매개 변수로 전달됩니다. catch
블록 끝에 도달하면 상태는 0으로 설정됩니다. finally
블록 의 끝에 도달하고 상태가 여전히 0이 아닌 경우, 메시지와 상태를 포함하는 내재적 스로우가 실행됩니다. 스크립트에는 trycatchfinally
처리되지 않은 예외 처리기가 포함 된 함수 를 호출해야 합니다.
trycatchfinally
명령 구문 은 다음과 같습니다.
trycatchfinally [-cde] [-h ERR_handler] [-k] [-o debug_file] [-u unhandled_handler] [-v variable] fifo function
이 -c
옵션은 호출 스택을 예외 메시지에 추가합니다.
이 -d
옵션은 디버그 출력을 활성화합니다.
이 -e
옵션은 명령 예외를 활성화합니다.
이 -h
옵션을 사용하면 사용자가 자신의 명령 예외 처리기를 대체 할 수 있습니다.
이 -k
옵션은 호출 스택을 디버그 출력에 추가합니다.
이 -o
옵션은 기본 출력 파일 인를 대체합니다 /dev/fd/2
.
이 -u
옵션을 사용하면 사용자가 처리되지 않은 예외 처리기를 대체 할 수 있습니다.
이 -v
옵션을 사용하면 명령 대체를 사용하여 옵션을 다시 전달할 수 있습니다.
는 fifo
FIFO에 파일 이름입니다.
이 함수 function
는 trycatchfinally
하위 프로세스로 호출됩니다 .
참고 :
cdko
스크립트를 단순화하기 위해 옵션이 제거되었습니다.
catch
명령 구문 은 다음과 같습니다.
catch [[-enoprt] list ...] ...
옵션은 아래에 정의되어 있습니다. 첫 번째 목록의 값은 상태입니다. 후속 값은 메시지입니다. 목록보다 많은 메시지가 있으면 나머지 메시지는 무시됩니다.
-e
수단 [[ $value == "$string" ]]
(값리스트의 적어도 하나의 문자열과 일치하는)
-n
수단 [[ $value != "$string" ]]
(값리스트의 문자열과 일치 할 수없는)
-o
수단 [[ $value != $pattern ]]
(값리스트의 패턴과 일치 할 수없는)
-p
수단 [[ $value == $pattern ]]
(값을 갖는다 목록의 적어도 하나 개의 패턴)에 일치하도록
-r
수단 [[ $value =~ $regex ]]
값이 적어도 하나) 목록에서 정규 표현 확장에 일치한다 (
-t
수단 [[ ! $value =~ $regex ]]
(값리스트의 확장 정규식 일치하지 수)
try/catch/finally
스크립트는 아래와 같습니다. 이 답변에 대한 스크립트를 단순화하기 위해 대부분의 오류 검사가 제거되었습니다. 이것은 크기를 64 % 줄였습니다. 이 스크립트의 전체 사본은 다른 답변 에서 찾을 수 있습니다 .
shopt -s expand_aliases
alias try='{ common.Try'
alias yrt='EchoExitStatus; common.yrT; }'
alias catch='{ while common.Catch'
alias hctac='common.hctaC; done; }'
alias finally='{ common.Finally'
alias yllanif='common.yllaniF; }'
DefaultErrHandler() {
echo "Orginal Status: $common_status"
echo "Exception Type: ERR"
}
exception() {
let "common_status = 10#$1"
shift
common_messages=()
for message in "$@"; do
common_messages+=("$message")
done
}
throw() {
local "message"
if [[ $# -gt 0 ]]; then
let "common_status = 10#$1"
shift
for message in "$@"; do
echo "$message" >"$common_fifo"
done
elif [[ ${#common_messages[@]} -gt 0 ]]; then
for message in "${common_messages[@]}"; do
echo "$message" >"$common_fifo"
done
fi
chmod "0400" "$common_fifo"
exit "$common_status"
}
common.ErrHandler() {
common_status=$?
trap ERR
if [[ -w "$common_fifo" ]]; then
if [[ $common_options != *e* ]]; then
common_status="0"
return
fi
eval "${common_errHandler:-} \"${BASH_LINENO[0]}\" \"${BASH_SOURCE[1]}\" \"${FUNCNAME[1]}\" >$common_fifo <$common_fifo"
chmod "0400" "$common_fifo"
fi
if [[ common_trySubshell -eq BASH_SUBSHELL ]]; then
return
else
exit "$common_status"
fi
}
common.Try() {
common_status="0"
common_subshell="$common_trySubshell"
common_trySubshell="$BASH_SUBSHELL"
common_messages=()
}
common.yrT() {
local "status=$?"
if [[ common_status -ne 0 ]]; then
local "message=" "eof=TRY_CATCH_FINALLY_END_OF_MESSAGES_$RANDOM"
chmod "0600" "$common_fifo"
echo "$eof" >"$common_fifo"
common_messages=()
while read "message"; do
[[ $message != *$eof ]] || break
common_messages+=("$message")
done <"$common_fifo"
fi
common_trySubshell="$common_subshell"
}
common.Catch() {
[[ common_status -ne 0 ]] || return "1"
local "parameter" "pattern" "value"
local "toggle=true" "compare=p" "options=$-"
local -i "i=-1" "status=0"
set -f
for parameter in "$@"; do
if "$toggle"; then
toggle="false"
if [[ $parameter =~ ^-[notepr]$ ]]; then
compare="${parameter#-}"
continue
fi
fi
toggle="true"
while "true"; do
eval local "patterns=($parameter)"
if [[ ${#patterns[@]} -gt 0 ]]; then
for pattern in "${patterns[@]}"; do
[[ i -lt ${#common_messages[@]} ]] || break
if [[ i -lt 0 ]]; then
value="$common_status"
else
value="${common_messages[i]}"
fi
case $compare in
[ne]) [[ ! $value == "$pattern" ]] || break 2;;
[op]) [[ ! $value == $pattern ]] || break 2;;
[tr]) [[ ! $value =~ $pattern ]] || break 2;;
esac
done
fi
if [[ $compare == [not] ]]; then
let "++i,1"
continue 2
else
status="1"
break 2
fi
done
if [[ $compare == [not] ]]; then
status="1"
break
else
let "++i,1"
fi
done
[[ $options == *f* ]] || set +f
return "$status"
}
common.hctaC() {
common_status="0"
}
common.Finally() {
:
}
common.yllaniF() {
[[ common_status -eq 0 ]] || throw
}
caught() {
[[ common_status -eq 0 ]] || return 1
}
EchoExitStatus() {
return "${1:-$?}"
}
EnableThrowOnError() {
[[ $common_options == *e* ]] || common_options+="e"
}
DisableThrowOnError() {
common_options="${common_options/e}"
}
GetStatus() {
echo "$common_status"
}
SetStatus() {
let "common_status = 10#$1"
}
GetMessage() {
echo "${common_messages[$1]}"
}
MessageCount() {
echo "${#common_messages[@]}"
}
CopyMessages() {
if [[ ${#common_messages} -gt 0 ]]; then
eval "$1=(\"\${common_messages[@]}\")"
else
eval "$1=()"
fi
}
common.GetOptions() {
local "opt"
let "OPTIND = 1"
let "OPTERR = 0"
while getopts ":cdeh:ko:u:v:" opt "$@"; do
case $opt in
e) [[ $common_options == *e* ]] || common_options+="e";;
h) common_errHandler="$OPTARG";;
u) common_unhandled="$OPTARG";;
v) common_command="$OPTARG";;
esac
done
shift "$((OPTIND - 1))"
common_fifo="$1"
shift
common_function="$1"
chmod "0600" "$common_fifo"
}
DefaultUnhandled() {
local -i "i"
echo "-------------------------------------------------"
echo "TryCatchFinally: Unhandeled exception occurred"
echo "Status: $(GetStatus)"
echo "Messages:"
for ((i=0; i<$(MessageCount); i++)); do
echo "$(GetMessage "$i")"
done
echo "-------------------------------------------------"
}
TryCatchFinally() {
local "common_errHandler=DefaultErrHandler"
local "common_unhandled=DefaultUnhandled"
local "common_options="
local "common_fifo="
local "common_function="
local "common_flags=$-"
local "common_trySubshell=-1"
local "common_subshell"
local "common_status=0"
local "common_command="
local "common_messages=()"
local "common_handler=$(trap -p ERR)"
[[ -n $common_handler ]] || common_handler="trap ERR"
common.GetOptions "$@"
shift "$((OPTIND + 1))"
[[ -z $common_command ]] || common_command+="=$"
common_command+='("$common_function" "$@")'
set -E
set +e
trap "common.ErrHandler" ERR
try
eval "$common_command"
yrt
catch; do
"$common_unhandled" >&2
hctac
[[ $common_flags == *E* ]] || set +E
[[ $common_flags != *e* ]] || set -e
[[ $common_flags != *f* || $- == *f* ]] || set -f
[[ $common_flags == *f* || $- != *f* ]] || set +f
eval "$common_handler"
}
아래는 위의 스크립트가라는 파일에 저장된 것으로 가정 한 예 simple
입니다. makefifo
파일에 설명 된 스크립트 포함 이 답변을 . 명명 된 파일 4444kkkkk
이 존재하지 않는 것으로 가정 하여 예외가 발생합니다. ls 4444kkkkk
명령 에서 출력 된 오류 메시지 는 해당 catch
블록 안에있을 때까지 자동으로 억제됩니다 .
#!/bin/bash
#
if [[ $0 != ${BASH_SOURCE[0]} ]]; then
bash "${BASH_SOURCE[0]}" "$@"
return
fi
source simple
source makefifo
MyFunction3() {
echo "entered MyFunction3" >&4
echo "This is from MyFunction3"
ls 4444kkkkk
echo "leaving MyFunction3" >&4
}
MyFunction2() {
echo "entered MyFunction2" >&4
value="$(MyFunction3)"
echo "leaving MyFunction2" >&4
}
MyFunction1() {
echo "entered MyFunction1" >&4
local "flag=false"
try
(
echo "start of try" >&4
MyFunction2
echo "end of try" >&4
)
yrt
catch "[1-3]" "*" "Exception\ Type:\ ERR"; do
echo 'start of catch "[1-3]" "*" "Exception\ Type:\ ERR"'
local -i "i"
echo "-------------------------------------------------"
echo "Status: $(GetStatus)"
echo "Messages:"
for ((i=0; i<$(MessageCount); i++)); do
echo "$(GetMessage "$i")"
done
echo "-------------------------------------------------"
break
echo 'end of catch "[1-3]" "*" "Exception\ Type:\ ERR"'
hctac >&4
catch "1 3 5" "*" -n "Exception\ Type:\ ERR"; do
echo 'start of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"'
echo "-------------------------------------------------"
echo "Status: $(GetStatus)"
[[ $(MessageCount) -le 1 ]] || echo "$(GetMessage "1")"
echo "-------------------------------------------------"
break
echo 'end of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"'
hctac >&4
catch; do
echo 'start of catch' >&4
echo "failure"
flag="true"
echo 'end of catch' >&4
hctac
finally
echo "in finally"
yllanif >&4
"$flag" || echo "success"
echo "leaving MyFunction1" >&4
} 2>&6
ErrHandler() {
echo "EOF"
DefaultErrHandler "$@"
echo "Function: $3"
while read; do
[[ $REPLY != *EOF ]] || break
echo "$REPLY"
done
}
set -u
echo "starting" >&2
MakeFIFO "6"
TryCatchFinally -e -h ErrHandler -o /dev/fd/4 -v result /dev/fd/6 MyFunction1 4>&2
echo "result=$result"
exec >&6-
위 스크립트는를 사용하여 테스트되었습니다 GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
. 이 스크립트를 실행 한 결과는 다음과 같습니다.
starting
entered MyFunction1
start of try
entered MyFunction2
entered MyFunction3
start of catch "[1-3]" "*" "Exception\ Type:\ ERR"
-------------------------------------------------
Status: 1
Messages:
Orginal Status: 1
Exception Type: ERR
Function: MyFunction3
ls: 4444kkkkk: No such file or directory
-------------------------------------------------
start of catch
end of catch
in finally
leaving MyFunction1
result=failure
아래에 표시된 스크립트로 throw
기능 MyFunction3
을 대체하여 를 사용하는 다른 예를 만들 수 있습니다 .
MyFunction3() {
echo "entered MyFunction3" >&4
echo "This is from MyFunction3"
throw "3" "Orginal Status: 3" "Exception Type: throw"
echo "leaving MyFunction3" >&4
}
throw
명령 구문 은 다음과 같습니다. 매개 변수가 없으면 대신 변수에 저장된 상태 및 메시지가 사용됩니다.
throw [status] [message ...]
수정 된 스크립트를 실행 한 결과는 다음과 같습니다.
starting
entered MyFunction1
start of try
entered MyFunction2
entered MyFunction3
start of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"
-------------------------------------------------
Status: 3
Exception Type: throw
-------------------------------------------------
start of catch
end of catch
in finally
leaving MyFunction1
result=failure