linux - error handling in case sentences [Shell Script]

08
2014-07
  • Adryoid

    I have this setup and I need when an error happened in the main script, to take this error to a $INFO_PLUGIN temp file, the problem is the script continues working after case and send also an OK message to $INFO_PLUGIN temp file, overriding existing error message.

    I would like to when a error comes, take this error to $INFO_PLUGIN temp file and prevent the execution of reportmail OK; in some way, but I don't know how and I need some help.

    Thnaks in advance for your time and cooperation.

    Main Script:

    source ~/share/mailReport.fn
    
    case $2 in
              firstcase)
                   ssh -q -n loguser@server "mkdir -p /home/loguser/logstorage" 
                   if [[ $? -ne 0 ]]; then
                        echo " ************ "
                        echo " *   Error  * "
                        echo " ************ "
                        reportmail 12;
                        exit 12;
                   fi;
              ;;
              secondcase)
                   startDate=`date +%s`
                   if [ -z $3 ]; then
                        echo -e "\nInsert a parameter...: $0 $1 $2 [Here!]\n"
                   exit;
                   fi;
                   echo -e "\nRunning test with error number...: $3\n"
                   endDate=`date +%s`
                   DIFF=$(( $endDate - $startDate ))
                   echo -e "* Time in sec..: $DIFF\n"
                   reportmail $3;
              ;;
    esac
    echo " ************ "
    echo " * Finished * "
    echo " ************ "
    
    reportmail OK;
    

    reportMail.sh Script:

    case $1 in
             12)
                mailx -s "Error happened during mkdir" -r $SENDER_ADDRESS $RECIPIENT_ADDRESS
    <<EOF
        Hello,
        Error happened in `uname -n` bla, bla, bla...
    EOF
                echo -e "CRITICAL - Error creating destination dir" > $INFO_PLUGIN
             ;;
             OK)
                echo -e "OK - Time in sec..: $DIFF Total Saved..: `cat $temp` MB" > $INFO_PLUGIN
                rm $temp
             ;;
    esac
    

    Edit:

    ############################### Little example ###############################
    

    test.sh

    #!/bin/sh
    
    source ~/test2.sh
    
    case $1 in
    
            a) error 0; ;;
            b) error 1; ;;
            c) error 2; ;;
    
    esac;
    
    error 0; # This is what I need to jump in case of error
    

    test2.sh:

    #!/bin/sh
    
    function error () {
    
    case $1 in
    
            0) echo "Everything = OK" > ~/results.txt ;;
            1) echo "Sth Wrong = error 2" > ~/results.txt ;;
            2) echo "Sth Wrong = error 3" > ~/results.txt ;;
    
    esac;
    }
    

    if I run:

    [user@server dir]$ ./Test.sh 1
    

    or:

    [user@server dir]$ ./Test.sh 2
    

    this is the result, included on file ~/results.txt

    [user@server dir]$ cat results.txt
    Everything = OK
    

    The result is the same on all cases, because error 0; on first script, so my question is how can I do to determine if an error happened on some value on first case, for example: error happened running Test.sh b. How may I do to jump error 0; and prevent to execute it if there is an error before, else if there is no error on case. error 0; should be executed to say hey, everything is ok!

    I hope you understand and thanks a lot for your time.

  • Answers
  • Adryoid

    Problem solved by adding this:

    echo -e "CRITICAL - Error deleting files - Performance" > $INFO_PLUGIN && echo "1" > $err
    

    this send a 1 to an error file and...

    if [ `cat $err` -eq 1 ]; then
        reportmail 201$BYTES;
        rm $err
    else
        reportmail 201$BYTES;
        reportmail OK;
        rm $err
    fi;
    

    this test if there was an error during execution of scripts, if yes, just send the report in ordr to see the log on a web server, otherwise, if everithing is OK send the log to a web server and send OK message to Nagios monitoring system.

    Thanks! & until next time!


  • Related Question

    linux - Byzantine behaviour in shell script
  • mrucci

    I wrote the following script to interactively and recursively remove orphan backup files, i.e. remove each file.txt~ that does not have a corresponding file.txt.

    #!/bin/sh -x
    
    set -o errexit
    unalias -a
    
    backups=$(find . -name "*~")
    
    orphans=""
    while read -r file
    do
        [ ! -e "${file%~}" ] && orphans=$(echo "$file\n$orphans");
    done << EOF
    $backups
    EOF
    
    if [ -z "$orphans" ]; then
        echo "No orphans."
    else
        echo "orphans:\n$orphans"
        echo "$orphans" | xargs --interactive -d '\n' rm
    fi
    

    This script does very weird things in a random fashion. Sometimes it behaves correctly, sometimes it ignores the -x options passed to sh, sometimes it executes commented code, sometimes the tests give just wrong results.

    The problem seems to be related to the here script, since by redirecting the output of find to a temporary file all problems seem to disappear. But why, where is the error?


    Solution (thanks to Dennis Williamson): Escape the ~ character. The unescaped ~ in the parameter expansion ${file%~} was creating somehow all the unpredictable behavior.


    A more readable and deterministic solution, with some fat cut out, could be (thanks to the suggestions of Mikel):

    #!/bin/sh
    IFS='
    '
    for backup in $(find . -type f -name "*~"); do
        if [ ! -e "${backup%\~}" ]; then
            rm -i "$backup"
        fi
    done
    

    If you are a while read loop fan, things get less elegant because the interactive command rm -i can not be used (it will conflict with the read command). Anyway, a solution could be:

    #!/bin/sh
    orphans=""
    while read -r backup; do
        if [ ! -e "${backup%\~}" ]; then
            orphans=$(echo "$backup\n$orphans");
    fi
    done << EOF
    $(find . -type f -name "*~")
    EOF
    
    if [ ! -z "$orphans" ]; then
        echo "$orphans" | xargs --interactive -d '\n' rm
    fi
    

    Or, a more complex way is also suggested by Dennis Williamson.


  • Related Answers
  • Dennis Williamson

    You probably need to escape the tilde in the brace expansion, otherwise it will get expanded to your home directory.

    Why don't you pipe the find into your while loop instead of creating variables to hold them? Inside your loop, just do rm -i "$file".

    #!/bin/sh -x
    
    set -o errexit
    unalias -a
    
    exec 3<&0    # open a duplicate of stdin
    flag=false
    find . -name "*~" | while IFS=$'\n' read -r file
    do
        if [ ! -e "${file%\~}" ]
        then
            orphans="$file"$'\n'"$orphans"
    
            # use an alternate file descriptor so read and rm -i get along
            rm -i "$file" <&3
            flag=true
        fi
    done
    exec 3<&-    # close the file descriptor
    
    if ! $flag
    then
        echo "No orphans."
    else
        echo "orphans:\n$orphans"
    fi
    

    If you want to use Bash, you need to move the find to the end of the loop so a subshell is not created.

    #!/bin/bash
    
    ...
    
    # use an alternate file descriptor so read and rm -i get along
    while read -u 3 -r ...
    
        rm -i ...
        ...
    
    done 3< <(find ...)
    
    ...
    
  • Mikel

    What do you mean "sometimes"? Sometimes on the same box? Or different behavior on different systems?

    What do you mean "executes commented code"? Your example doesn't have any comments.

    Some thoughts:

    • try set -x in place of /bin/sh -x
    • better to use set -e than set -o errexit
    • if you're using bash, then call /bin/bash, not /bin/sh, which could be something else
    • echo is unnecessary, just use = with a literal newline
    • if you do have to use echo, you should use echo -e or ensure xpg_echo is set
    • your orphans line is putting them in reverse order, is that deliberate?
    • your read loop will fail if file names contain spaces, you should set IFS first

    A simpler version:

    #!/bin/bash
    
    set -x
    set -e
    
    IFS=$'\n'
    orphans=false
    for backup in $(find . -type f -name "*~"); do
        original=${backup%\~}
        if [ ! -e "$original" ]; then
            orphans=true
            rm -i "$backup"
        fi  
    done
    
    if ! $orphans; then
        echo "No orphans."
    fi
    

    Or if you want it to work using /bin/sh:

    #!/bin/sh
    
    set -x
    set -e
    
    IFS='
    '
    orphans=false
    for backup in $(find . -type f -name "*~"); do
        original=${backup%\~}
        if [ ! -e "$original" ]; then
            orphans=true
            rm -i "$backup"
        fi  
    done
    
    if ! $orphans; then
        echo "No orphans."
    fi