ฉันกำลังเขียนเชลล์สคริปต์และจำเป็นต้องตรวจสอบว่ามีการติดตั้งแอปเทอร์มินัล ฉันต้องการใช้คำสั่งลอง / จับเพื่อทำสิ่งนี้เว้นแต่จะมีวิธีการที่เหนือกว่า
help test
อาจช่วยให้คุณค้นหาวิธีการแก้ไขปัญหาของคุณ
trap
คำสั่ง
ฉันกำลังเขียนเชลล์สคริปต์และจำเป็นต้องตรวจสอบว่ามีการติดตั้งแอปเทอร์มินัล ฉันต้องการใช้คำสั่งลอง / จับเพื่อทำสิ่งนี้เว้นแต่จะมีวิธีการที่เหนือกว่า
help test
อาจช่วยให้คุณค้นหาวิธีการแก้ไขปัญหาของคุณ
trap
คำสั่ง
คำตอบ:
มีคำสั่ง TRY CATCH ใน Bash หรือไม่?
เลขที่
Bash ไม่มีสินค้าฟุ่มเฟือยมากเท่าที่จะหาได้ในภาษาการเขียนโปรแกรมจำนวนมาก
ไม่มีการtry/catch
ทุบตี แต่หนึ่งสามารถบรรลุพฤติกรรมคล้ายกันโดยใช้หรือ&&
||
การใช้||
:
หากcommand1
ล้มเหลวให้command2
รันดังนี้
command1 || command2
ในทำนองเดียวกันการใช้&&
, command2
จะทำงานถ้าcommand1
ประสบความสำเร็จ
การประมาณที่ใกล้เคียงที่สุดของtry/catch
เป็นดังนี้
{ # try
command1 &&
#save your output
} || { # catch
# save log for exception
}
ทุบตียังมีกลไกการจัดการข้อผิดพลาดบางอย่างเช่นกัน
set -e
มันหยุดสคริปต์ของคุณหากคำสั่งง่ายๆใด ๆ ล้มเหลว
if...else
และยังทำไมไม่ มันเป็นเพื่อนที่ดีที่สุดของคุณ
#save your output
ไม่ล้มเหลวมิฉะนั้นบล็อก "catch" จะยังคงทำงาน
if...else
โครงสร้าง สิ่งนี้หมายความว่าคำสั่ง bash แก้ไขเป็น "ความจริง" หรือไม่หากคำสั่งเหล่านั้นทำงานได้สำเร็จและ "ผิดพลาด" หากล้มเหลว?
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()
หมายถึงการลัดวงจรหรือลองใช้คำสั่งแรกที่ไม่ได้กลับมาจริงและตอนนี้ย้ายไปยังคำสั่งต่อไป &&
จะไม่ทำงานเพราะคำสั่งแรก ( try
) ให้ผลที่ผิดพลาดซึ่งหมายความว่าคำสั่งไม่จำเป็นต้องตามกฎซ้ำซากcatch
false&any equals false
มีเพียงไฟฟ้าลัดวงจรและ / หรือที่จะทำงานทั้งสองอย่าง
ฉันได้พัฒนาการใช้งานลองและจับได้อย่างไร้ที่ติใน bash ซึ่งช่วยให้คุณเขียนโค้ดเช่น:
try
echo 'Hello'
false
echo 'This will not be displayed'
catch
echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
คุณสามารถวางบล็อกบล็อคที่อยู่ภายในตัวเองได้!
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__!"
}
รหัสเป็นส่วนหนึ่งของฉันทุบตีสำเร็จรูป / กรอบ นอกจากนี้ยังขยายแนวคิดของการลองและจับกับสิ่งต่าง ๆ เช่นการจัดการข้อผิดพลาดด้วย backtrace และข้อยกเว้น (รวมถึงคุณสมบัติที่ดีอื่น ๆ )
นี่คือรหัสที่รับผิดชอบเพียงลองและจับ:
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 ของ subshells ได้ แต่นั่นค่อนข้างซับซ้อนและอาจเป็นคำถามอีกข้อหนึ่ง ;-)
การใช้-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 ด้วย-e
อาร์กิวเมนต์หรือใช้set -e
ภายในสคริปต์เพื่อยกเลิกกระบวนการ bash ทั้งหมดหากคำสั่งใด ๆ มีรหัสออกที่ไม่ใช่ศูนย์ (คุณสามารถset +e
อนุญาตคำสั่งที่ล้มเหลวได้ชั่วคราว)
ดังนั้นหนึ่งเทคนิคในการจำลอง try / catch block คือการเรียกใช้กระบวนการย่อยเพื่อทำงานกับการ-e
เปิดใช้งาน จากนั้นในกระบวนการหลักให้ตรวจสอบรหัสส่งคืนของกระบวนการย่อย
Bash รองรับสตริง heredoc ดังนั้นคุณไม่ต้องเขียนไฟล์สองไฟล์แยกกันเพื่อจัดการสิ่งนี้ ในตัวอย่างด้านล่าง heryoc TRY จะทำงานในอินสแตนซ์ bash ที่แยกต่างหากโดย-e
เปิดใช้งานดังนั้นกระบวนการย่อยจะล้มเหลวหากคำสั่งใด ๆ ส่งคืนโค้ดออกที่ไม่ใช่ศูนย์ จากนั้นย้อนกลับไปในกระบวนการหลักเราสามารถตรวจสอบรหัสส่งคืนเพื่อจัดการ catch block
#!/bin/bash
set +e
bash -e <<TRY
echo hello
cd /does/not/exist
echo world
TRY
if [ $? -ne 0 ]; then
echo caught exception
fi
ไม่ใช่บล็อกลอง / catch ที่รองรับภาษา แต่อาจทำให้เป็นรอยคล้าย ๆ กับคุณ
คุณทำได้:
#!/bin/bash
if <command> ; then # TRY
<do-whatever-you-want>
else # CATCH
echo 'Exception'
<do-whatever-you-want>
fi
และคุณมีกับดักhttp://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.htmlซึ่งไม่เหมือนกัน แต่เทคนิคอื่น ๆ ที่คุณสามารถใช้เพื่อจุดประสงค์นี้
สิ่งที่ฉันใช้ง่ายมาก:
try() {
"$@" || (e=$?; echo "$@" > /dev/stderr; exit $e)
}
||
อยู่ใน()
มันจะทำงานใน subshell และออกโดยไม่ทำให้เปลือกหลักเพื่อออก ใช้{ }
การจัดกลุ่มแทน
ด้านล่างนี้เป็นสำเนาที่สมบูรณ์ของสคริปต์ง่ายที่ใช้ในการฉันคำตอบอื่น ๆ นอกเหนือจากการตรวจสอบข้อผิดพลาดเพิ่มเติมมีนามแฝงที่อนุญาตให้ผู้ใช้เปลี่ยนชื่อของนามแฝงที่มีอยู่ ไวยากรณ์ได้รับด้านล่าง หากไม่ระบุ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
ในการทุบตี
เช่นเดียวกับคำตอบอื่น ๆ สำหรับคำถามนี้จะต้องตรวจจับข้อยกเว้นหลังจากออกจากกระบวนการย่อย
ตัวอย่างสคริปต์เริ่มต้นด้วยการสร้าง fifo แบบไม่ระบุชื่อซึ่งใช้เพื่อส่งข้อความสตริงจากcommand exception
หรือthrow
ไปยังจุดสิ้นสุดของtry
บล็อกที่ใกล้เคียงที่สุด ที่นี่ข้อความจะถูกลบออกจาก Fifo และวางไว้ในตัวแปรอาร์เรย์ สถานะจะถูกส่งกลับผ่านreturn
และexit
คำสั่งและวางไว้ในตัวแปรที่แตกต่างกัน ในการเข้าสู่catch
บล็อกสถานะนี้ต้องไม่เป็นศูนย์ ข้อกำหนดอื่น ๆ เพื่อเข้าสู่catch
บล็อกจะถูกส่งเป็นพารามิเตอร์ หากถึงจุดสิ้นสุดของcatch
บล็อกแล้วสถานะจะถูกตั้งค่าเป็นศูนย์ หากถึงจุดสิ้นสุดของfinally
บล็อกและสถานะยังคงเป็นศูนย์ดังนั้นการโยนโดยนัยที่มีข้อความและสถานะจะถูกดำเนินการ สคริปต์ต้องการการเรียกใช้ฟังก์ชันtrycatchfinally
ที่มีตัวจัดการข้อยกเว้นที่ไม่สามารถจัดการได้
ไวยากรณ์สำหรับtrycatchfinally
คำสั่งได้รับด้านล่าง
trycatchfinally [-cde] [-h ERR_handler] [-k] [-o debug_file] [-u unhandled_handler] [-v variable] fifo function
-c
ตัวเลือกเพิ่มสาย stack กับข้อความข้อยกเว้น ตัวเลือกที่ช่วยให้การแก้ปัญหาส่งออก ตัวเลือกที่ช่วยให้ข้อยกเว้นคำสั่ง ตัวเลือกที่ช่วยให้ผู้ใช้เพื่อทดแทนการจัดการข้อยกเว้นคำสั่งของตัวเอง ตัวเลือกเพิ่มสแต็คการเรียกร้องให้แก้ปัญหาการส่งออก ตัวเลือกแทนที่ไฟล์ที่ส่งออกเริ่มต้นซึ่งเป็น ตัวเลือกที่ช่วยให้ผู้ใช้เพื่อทดแทนของตัวเองที่ไม่สามารถจัดการจัดการข้อยกเว้นของพวกเขา ตัวเลือกที่ช่วยให้ผู้ใช้สามารถเลือกที่จะส่งค่ากลับมาแม้ว่าการใช้คำสั่งเปลี่ยนตัว เป็นชื่อไฟล์ของ FIFO
ฟังก์ชั่นถูกเรียกโดยเป็นกระบวนการย่อย-d
-e
-h
-k
-o
/dev/fd/2
-u
-v
fifo
function
trycatchfinally
หมายเหตุ:
cdko
ตัวเลือกจะถูกลบออกเพื่อทำให้สคริปต์ง่ายขึ้น
ไวยากรณ์สำหรับcatch
คำสั่งได้รับด้านล่าง
catch [[-enoprt] list ...] ...
ตัวเลือกมีการกำหนดไว้ด้านล่าง ค่าสำหรับรายการแรกคือสถานะ ค่า Subsquent เป็นข้อความ หากมีข้อความมากกว่ารายการแสดงว่าข้อความที่เหลือจะถูกละเว้น
-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
อีกตัวอย่างที่ใช้ a 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