Bash에서 오류 처리


240

Bash에서 오류를 처리하는 가장 좋아하는 방법은 무엇입니까? 웹에서 찾은 오류를 처리하는 가장 좋은 예는 William Shotts, Jr ( http://www.linuxcommand.org) 가 작성했습니다 .

그는 Bash에서 오류 처리를 위해 다음 기능을 사용할 것을 제안합니다.

#!/bin/bash

# A slicker error handling routine

# I put a variable in my scripts named PROGNAME which
# holds the name of the program being run.  You can get this
# value from the first item on the command line ($0).

# Reference: This was copied from <http://www.linuxcommand.org/wss0150.php>

PROGNAME=$(basename $0)

function error_exit
{

#   ----------------------------------------------------------------
#   Function for exit due to fatal program error
#       Accepts 1 argument:
#           string containing descriptive error message
#   ---------------------------------------------------------------- 

    echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2
    exit 1
}

# Example call of the error_exit function.  Note the inclusion
# of the LINENO environment variable.  It contains the current
# line number.

echo "Example of error with line number and message"
error_exit "$LINENO: An error has occurred."

Bash 스크립트에서 사용하는 더 나은 오류 처리 루틴이 있습니까?


1
이 자세한 답변 : Bash 스크립트에서 오류 발생을 참조하십시오 .
codeforester

1
여기에 로깅 및 오류 처리 구현을 참조하십시오 github.com/codeforester/base/blob/master/lib/stdlib.sh을
codeforester

답변:


154

함정을 사용하십시오!

tempfiles=( )
cleanup() {
  rm -f "${tempfiles[@]}"
}
trap cleanup 0

error() {
  local parent_lineno="$1"
  local message="$2"
  local code="${3:-1}"
  if [[ -n "$message" ]] ; then
    echo "Error on or near line ${parent_lineno}: ${message}; exiting with status ${code}"
  else
    echo "Error on or near line ${parent_lineno}; exiting with status ${code}"
  fi
  exit "${code}"
}
trap 'error ${LINENO}' ERR

... 그러면 임시 파일을 만들 때마다 :

temp_foo="$(mktemp -t foobar.XXXXXX)"
tempfiles+=( "$temp_foo" )

$temp_foo종료시 삭제되고 현재 행 번호가 인쇄됩니다. ( 심각한 경고가 발생 하고 코드의 예측 가능성과 이식성이 약화 되지만set -e 오류 발생시 종료 동작 을 제공 합니다).

트랩이 사용자를 호출하도록하거나 error(이 경우 기본 종료 코드 1을 사용하고 메시지를 사용하지 않음) 직접 호출하여 명시적인 값을 제공 할 수 있습니다. 예를 들어 :

error ${LINENO} "the foobar failed" 2

상태 2로 종료되고 명시적인 메시지가 표시됩니다.


4
@draemon 변수 대문자는 의도적입니다. 모든 대문자는 쉘 내장 및 환경 변수에만 적용되며 다른 모든 것에 소문자를 사용하면 네임 스페이스 충돌이 방지됩니다. 또한 참조 stackoverflow.com/questions/673055/...
찰스 더피에게

1
다시 중단하기 전에 변경 사항을 테스트하십시오. 컨벤션은 좋은 것이지만 작동하는 코드보다 부차적입니다.
Draemon 2016 년

3
@Draemon, 나는 실제로 동의하지 않습니다. 분명히 깨진 코드가 눈에 띄고 수정됩니다. 나쁜 습관이지만 대부분 작동하는 코드는 영원히 살며 전파됩니다.
Charles Duffy

1
그러나 당신은 눈치 채지 못했습니다. 기능 코드가 주요 관심사 이므로 깨진 코드가 표시 됩니다.
Draemon

5
정확히 무사하지는 않지만 ( stackoverflow.com/a/10927223/26334 ) 코드가 이미 POSIX와 호환되지 않는 경우 함수 키워드를 제거해도 POSIX sh에서 더 이상 실행할 수는 없지만 내 주요 요점은 ' ve (IMO)는 set -e 사용 권장 사항을 약화시켜 답변의 가치를 떨어 뜨 렸습니다. Stackoverflow는 "귀하의"코드가 아니라 최상의 답변을 얻는 것입니다.
Draemon

123

좋은 해결책입니다. 방금 추가하고 싶었습니다

set -e

기본적인 오류 메커니즘으로. 간단한 명령이 실패하면 스크립트가 즉시 중지됩니다. 나는 이것이 기본 동작이어야한다고 생각합니다. 그러한 오류는 거의 항상 예기치 않은 것을 의미하기 때문에 다음 명령을 계속 실행하는 것이 실제로 '정치적인'것은 아닙니다.


29
set -e문제 없음 : mywiki.wooledge.org/BashFAQ/105 를 참조하십시오 .
찰스 더피

3
@CharlesDuffy, 몇 가지 문제를 극복 할 수 있습니다set -o pipefail
호브

7
@CharlesDuffy 문제를 지적 해 주셔서 감사합니다. 그래도 전반적으로 여전히 set -e이익-비용 비율이 높다고 생각 합니다.
Bruno De Fraine

3
@BrunoDeFraine 나는 set -e나 자신을 사용 하지만 irc.freenode.org # bash의 다른 많은 규칙은 그것에 대해 (강한 용어로) 조언합니다. 최소한 문제는 잘 이해해야합니다.
Charles Duffy

3
-e -o pipefail -u #를 설정하고 무엇을하는지 알아라
Sam Watkins

78

이 페이지의 모든 답변을 읽으면 많은 영감을 얻었습니다.

그래서 여기 내 힌트가 있습니다 :

파일 내용 : lib.trap.sh

lib_name='trap'
lib_version=20121026

stderr_log="/dev/shm/stderr.log"

#
# TO BE SOURCED ONLY ONCE:
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

if test "${g_libs[$lib_name]+_}"; then
    return 0
else
    if test ${#g_libs[@]} == 0; then
        declare -A g_libs
    fi
    g_libs[$lib_name]=$lib_version
fi


#
# MAIN CODE:
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

set -o pipefail  # trace ERR through pipes
set -o errtrace  # trace ERR through 'time command' and other functions
set -o nounset   ## set -u : exit the script if you try to use an uninitialised variable
set -o errexit   ## set -e : exit the script if any statement returns a non-true return value

exec 2>"$stderr_log"


###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
#
# FUNCTION: EXIT_HANDLER
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

function exit_handler ()
{
    local error_code="$?"

    test $error_code == 0 && return;

    #
    # LOCAL VARIABLES:
    # ------------------------------------------------------------------
    #    
    local i=0
    local regex=''
    local mem=''

    local error_file=''
    local error_lineno=''
    local error_message='unknown'

    local lineno=''


    #
    # PRINT THE HEADER:
    # ------------------------------------------------------------------
    #
    # Color the output if it's an interactive terminal
    test -t 1 && tput bold; tput setf 4                                 ## red bold
    echo -e "\n(!) EXIT HANDLER:\n"


    #
    # GETTING LAST ERROR OCCURRED:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    #
    # Read last file from the error log
    # ------------------------------------------------------------------
    #
    if test -f "$stderr_log"
        then
            stderr=$( tail -n 1 "$stderr_log" )
            rm "$stderr_log"
    fi

    #
    # Managing the line to extract information:
    # ------------------------------------------------------------------
    #

    if test -n "$stderr"
        then        
            # Exploding stderr on :
            mem="$IFS"
            local shrunk_stderr=$( echo "$stderr" | sed 's/\: /\:/g' )
            IFS=':'
            local stderr_parts=( $shrunk_stderr )
            IFS="$mem"

            # Storing information on the error
            error_file="${stderr_parts[0]}"
            error_lineno="${stderr_parts[1]}"
            error_message=""

            for (( i = 3; i <= ${#stderr_parts[@]}; i++ ))
                do
                    error_message="$error_message "${stderr_parts[$i-1]}": "
            done

            # Removing last ':' (colon character)
            error_message="${error_message%:*}"

            # Trim
            error_message="$( echo "$error_message" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//' )"
    fi

    #
    # GETTING BACKTRACE:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
    _backtrace=$( backtrace 2 )


    #
    # MANAGING THE OUTPUT:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    local lineno=""
    regex='^([a-z]{1,}) ([0-9]{1,})$'

    if [[ $error_lineno =~ $regex ]]

        # The error line was found on the log
        # (e.g. type 'ff' without quotes wherever)
        # --------------------------------------------------------------
        then
            local row="${BASH_REMATCH[1]}"
            lineno="${BASH_REMATCH[2]}"

            echo -e "FILE:\t\t${error_file}"
            echo -e "${row^^}:\t\t${lineno}\n"

            echo -e "ERROR CODE:\t${error_code}"             
            test -t 1 && tput setf 6                                    ## white yellow
            echo -e "ERROR MESSAGE:\n$error_message"


        else
            regex="^${error_file}\$|^${error_file}\s+|\s+${error_file}\s+|\s+${error_file}\$"
            if [[ "$_backtrace" =~ $regex ]]

                # The file was found on the log but not the error line
                # (could not reproduce this case so far)
                # ------------------------------------------------------
                then
                    echo -e "FILE:\t\t$error_file"
                    echo -e "ROW:\t\tunknown\n"

                    echo -e "ERROR CODE:\t${error_code}"
                    test -t 1 && tput setf 6                            ## white yellow
                    echo -e "ERROR MESSAGE:\n${stderr}"

                # Neither the error line nor the error file was found on the log
                # (e.g. type 'cp ffd fdf' without quotes wherever)
                # ------------------------------------------------------
                else
                    #
                    # The error file is the first on backtrace list:

                    # Exploding backtrace on newlines
                    mem=$IFS
                    IFS='
                    '
                    #
                    # Substring: I keep only the carriage return
                    # (others needed only for tabbing purpose)
                    IFS=${IFS:0:1}
                    local lines=( $_backtrace )

                    IFS=$mem

                    error_file=""

                    if test -n "${lines[1]}"
                        then
                            array=( ${lines[1]} )

                            for (( i=2; i<${#array[@]}; i++ ))
                                do
                                    error_file="$error_file ${array[$i]}"
                            done

                            # Trim
                            error_file="$( echo "$error_file" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//' )"
                    fi

                    echo -e "FILE:\t\t$error_file"
                    echo -e "ROW:\t\tunknown\n"

                    echo -e "ERROR CODE:\t${error_code}"
                    test -t 1 && tput setf 6                            ## white yellow
                    if test -n "${stderr}"
                        then
                            echo -e "ERROR MESSAGE:\n${stderr}"
                        else
                            echo -e "ERROR MESSAGE:\n${error_message}"
                    fi
            fi
    fi

    #
    # PRINTING THE BACKTRACE:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    test -t 1 && tput setf 7                                            ## white bold
    echo -e "\n$_backtrace\n"

    #
    # EXITING:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    test -t 1 && tput setf 4                                            ## red bold
    echo "Exiting!"

    test -t 1 && tput sgr0 # Reset terminal

    exit "$error_code"
}
trap exit_handler EXIT                                                  # ! ! ! TRAP EXIT ! ! !
trap exit ERR                                                           # ! ! ! TRAP ERR ! ! !


###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
#
# FUNCTION: BACKTRACE
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

function backtrace
{
    local _start_from_=0

    local params=( "$@" )
    if (( "${#params[@]}" >= "1" ))
        then
            _start_from_="$1"
    fi

    local i=0
    local first=false
    while caller $i > /dev/null
    do
        if test -n "$_start_from_" && (( "$i" + 1   >= "$_start_from_" ))
            then
                if test "$first" == false
                    then
                        echo "BACKTRACE IS:"
                        first=true
                fi
                caller $i
        fi
        let "i=i+1"
    done
}

return 0



사용 예 :
파일 내용 : trap-test.sh

#!/bin/bash

source 'lib.trap.sh'

echo "doing something wrong now .."
echo "$foo"

exit 0


달리는:

bash trap-test.sh

산출:

doing something wrong now ..

(!) EXIT HANDLER:

FILE:       trap-test.sh
LINE:       6

ERROR CODE: 1
ERROR MESSAGE:
foo:   unassigned variable

BACKTRACE IS:
1 main trap-test.sh

Exiting!


아래 스크린 샷에서 볼 수 있듯이 출력 색상이 지정되고 오류 메시지가 사용 된 언어로 제공됩니다.

여기에 이미지 설명을 입력하십시오


3
이 일은 굉장합니다. github 프로젝트를 만들어야 사람들이 쉽게 개선하고 기여할 수 있습니다. 나는 그것을 log4bash와 결합하고 함께 좋은 bash 스크립트를 만들기위한 강력한 환경을 만듭니다.
도미니크 도른

1
참고 test ${#g_libs[@]} == 0-POSIX와 호환되지 않습니다 (POSIX 테스트는 =문자열 비교 또는 -eq숫자 비교를 지원하지만 ==POSIX의 배열 부족을 언급 하지는 않음 ). POSIX를 준수 하지 않으려는 이유는 무엇입니까? 세상은 test수학 컨텍스트가 아닌 전혀 사용 하고 있습니까? (( ${#g_libs[@]} == 0 ))결국, 읽기가 더 쉽습니다.
Charles Duffy

2
@ 루카-이것은 정말 대단합니다! 당신의 사진은 나 자신의 구현을 만들도록 영감을주었습니다. 아래 답변에 게시했습니다 .
niieani

3
브라 비스 시모 !! 이것은 스크립트를 디버깅하는 훌륭한 방법입니다. Grazie mille 내가 추가 한 유일한 것은 다음과 같이 OS X를 확인하는 것입니다. case "$(uname)" in Darwin ) stderr_log="${TMPDIR}stderr.log";; Linux ) stderr_log="/dev/shm/stderr.log";; * ) stderr_log="/dev/shm/stderr.log" ;; esac
SaxDaddy

1
약간의 뻔뻔한 자체 플러그 인이지만이 스 니펫을 가져 와서 정리하고 더 많은 기능을 추가하고 출력 형식을 향상 시켰으며 POSIX와 호환되도록했습니다 (Linux 및 OSX 모두에서 작동). Github에서 Privex ShellCore의 일부로 게시되었습니다 : github.com/Privex/shell-core
Someguy123

22

"set -e"와 동등한 대안은 다음과 같습니다.

set -o errexit

플래그의 의미는 "-e"보다 다소 명확합니다.

무작위 추가 : 플래그를 일시적으로 비활성화하고 기본값 (종료 코드에 관계없이 계속 실행)으로 돌아가려면 다음을 사용하십시오.

set +e
echo "commands run here returning non-zero exit codes will not cause the entire script to fail"
echo "false returns 1 as an exit code"
false
set -e

이것은 다른 응답에서 언급 된 적절한 오류 처리를 배제하지만 신속하고 효과적입니다 (bash와 마찬가지로).


1
$(foo)단지 foo잘못된 것 보다는 맨손으로 사용 하는 것이 보통 잘못된 것입니다. 왜 예를 들어서 홍보해야합니까?
Charles Duffy

20

여기에 제시된 아이디어에서 영감을 얻어 bash 상용구 프로젝트 에서 bash 스크립트의 오류를 처리하는 읽기 쉽고 편리한 방법을 개발했습니다 .

라이브러리를 소싱하면 다음과 같은 결과를 즉시 얻을 수 있습니다 (즉 set -e, trapon ERR및 일부 bash-fu 덕분에 오류가 발생하면 실행이 중단됩니다 ).

bash-oo-framework 오류 처리

try 및 catch 또는 throw 키워드 와 같이 오류를 처리하는 데 도움이되는 몇 가지 추가 기능이있어 한 시점에서 실행을 중단하여 역 추적을 볼 수 있습니다. 또한 단말기가 지원하는 경우 전력선 이모지를 뱉어 내고 출력의 일부를 색칠하여 가독성을 높이고 코드 라인의 맥락에서 예외를 일으킨 방법에 밑줄을 긋습니다.

단점은-이식성이 없습니다-코드는 bash에서 작동하며 아마도 = 4 이상 일 것입니다 (그러나 3을 bash하기 위해 약간의 노력으로 이식 될 수 있다고 상상할 것입니다).

코드는 더 나은 처리를 위해 여러 파일로 분리되어 있지만 Luca Borrione의 위 답변 .

자세한 내용을 읽거나 소스를 살펴 보려면 GitHub를 참조하십시오.

https://github.com/niieani/bash-oo-framework#error-handling-with-exceptions-and-throw


이것은 Bash Object Oriented Framework 프로젝트 안에 있습니다. ... 다행히도 GLOC 에 따르면 7.4k LOC 만 있습니다 . OOP-객체 지향 통증?
ingyhere

@ingy 여기서는 모듈 식이며 삭제하기 쉬우므로 예외 부분 만 사용할 수 있습니다.)
niieani

11

정말 전화하기 쉬운 것을 선호합니다. 그래서 조금 복잡해 보이지만 사용하기 쉬운 것을 사용합니다. 나는 보통 아래 코드를 복사하여 스크립트에 붙여 넣습니다. 코드 뒤에 설명이 있습니다.

#This function is used to cleanly exit any script. It does this displaying a
# given error message, and exiting with an error code.
function error_exit {
    echo
    echo "$@"
    exit 1
}
#Trap the killer signals so that we can exit with a good message.
trap "error_exit 'Received signal SIGHUP'" SIGHUP
trap "error_exit 'Received signal SIGINT'" SIGINT
trap "error_exit 'Received signal SIGTERM'" SIGTERM

#Alias the function so that it will print a message with the following format:
#prog-name(@line#): message
#We have to explicitly allow aliases, we do this because they make calling the
#function much easier (see example).
shopt -s expand_aliases
alias die='error_exit "Error ${0}(@`echo $(( $LINENO - 1 ))`):"'

나는 일반적으로 error_exit 함수와 함께 클린업 함수를 호출했지만 스크립트마다 다릅니다. 트랩은 일반적인 종료 신호를 포착하여 모든 것이 정리되도록합니다. 별명은 진짜 마술을하는 것입니다. 모든 것이 고장인지 확인하고 싶습니다. 그래서 일반적으로 나는 "if!" 유형 진술. 행 번호에서 1을 빼면 별명은 실패가 발생한 위치를 알려줍니다. 전화하는 것도 간단하고 바보 증거입니다. 아래는 예입니다 (/ bin / false를 호출하려는 것으로 바꾸십시오).

#This is an example useage, it will print out
#Error prog-name (@1): Who knew false is false.
if ! /bin/false ; then
    die "Who knew false is false."
fi

2
"명시 적으로 별칭을 허용해야 합니다 " 라는 문장을 확장 할 수 있습니까 ? 예기치 않은 동작이 발생할 수 있습니다. 더 적은 영향으로 동일한 것을 달성 할 수있는 방법이 있습니까?
blong

나는 필요성을 해달라고 $LINENO - 1. 그것없이 올바르게 표시하십시오.
kyb

bash 및 zsh의 짧은 사용 예false || die "hello death"
kyb

6

또 다른 고려 사항은 리턴 할 종료 코드입니다. bash 자체가 사용1 하는 소수의 예약 된 종료 코드 가 있지만 " "는 꽤 표준입니다. 동일한 페이지에서 C / C ++ 표준을 준수하려면 사용자 정의 코드가 64-113 범위에 있어야한다고 주장합니다.

mount종료 코드에 사용 하는 비트 벡터 접근 방식을 고려할 수도 있습니다 .

 0  success
 1  incorrect invocation or permissions
 2  system error (out of memory, cannot fork, no more loop devices)
 4  internal mount bug or missing nfs support in mount
 8  user interrupt
16  problems writing or locking /etc/mtab
32  mount failure
64  some mount succeeded

OR코드를 함께 사용하면 스크립트에서 여러 개의 동시 오류를 알릴 수 있습니다.


4

다음 트랩 코드를 사용하며 파이프 및 '시간'명령을 통해 오류를 추적 할 수도 있습니다.

#!/bin/bash
set -o pipefail  # trace ERR through pipes
set -o errtrace  # trace ERR through 'time command' and other functions
function error() {
    JOB="$0"              # job name
    LASTLINE="$1"         # line of error occurrence
    LASTERR="$2"          # error code
    echo "ERROR in ${JOB} : line ${LASTLINE} with exit code ${LASTERR}"
    exit 1
}
trap 'error ${LINENO} ${?}' ERR

5
function키워드는 무상 POSIX 호환되지 않는 것입니다. 전에 선언 error() {하지 말고 선언 function하십시오.
Charles Duffy

5
${$?}단지해야한다 $?, 또는 ${?}불필요한 괄호를 사용하여 주장하는 경우; 내부 $가 잘못되었습니다.
Charles Duffy

3
지금 쯤 @CharlesDuffy는 POSIX는 (여전히, 당신의 점을) 무상 GNU / 리눅스 호환되지 않습니다
Croad Langshan

3

나는 사용했다

die() {
        echo $1
        kill $$
}

전에; 나는 '종료'가 어떤 이유로 나를 위해 실패했기 때문에 생각합니다. 위의 기본값은 좋은 생각처럼 보입니다.


오류 메시지를 STDERR로 보내는 것이 더 낫습니다.
ankostis

3

이것은 지금 잠시 동안 나에게 잘 봉사했습니다. 매개 변수 당 한 줄씩 오류 또는 경고 메시지를 빨간색으로 인쇄하고 선택적인 종료 코드를 허용합니다.

# Custom errors
EX_UNKNOWN=1

warning()
{
    # Output warning messages
    # Color the output red if it's an interactive terminal
    # @param $1...: Messages

    test -t 1 && tput setf 4

    printf '%s\n' "$@" >&2

    test -t 1 && tput sgr0 # Reset terminal
    true
}

error()
{
    # Output error messages with optional exit code
    # @param $1...: Messages
    # @param $N: Exit code (optional)

    messages=( "$@" )

    # If the last parameter is a number, it's not part of the messages
    last_parameter="${messages[@]: -1}"
    if [[ "$last_parameter" =~ ^[0-9]*$ ]]
    then
        exit_code=$last_parameter
        unset messages[$((${#messages[@]} - 1))]
    fi

    warning "${messages[@]}"

    exit ${exit_code:-$EX_UNKNOWN}
}

3

이것이 당신에게 도움이되는지 확실하지 않지만, 오류 확인 (이전 명령의 종료 코드)을 포함시키기 위해 여기에 제안 된 기능 중 일부를 수정했습니다. 각 "체크"에서 로깅 목적으로 오류가 무엇인지 "메시지"를 매개 변수로 전달합니다.

#!/bin/bash

error_exit()
{
    if [ "$?" != "0" ]; then
        log.sh "$1"
        exit 1
    fi
}

이제 동일한 스크립트 내에서 (또는 사용하는 경우 다른 스크립트에서) 호출 export -f error_exit하려면 함수의 이름을 작성하고 다음과 같이 메시지를 매개 변수로 전달하면됩니다.

#!/bin/bash

cd /home/myuser/afolder
error_exit "Unable to switch to folder"

rm *
error_exit "Unable to delete all files"

이것을 사용하여 자동화 된 프로세스를 위해 정말 강력한 bash 파일을 만들 수 있었고 오류가 발생하면 멈추고 나에게 알릴 것입니다 ( log.sh그렇게 할 것입니다)


2
function키워드 를 정의하지 않고 함수를 정의하기 위해 POSIX 구문을 사용해보십시오 error_exit() {.
Charles Duffy

2
왜하지 않는 이유가 cd /home/myuser/afolder || error_exit "Unable to switch to folder"있습니까?
Pierre-Olivier Vares

@ Pierre-OlivierVares ||를 사용하지 않을 특별한 이유가 없습니다. 이것은 기존 코드에서 발췌 한 것이며 각 행 뒤에 "오류 처리"행을 추가했습니다. 일부는 매우 길어서 별도의 (즉시) 라인에있는 것이 더 깨끗했습니다
Nelson Rodriguez


1

이 트릭은 누락 된 명령 또는 기능에 유용합니다. 누락 된 함수 (또는 실행 파일)의 이름은 $ _에 전달됩니다.

function handle_error {
    status=$?
    last_call=$1

    # 127 is 'command not found'
    (( status != 127 )) && return

    echo "you tried to call $last_call"
    return
}

# Trap errors.
trap 'handle_error "$_"' ERR

하지 않을까요 $_과 같은 기능에서 사용할 수 있습니다 $?? 함수에서 하나를 사용해야하는 이유는 확실하지 않지만 다른 하나는 사용하지 않아야합니다.
ingyhere

1

이 기능은 최근에 저에게 다소 도움이되었습니다.

action () {
    # Test if the first parameter is non-zero
    # and return straight away if so
    if test $1 -ne 0
    then
        return $1
    fi

    # Discard the control parameter
    # and execute the rest
    shift 1
    "$@"
    local status=$?

    # Test the exit status of the command run
    # and display an error message on failure
    if test ${status} -ne 0
    then
        echo Command \""$@"\" failed >&2
    fi

    return ${status}
}

실행할 명령 이름에 0 또는 마지막 반환 값을 추가하여 호출하므로 오류 값을 확인하지 않고도 명령을 연결할 수 있습니다. 이를 통해이 문장은 다음을 차단합니다.

command1 param1 param2 param3...
command2 param1 param2 param3...
command3 param1 param2 param3...
command4 param1 param2 param3...
command5 param1 param2 param3...
command6 param1 param2 param3...

이것이된다 :

action 0 command1 param1 param2 param3...
action $? command2 param1 param2 param3...
action $? command3 param1 param2 param3...
action $? command4 param1 param2 param3...
action $? command5 param1 param2 param3...
action $? command6 param1 param2 param3...

<<<Error-handling code here>>>

명령 중 하나라도 실패하면 오류 코드는 단순히 블록의 끝으로 전달됩니다. 이전 명령이 실패한 경우 후속 명령을 실행하지 않으려는 경우에도 유용하지만 스크립트가 바로 종료되는 것을 원하지 않습니다 (예 : 루프 내부).


0

트랩을 사용하는 것이 항상 옵션은 아닙니다. 예를 들어, 오류 처리가 필요하고 스크립트에서 호출 할 수있는 일종의 재사용 가능한 함수를 작성하는 경우 (도우미 함수로 파일을 소싱 한 후), 해당 함수는 외부 스크립트의 종료 시간에 대해 아무 것도 가정 할 수 없습니다. 함정 사용이 매우 어렵습니다. 트랩을 사용하는 또 다른 단점은 호출자 체인에서 이전에 설정되었을 수있는 이전 트랩을 덮어 쓸 위험이 있으므로 구성 성이 나쁘다는 것입니다.

트랩없이 적절한 오류 처리를 수행하는 데 사용할 수있는 약간의 트릭이 있습니다. 이미 다른 답변에서 알 수 있듯이 하위 쉘에서 실행하더라도 연산자 뒤에 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할 것은 명령이 서브 쉘에서 실행되기 때문에 전달한 명령에서 수행 된 쉘 변수의 모든 수정 사항이 호출 함수로 전파되지 않는다는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.