1

My script below the function asks whether you want to install node.js. I know that I can check the success of my last command with $?. But now I have this node() function. How can I efficiently check if an error occured anywhere in my shell function?

node () {
    apt-get -y install python g++ make checkinstall
    mkdir ~/src && cd $_
    wget -N http://nodejs.org/dist/node-latest.tar.gz
    tar xzvf node-latest.tar.gz && cd node-v*
    ./configure
    checkinstall -y --install=no --pkgversion 0.10.24 # Replace with current version number.
    dpkg -i node_*
    cd ~
    rm -r ~/src
    # If an error occured anywhere in this function,
    # an error value should be returned so that the if-clause below fails,
    # for to exit the whole script
}

read -p "[q] Install node.js? [y/n] "
if [ $REPLY = "y" ]; then
    echo "[x] node script"
    node > /dev/null # This should 'get' the error so that ...
else
    echo "[s] Skipping installation of node.js"
fi
if [ $? -gt 0 ]; then echo "[e] An error occured"; exit 1; fi # ... it is caught here

echo "[f] Finished successfully"

exit 0
1
  • If wget fails (for instance), does it even make sense to continue? If you want to stop on error, you can start your function with set -e.... Commented Jan 10, 2014 at 23:44

3 Answers 3

1
node () {
    code=0
    apt-get -y install python g++ make checkinstall  || ((code +=1))
    mkdir ~/src && cd $_ || ((code+=4)
    wget -N http://nodejs.org/dist/node-latest.tar.gz  || ((code+=8))
    tar xzvf node-latest.tar.gz && cd node-v* || ((code+=16))
    ./configure  || ((code++32))
    checkinstall -y --install=no --pkgversion 0.10.24 || ((code+=64))
    dpkg -i node_*  || (code+=128))
    cd ~  || ((code+=256))
    rm -r ~/src || ((code+=512))
    return "$code"
}

If node returns with an error code, you can use the shell's bitwise comparison operators to determine which line failed. For example, to test if the wget line failed:

node
code=$?
(( $code & 8 )) && echo "wget failed in node"

If you want to know which line failed and not execute any succeeding lines, then use:

node () {
    apt-get -y install python g++ make checkinstall  || return 1
    mkdir ~/src && cd $_ || return 2
    wget -N http://nodejs.org/dist/node-latest.tar.gz  || return 3
    tar xzvf node-latest.tar.gz && cd node-v* || return 4
    ./configure  || return 5
    checkinstall -y --install=no --pkgversion 0.10.24 || return 6
    dpkg -i node_*  || return 7
    cd ~  || return 8
    rm -r ~/src || return 9
}
Sign up to request clarification or add additional context in comments.

Comments

0

Bash has a return statement that will set the value of $? in the callee to the numeric value specified in the function by return. Ex:

function fun1(){
  return 2
}

fun1
echo "fun1 returned" $?

should output

fun1 returned 2

2 Comments

Thank you for that. Unfortunately, this does not solve the problem: If fun1 contains five statements, I will have to check for every single command whether it returned zero or not, eventually resulting in the return value of that function. Isn't there a more general approach?
The history of return values is not preserved. You will need to, in some way, examine the return value of every command that can return an error. One simple way is to add || return X statements behind commands that may fail, assuming you don't need clean-up. An alternative is to set a global value to an error condition when one of the commands failed. But somehow, each command needs to be checked.
0

In your case, you'll definitely want to stop if one of the commands failed, and not just run them all regardless and check the statuses at the end.

You can do this by simulating a try-catch using subshells and set -e:

myfunction() {
  (  # Start subshell
    set -e # Exit on error
    cmd1
    cmd2
    cmd3
  ) 
  value=$?
  cleanup # any command to run regardless of status
  return $value
}

if myfunction
then
  echo "All commands executed successfully"
else
  echo "One of the commands failed, so execution was aborted"
fi

If there are any commands in this subshells where you don't want to abort, you can use cmd || true, which will run cmd and continue even if it fails.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.