6

I have a script that calls another script. When the child script fails, I'd like the parent to fail as well.

In the child script child_1.sh, I have something like this:

if [ $SOME_BAD_CONDITION ] ; then
  echo "Error message...."
  exit 1
fi

In the parent, I have this:

#!/bin/bash
set -e
#...
bash ./child_1.sh
echo "continuing to next step..."
bash ./child_2.sh
bash ./child_3.sh
#...

I've set up my environment so that $SOME_BAD_CONDITION will always happen, and the script exits as expected and the error message does print, but the parent script continues: the message "continuing..." is printed and the next script begins executing.

I thought that having set -e would ensure that my parent script fails if any child script exists with a non-zero exit code, but that doesn't seem to be happening. I'm not sure what I got wrong here...

bash version: 4.2.25


UPDATE:

I tried echoing $?:

bash ./child_1.sh
echo $?
echo "continuing to next step..."

The output looks like this:

Error message....
0
continuing to next step...

Why doesn't the exit code from the child make it into the parent?


ANSWER: The original code snippet was incomplete. The exit 1 was inside a code block that was piped to tee. My attempt to post a clean, short code sample ignored this because I did not realize how significant it was (I'm still fairly new to bash scripting). See my posted answer for details.

3 Answers3

4

So, I might have made an error in not posting enough details about the child script - I simplified it for the purpose of this question, but maybe too much. The exit statement was buried inside a code block (between { and }) which was piped to tee for logging:

{
  #...
  if [ $SOME_BAD_CONDITION ] ; then
    exit 1
  fi
  #...
} | tee -a $LOGFILE
echo "script ended at $date">>$LOGFILE

From what I understand, I always got a return code of 0 from this child script because the last echo was returning 0. I did some research and discovered $PIPESTATUS. I was able to use this to ensure that when my script tried to exit with exit code 1, it would be passed up to parent script:

{
  #...
} | tee -a $LOGFILE
EXIT_CODE=${PIPESTATUS[0]}
echo "script ended at $date">>$LOGFILE
exit $EXIT_CODE

Combined with John's suggestion, I have a solution that seems to be working.

3

Since your child processes exit with a proper zero/non-zero exit code convention, and given that you execute them sequentially, I'd say you could just use the && operator:

#!/bin/bash

./child_1.sh
[[ $? -ne 0 ]] && exit # Exit if non-zero exit code
./child_2.sh
./child_3.sh
./child_4.sh
exit 0

Here, [[ $? -ne 0 ]] && exit acts like:

if [ $? -ne 0 ]; then # If: last exit code is non-zero
    exit
fi

If you don't have anything to do between the calls, you could even use...

#!/bin/bash
./child_1.sh && ./child_2.sh && ./child_3.sh && ./child_4
exit $?

The && operator acts like a short-circuit AND operator, and a zero exit code is pretty much like a true boolean value. If at any time in the chain, a script exits with non-zero, the && will fail, and subsequents scripts shouldn't be called.

John WH Smith
  • 15,880
  • I added a debugging statement to print the value of $? and it prints 0. It looks like the exit code from the child script does not get picked up by the parent script. – FrustratedWithFormsDesigner Feb 25 '15 at 18:26
  • @FrustratedWithFormsDesigner There must be a problem with your child script. I tried it myself just now, and my parent script did get the correct exit code back. Make sure your child script exits as you expect it to. – John WH Smith Feb 25 '15 at 18:54
  • Yes, you were right. I had a problem in the child script, but it seems I over-simplified the child script in the original post, which is why no one caught it. – FrustratedWithFormsDesigner Feb 25 '15 at 19:21
0

Looks like your issue stems from how you throw the error in your child script.

From man set "if a simple command (see SHELL GRAMMAR above) exits with a non-zero status. The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test in a if statement, part of an && or || list, or if the command's return value is being inverted via !"

Rather than using set -e, use a trap statement and throw an error. (If you need to use the conditional exit.

See http://mywiki.wooledge.org/BashFAQ/105

Crp
  • 1