[bash] Bash에서 오류 처리

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 스크립트에서 사용하는 더 나은 오류 처리 루틴이 있습니까?



답변

함정을 사용하십시오!

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로 종료되고 명시적인 메시지가 표시됩니다.


답변

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

set -e

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


답변

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

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

파일 내용 : 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!

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

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


답변

“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와 마찬가지로).


답변

여기에 제시된 아이디어에서 영감을 얻어 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


답변

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

#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


답변

또 다른 고려 사항은 리턴 할 종료 코드입니다. 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코드를 함께 사용하면 스크립트에서 여러 개의 동시 오류를 알릴 수 있습니다.