简体   繁体   中英

How to abort only function on error, but do not the script?

I have the following script:

#!/bin/bash

set -e

function do_it {
    rm this-file-does-not-exit
    echo "Continuing ..."
    echo "Done ..."
    return 0
}

if do_it ; then
    echo "Success"
else
    echo "Error"
fi
echo "End of script"

Which produces:

rm: cannot remove 'this-file-does-not-exit': No such file or directory
Continuing ...
Done ...
Success
End of script

The function is not aborted because running the function in the if statement disables set -e for the function (and can not be enabled). This is not what I want.

Running the function outside the if statement, and collecting the result, is not possible, because in this situation the complete script is aborted if the function fails:

#!/bin/bash

set -e

function do_it {
    rm this-file-does-not-exit
    echo "Continuing ..."
    echo "Done ..."
    return 0
}

do_it
if $? ; then
    echo "Success"
else
    echo "Error"
fi
echo "End of script"

Produces:

rm: cannot remove 'this-file-does-not-exit': No such file or directory

I would like the following:

  • the script aborts whenever a top-level command has a failure
  • functions are aborted whenever a command has a failure. The return code of the failing command is returned as function return code
  • failure in a function does not abort the script (only the function)

This can be implemented as follows:

#!/bin/bash

set -e

function do_it {
    rm this-file-does-not-exit || return $?
    echo "Continuing ..." || return $?
    echo "Done ..." || return $?
    return 0
}

if do_it ; then
    echo "Success"
else
    echo "Error"
fi
echo "End of script"

Which produces what I want:

rm: cannot remove 'this-file-does-not-exit': No such file or directory
Error
End of script

But this makes the implementation of the function completely unreadable: each line must be followed with a || return $? || return $?

Is there another way of aborting a function, and only the function , whenever an statement in the function fails?

You can use "pipefail" to get the return code of the last executed command in the pipe. I wouldn't say it looks much better than your code, but at least it's shorter.

#!/bin/bash

set -ep

function do_it {
    rm this-file-does-not-exit &&
    echo "Continuing ..." &&
    echo "Done ..."
    return $?
}

if do_it ; then
    echo "Success"
else
    echo "Error"
fi
echo "End of script"

In this case function will stop executing right after first failed command (rm) and the result will be the return code of the last command in the pipeline. If "rm <filename>" will be successful, both echoes should work, and the return code will be 0.

Output:

$ touch this-file-does-not-exit
$ ./script-return.sh 
Continuing ...
Done ...
Success
End of script
$ ./script-return.sh 
rm: cannot remove 'this-file-does-not-exit': No such file or directory
Error
End of script

I'm not very good with bash scripting and it would take me a lot of effort to implement my following thought, but I think this should work:

  • implement a function
    • which gets an array as input
    • interrupts the commands (which were passed as strings) on failure like your example
    • returns something which identifies where it was interrupted and why (if it was interrupted)
  • when you desire the behavior you described
  • build an array of the commands that you would like to execute
  • pass this array to the function
  • handle the response

Nice question :D What you can do is create a function that parses functions and appends | return $? | return $? if a command fails .

#! /bin/bash

function _exec {
        local func=$1
        [[ ! -z $func ]] || exit 1
        local data="$(typeset -f $1 | sed '1,2d;$d');"
        while read -d ';' cmd; do
                eval "$cmd" || return $?
        done <<<"$data"
        return 0

}

function _test { 
        local ret=$1
        echo -n "[ RESULT ]: "
        if [[ $ret -ne 0 ]]; then
                echo "failure"
        else
                echo "success"
        fi
}

function _fail {
        rm thisfilewillnevereverexistonmyfilesystemsoidonthavetomakesureitdoesnt.txt
        echo "hello"
        echo 2 3 \
                multiline also works
}

function _pass {
        true
        echo "Hello world"
}

_exec _fail
_test $?
_exec _pass
_test $?
exit 0

How it basically works is you let _exec execute the functions that may not fail. It retrieves the content of the function given as it's parameter and executes it with eval . Note that if you would execute false in _true , the _exec function will fail also.

Hope this works out for you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM