4

I am trying to implement error handling in my shell script as described in the 2nd answer in Best practice to use $? in bash?

My script looks like this:

#!/bin/bash

try() {
    "$@"
    code=$?
    if [ $code -ne 0 ]
    then
        echo "oops $1 didn't work"
        exit 1
    fi
}

try myvar=$(mktemp -p ./)

The script exits with a

./test.sh: line 4: myvar=./tmp.scNLzO1DDi: No such file or directory
oops myvar=./tmp.scNLzO1DDi didn't work

Just,

myvar=$(mktemp -p ./)

of course works fine, and $myvar returns the full path and name of the temp file.

How can I get the statement to assign the name of the tmp file to the variable myvar, while still passing the entire statement and it's results to try() so try() can do what it needs to? Thanks.

6
  • I think your function could look like: try() { "$@" || ! echo "$1 returned $?" >&2 || exit; }
    – mikeserv
    Commented Sep 9, 2014 at 11:29
  • You might also want to look at set -e.
    – phemmer
    Commented Sep 9, 2014 at 12:46
  • @mikeserv: modifying as suggested results in the following out: ./test.sh: line 15: myvar=./tmp.6vHt78D2Pk: No such file or directory myvar=./tmp.6vHt78D2Pk returned 127
    – Jim Walker
    Commented Sep 9, 2014 at 13:48
  • @JimWalker - so it's working as well as it was I guess. But you still need to do as Volker Siegel says below. In general you can't set a variable as a consequence of evaluating one - it's just that the shell's bound to do one of them first, and it doesn't backtrack unless explicitly told to. That's what eval does. It is something you need to be careful with.
    – mikeserv
    Commented Sep 9, 2014 at 13:53
  • @mikeserv - understood. I was trying to avoid using eval due to the purported security implications, I guess I don't have much of a choice in this case.
    – Jim Walker
    Commented Sep 9, 2014 at 13:58

2 Answers 2

4

With try myvar=$(mktemp -p ./), the subshell with the mktmp is executed before try is called, during building the argument list for try.

To make the subshell be expanded later, you need to quote the argument of try:

try 'myvar=$(mktemp -p ./)'

To do the expansion later, you need to use eval "$@" instead of the "$@" in the first line of try.

3
  • Adding the eval to "$@" and quoting the try argument works perfectly. Thanks.
    – Jim Walker
    Commented Sep 9, 2014 at 13:50
  • On second thoughts, is there any way at all to do this without using eval? My concern is that the try() function will be used with pretty much every statement of the script, along with the fact that "$@" encompasses all arguments coming in, which would significantly increase the security exposure in this case.
    – Jim Walker
    Commented Sep 9, 2014 at 14:06
  • What you want is "execute the shell command in the args of try". The security aspect is independant of how you do that, with eval or some other way. It's the main point of try to execute the arguments (and see what happens) - no way around that. Commented Sep 9, 2014 at 14:11
1

I think you want to use an error trap instead of a wrapper around evaluation.

err_handler () {
    code=$?
    if [ $code -ne 0 ]
    then
        echo "oops $1 didn't work"
        exit 1
    fi
}

trap 'err_handler' ERR
myvar=$(mktemp -p ./)
trap ERR
3
  • could you please explain why this approach is better than the try() method? Also, will this approach require a 'trap ERR' statement after every line in the script that needs to be checked for success or just a single 'trap ERR' statement at the end of the script? thanks.
    – Jim Walker
    Commented Sep 14, 2014 at 12:49
  • With the try method, you need to use eval to execute arbitrary code, which can be difficult to do correctly and safely. In the above, I only used the pair of trap statements to more closely simulate your use of try, which would only apply the error handler to a single statement. If your handler is general enough, you can enable it with the first trap and leave it enabled for the remainder of the script. Note, though, that traps are not inherited by subshells.
    – chepner
    Commented Sep 15, 2014 at 12:46
  • Thank you. While the previous answer did solve my original question, I like this approach better. There were way too many issues using eval while passing parameters to functions, subshells, etc. with the try() method. Setting a trap and handling it is a lot easier, and I was able to fine tune to the point where the trap nicely handles all (or most) error conditions. Where subshells are involved, I went with manual handling, still better than preceding every line with a try(). Thanks for both answers though.
    – Jim Walker
    Commented Sep 27, 2014 at 15:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .