बैश में त्रुटि से निपटने


239

बैश में त्रुटियों को संभालने के लिए आपकी पसंदीदा विधि क्या है? वेब पर मुझे मिलने वाली त्रुटियों से निपटने का सबसे अच्छा उदाहरण विलियम शॉट्ट्स, जूनियर द्वारा http://www.linuxcommand.org पर लिखा गया था ।

वह बश में त्रुटि से निपटने के लिए निम्नलिखित फ़ंक्शन का उपयोग करने का सुझाव देता है:

#!/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."

क्या आपके पास बैश स्क्रिप्ट में उपयोग की जाने वाली दिनचर्या को संभालने में एक बेहतर त्रुटि है?


1
इस विस्तृत उत्तर को देखें: बैश स्क्रिप्ट में त्रुटि उठाएँ
कोडवर्ड

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
@ चर पूंजीकरण जानबूझकर है। ऑल-कैप केवल शेल बिल्डिंग्स और पर्यावरण चर के लिए पारंपरिक है - बाकी सभी चीज़ों के लिए लोअरकेस का उपयोग करने से नेमस्पेस संघर्षों को रोकता है। यह भी देखें stackoverflow.com/questions/673055/...
चार्ल्स डफी

1
इससे पहले कि आप इसे फिर से तोड़ दें, अपने बदलाव का परीक्षण करें। कन्वेंशन एक अच्छी बात है, लेकिन वे कामकाजी कोड के लिए माध्यमिक हैं।
ड्रेमन

3
@Draemon, मैं वास्तव में असहमत हूं। स्पष्ट रूप से टूटे हुए कोड पर ध्यान दिया जाता है और ठीक किया जाता है। बुरे व्यवहार लेकिन ज्यादातर काम करने वाला कोड हमेशा के लिए रहता है (और प्रचारित हो जाता है)।
चार्ल्स डफी

1
लेकिन आपने ध्यान नहीं दिया। टूटे हुए कोड पर ध्यान जाता है क्योंकि कामकाज कोड प्राथमिक चिंता है।
डेमॉन

5
यह बिल्कुल गंभीर नहीं है ( stackoverflow.com/a/10927223/26334 ) और यदि कोड पहले से ही POSIX के साथ असंगत है, तो फ़ंक्शन कीवर्ड को हटाने से यह POSIX श के तहत चलने में अधिक सक्षम नहीं होता है, लेकिन मुख्य बिंदु यह था कि आप ' ve (IMO) ने सेट -e का उपयोग करने की सिफारिश को कमजोर करके उत्तर का अवमूल्यन किया। Stackoverflow "अपने" कोड के बारे में नहीं है, यह सबसे अच्छा जवाब होने के बारे में है।
ड्रेमन

123

यह एक अच्छा समाधान है। मैं सिर्फ जोड़ना चाहता था

set -e

एक अल्पविकसित त्रुटि तंत्र के रूप में। यदि एक साधारण आदेश विफल रहता है तो यह तुरंत आपकी स्क्रिप्ट को रोक देगा। मुझे लगता है कि यह डिफ़ॉल्ट व्यवहार होना चाहिए था: चूंकि ऐसी त्रुटियां लगभग हमेशा किसी अप्रत्याशित चीज को दर्शाती हैं, यह वास्तव में निम्नलिखित कमांड को निष्पादित करने के लिए 'समझदार' नहीं है।


29
set -eगोच के बिना नहीं है: कई के लिए mywiki.wooledge.org/BashFAQ/105 देखें ।
चार्ल्स डफी

3
@CharlesDuffy, कुछ गमछा को काबू में किया जा सकता हैset -o pipefail
hobs

7
@ गोर्ल्सडॉफ़ी गोचैट्स को इंगित करने के लिए धन्यवाद; कुल मिलाकर, मुझे अभी भी लगता set -eहै कि उच्च लाभ-लागत अनुपात है।
ब्रूनो डी फेन

