12

I have a fake bash function such as this:

has_error() {
  true
  echo "Ok..."
  false
  echo "Shouldn't be here!"
}

What I'd like to have happen when I run this function and check the error status:

> has_error; echo $?
Ok...
1

But what actually happens:

> has_error; echo $?
Ok...
Shouldn't be here!
0

The problem is that the function keeps executing after an error has been thrown, and moreover, I can't even detect that an error was thrown. How can I change this? I could put a set -e at the beginning of the function, but then my whole shell will terminate if an error gets thrown. What I'd like is to simply have it return and set the exit status to 1. I could do it with putting && in between the commands, or by suffixing every line with || return 1, but neither of those are very elegant. What's the proper way to do this?

EDIT:

It seems that I'm not being clear enough, because a lot of the responses seem to suggest that I don't actually know how to perform tests in bash. As I mention above, I am aware that I could manually test each command in my function, and return or handle errors as I wanted. I'm also aware that I can set -e and cause my shell session to terminate on an error. But what I'm asking is: is there a way to have a function cease to continue execution -- without an explicit test -- if any of the commands within that function return nonzero statuses?

limp_chimp
  • 13,475
  • 17
  • 66
  • 105
  • 2
    [Use a trap](http://stackoverflow.com/a/185900/258009) perhaps? – Component 10 May 27 '15 at 17:01
  • `set -e` will terminate anytime an error occurrs. Not sure about the scope it applies to but it's always worth knowing this. – RedX May 27 '15 at 17:20
  • @RedX I mentioned this in my post, and the reason why it doesn't work here. – limp_chimp May 27 '15 at 18:31
  • Before using `set -e`, you may want to review: [Why doesn't set -e (or set -o errexit, or trap ERR) do what I expected?](http://mywiki.wooledge.org/BashFAQ/105) – John1024 May 27 '15 at 19:30

2 Answers2

20

If your function doesn't need to set any global variables, you can have the function create a subshell in which set -e is used. The subshell will exit, not the shell in which has_error is called.

has_error () (    # "(", not "{"
  set -e
  true
  echo "Ok..."
  false
  echo "Shouldn't be here!"
)
chepner
  • 497,756
  • 71
  • 530
  • 681
  • I think this is the correct solution (worked for me)... OP, consider accepting? – Tom Jul 16 '15 at 11:35
  • 1
    Excellent solution! However, how come if I do `has_error || echo "I failed"` the function executes as if `set -e` was never done? – fgblomqvist May 13 '21 at 17:14
  • @fgblomqvist Thanks, I was confused too and then realised I had the same cause as you. https://unix.stackexchange.com/a/490177/58450 seems to answer this. – Tim Malone May 30 '22 at 04:18
8

The answer by @chepner helped me, but it is incomplete.

If you want to check function return code, don't put function call inside if statement, use $? variable.

randomly_fail() (
  set -e
  echo "Ok..."
  (( $RANDOM % 2 == 0 ))
  echo "I'm lucky"
)

FAILED=0
SUCCESS=0
for (( i = 0; i < 10; i++ )); do
  randomly_fail
  if (( $? != 0 )); then
      (( FAILED += 1 ))
  else
    (( SUCCESS += 1 ))
  fi
done

echo "$SUCCESS success, $FAILED failed"

This will output something like

Ok...
I'm lucky
Ok...
I'm lucky
Ok...
I'm lucky
Ok...
Ok...
I'm lucky
Ok...
Ok...
I'm lucky
Ok...
Ok...
Ok...
I'm lucky
6 success, 4 failed
Vas Laur
  • 81
  • 1
  • 2
  • 1
    +1 to clarify the good answer the code - the exit code $? is captured because the code between the ()'s is executed actually in a new forked shell process ... – Yordan Georgiev Mar 05 '17 at 14:01