3
@BrunoDeFraine मैं set -eखुद का उपयोग करता हूं, लेकिन इसके खिलाफ irc.freenode.org # bash सलाह (काफी मजबूत शब्दों में) में अन्य नियमित की एक संख्या। कम से कम, विचाराधीन गोटकों को अच्छी तरह से समझा जाना चाहिए।
चार्ल्स डफी

3
set -e -o pipefail -u # और जानें कि आप क्या कर रहे हैं
सैम वॉटकिंस

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
यह बात कमाल की है .. आपको इसके लिए एक गिथब प्रोजेक्ट बनाना चाहिए, ताकि लोग आसानी से सुधार कर सकें और वापस योगदान कर सकें। मैंने इसे log4bash के साथ संयोजित किया और साथ में यह अच्छी बैश स्क्रिप्ट बनाने के लिए एक शक्तिशाली env बनाता है।
डॉमिनिक डोर्न

1
FYI करें - test ${#g_libs[@]} == 0POSIX- आज्ञाकारी नहीं है (POSIX परीक्षण =स्ट्रिंग तुलना के लिए या -eqसंख्यात्मक तुलना के लिए समर्थन करता है , लेकिन ==POSIX में सरणियों की कमी का उल्लेख नहीं करने के लिए), और यदि आप POSIX अनुरूप होने की कोशिश नहीं कर रहे हैं , तो क्यों दुनिया आप testएक गणित संदर्भ के बजाय सभी का उपयोग कर रहे हैं ? (( ${#g_libs[@]} == 0 ))आखिरकार, पढ़ना आसान है।
चार्ल्स डफी

2
@ लुका - यह वास्तव में बहुत अच्छा है! आपकी तस्वीर ने मुझे अपना खुद का कार्यान्वयन बनाने के लिए प्रेरित किया, जो इसे कुछ कदम भी आगे ले जाता है। मैंने इसे अपने उत्तर में नीचे पोस्ट किया है ।
niieani

3
Bravissimo !! यह एक स्क्रिप्ट को डीबग करने का एक शानदार तरीका है। ग्रेजी मिल केवल एक चीज मैं जोड़ा इस तरह ओएस एक्स के लिए एक चेक था: 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
एक बेशर्म आत्म-प्लग का एक सा, लेकिन हमने इस स्निपेट को लिया है, इसे साफ किया है, अधिक सुविधाओं को जोड़ा है, आउटपुट स्वरूपण में सुधार किया है, और इसे और अधिक पॉसिक्स संगत (लिनक्स और ओएसएक्स दोनों पर काम करता है) बनाया है। यह Github पर Privex ShellCore के भाग के रूप में प्रकाशित किया गया है: github.com/Privex/shell-core
Someguy123

22

"सेट-ई" के बराबर विकल्प है

set -o errexit

यह ध्वज के अर्थ को "-ई" की तुलना में कुछ हद तक स्पष्ट करता है।

यादृच्छिक जोड़: ध्वज को अस्थायी रूप से अक्षम करने के लिए, और डिफ़ॉल्ट पर लौटें (निकास कोड की परवाह किए बिना निरंतर निष्पादन), बस उपयोग करें

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

यह अन्य प्रतिक्रियाओं में उल्लिखित उचित त्रुटि से निपटने को रोकता है, लेकिन त्वरित और प्रभावी है (बस बैश की तरह)।


1
$(foo)नंगे लाइन पर उपयोग करने के बजाय सिर्फ fooआम तौर पर गलत चीज है। इसे एक उदाहरण के रूप में देकर इसका प्रचार क्यों करें?
चार्ल्स डफी

20

यहां प्रस्तुत विचारों से प्रेरित होकर, मैंने अपने बैश बॉयलरप्लेट प्रोजेक्ट में बैश स्क्रिप्ट में त्रुटियों को संभालने के लिए एक पठनीय और सुविधाजनक तरीका विकसित किया है ।

बस पुस्तकालय का सोर्सिंग करके, आप निम्नलिखित बॉक्स से बाहर निकलते हैं (जैसे कि यह किसी भी त्रुटि पर निष्पादन को रोक देगा, जैसे कि set -eकिसी trapऑन ERRऔर कुछ बैश-फू के लिए धन्यवाद का उपयोग करते हुए ):

बैश- oo- फ्रेमवर्क एरर हैंडलिंग

कुछ अतिरिक्त विशेषताएं हैं जो त्रुटियों को संभालने में मदद करती हैं, जैसे कि प्रयास करें और पकड़ें , या थ्रो कीवर्ड, जो आपको बैकट्रेस को देखने के लिए एक बिंदु पर निष्पादन को तोड़ने की अनुमति देता है। इसके अलावा, अगर टर्मिनल इसका समर्थन करता है, तो यह पावरलाइन इमोजीस को बाहर निकालता है, महान पठनीयता के लिए आउटपुट के रंग भागों को दिखाता है, और उस पद्धति को रेखांकित करता है जो कोड की लाइन के संदर्भ में अपवाद का कारण बनता है।

नकारात्मक पक्ष यह है कि यह पोर्टेबल नहीं है - कोड बैश में काम करता है, शायद> = 4 केवल (लेकिन मुझे लगता है कि इसे 3 को टक्कर देने के प्रयास के साथ चित्रित किया जा सकता है)।

बेहतर हैंडलिंग के लिए कोड को कई फाइलों में अलग किया गया है, लेकिन मैं लुका बोर्रियन द्वारा दिए गए उत्तर से बैकट्रेस विचार से प्रेरित था ।

अधिक पढ़ने के लिए या स्रोत पर एक नज़र डालें, GitHub देखें:

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


यह बैश ऑब्जेक्ट ओरिएंटेड फ्रेमवर्क प्रोजेक्ट के अंदर है । ... सौभाग्य से इसमें केवल 7.4k LOC ( GLOC के अनुसार ) है। ओओपी - वस्तु-उन्मुख दर्द?
19 को निगलना

@ कहीं भी यह अत्यधिक मॉड्यूलर (और डिलीट-फ्रेंडली) है, इसलिए आप केवल अपवाद भाग का उपयोग कर सकते हैं यदि वह आपके लिए आया हो;)
niieani 19

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 ))`):"'

मैं आमतौर पर एरर_एक्सिट फ़ंक्शन के पक्ष में क्लीनअप फ़ंक्शन के लिए कॉल करता हूं, लेकिन यह स्क्रिप्ट से स्क्रिप्ट में भिन्न होता है इसलिए मैंने इसे छोड़ दिया। जाल आम समाप्ति संकेतों को पकड़ते हैं और सुनिश्चित करते हैं कि सब कुछ साफ हो जाता है। उपनाम वह है जो असली जादू करता है। मुझे असफलता के लिए सब कुछ जांचना पसंद है। इसलिए सामान्य तौर पर मैं कार्यक्रमों को "अगर!" टाइप स्टेटमेंट। लाइन नंबर से 1 घटाकर उपनाम मुझे बताएगा कि विफलता कहां हुई। यह कॉल करने के लिए भी सरल है, और बहुत ज्यादा बेवकूफ सबूत है। नीचे एक उदाहरण दिया गया है (जो भी आप कॉल करने जा रहे हैं उसके साथ बस / बिन / झूठ बदलें)।

#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
क्या आप "हम उपनाम स्पष्ट रूप से अनुमति देने के लिए" बयान पर विस्तार कर सकते हैं ? मुझे चिंता होगी कि कुछ अप्रत्याशित व्यवहार का परिणाम हो सकता है। क्या छोटे प्रभाव के साथ एक ही चीज को प्राप्त करने का एक तरीका है?
ब्‍लॉन्‍ग

मैं जरूरत न $LINENO - 1। इसके बिना सही ढंग से दिखाएं।
kyb

बाश और zsh में छोटा उपयोग उदाहरणfalse || die "hello death"
kyb

6

लौटने के लिए एक और विचार निकास कोड है। बस " 1" बहुत मानक है, हालांकि कुछ मुट्ठी भर आरक्षित निकास कोड हैं जो स्वयं उपयोग करते हैं , और उसी पृष्ठ का तर्क है कि उपयोगकर्ता-परिभाषित कोड 64-113 की सीमा / C ++ मानकों के अनुरूप होना चाहिए।

आप बिट वेक्टर दृष्टिकोण पर भी विचार कर सकते हैं जो 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पहले नहीं।
चार्ल्स डफी

5
${$?}बस होना चाहिए $?, या ${?}यदि आप अनावश्यक ब्रेसिज़ का उपयोग करने पर जोर देते हैं; भीतरी $गलत है।
चार्ल्स डफी

3
@CharlesDuffy अब तक, POSIX जीएनयू / लिनक्स-असंगत (फिर भी, मैं आपकी बात ले लेता हूं)
आभारी

3

मैंने उपयोग किया है

die() {
        echo $1
        kill $$
}

इससे पहले; मुझे लगता है क्योंकि 'एक्जिट' मेरे लिए किसी कारण से असफल रही थी। उपरोक्त चूक एक अच्छे विचार की तरह लगते हैं, हालाँकि।


बेहतर एसटीडीआरआर को गलत संदेश भेजें, नहीं?
एकॉस्टिस

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"

इसका उपयोग करके मैं कुछ स्वचालित प्रक्रिया के लिए वास्तव में मजबूत बैश फ़ाइल बनाने में सक्षम था और यह त्रुटियों के मामले में बंद हो जाएगा और मुझे सूचित log.shकरेगा (मैं क्या करूँगा))


2
कार्यों को परिभाषित करने के लिए POSIX सिंटैक्स का उपयोग करने पर विचार करें - कोई functionकीवर्ड नहीं , बस error_exit() {
चार्ल्स डफी

2
वहाँ एक कारण है कि तुम सिर्फ क्यों नहीं है cd /home/myuser/afolder || error_exit "Unable to switch to folder"?
पियरे-ओलिवियर वेर्स

@ पियरे-ओलिवियरवेयर का उपयोग नहीं करने के बारे में कोई विशेष कारण || यह एक मौजूदा कोड का एक अंश था और मैंने प्रत्येक पंक्ति के बाद "त्रुटि हैंडलिंग" लाइनों को जोड़ा। कुछ बहुत लंबे होते हैं और इसे साफ करने के लिए एक अलग (तत्काल) लाइन पर रखा गया था
नेल्सन रोड्रिगेज

एक साफ समाधान की तरह दिखता है, हालांकि, शेल की जांच की शिकायत है: github.com/koalaman/shellcheck/wiki/SC2181
mhulse

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

$_समारोह में उपलब्ध नहीं होगा के रूप में ही $?? मुझे यकीन नहीं है कि फ़ंक्शन में एक का उपयोग करने का कोई कारण है, लेकिन अन्य नहीं।
19 को लागू करें

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

लेकिन ||सफाई से पहले बाहरी फ़ंक्शन से लौटने से रोकने के लिए ऑपरेटर की आवश्यकता होती है। चाल पृष्ठभूमि में आंतरिक कमांड को चलाने के लिए है, और फिर तुरंत इसके लिए प्रतीक्षा करें। waitBuiltin भीतरी आदेश के निकास कोड प्राप्त होगा, और अब आप उपयोग कर रहे हैं ||के बाद 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

यहाँ जेनेरिक फ़ंक्शन है जो इस विचार पर बनाता है। यदि आप localकीवर्ड हटाते हैं , तो सभी POSIX- संगत गोले में काम करना चाहिए , अर्थात सभी 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कॉलिंग फ़ंक्शन के लिए प्रचारित नहीं करेंगे, क्योंकि कमांड सब-डिस्क्रिप्शन में चलता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.