3

I have a wrapper script for a CI pipeline which works great, but it always returns with 0 even though subcommands in a for loop fails. Here is an example:

#!/bin/bash

file_list=("file1 file2 file_nonexistant file3")

for file in $file_list
do
  cat $file
done
>./listfiles.sh
file1 contents
file2 contents
cat: file_nonexistant: No such file or directory
file3 contents
>echo $?
>0

Since the last iteration of the loop is successfull the entire script exits with 0. What i want is for the loop to continue on fail and for the script to exit 1 if any of the loop iterations returned errors.

What i have tried so far:

  • set -e but it halts the loop and exits when an iteration fails
  • replaced done with done || exit 1 - no effect
  • replaced cat $file with cat $file || continue - no effect
2
  • Hmm the script still returns 0 even if file_nonexistant is the last file to be iterated over. even with done || exit 1 Commented Oct 29, 2021 at 8:57
  • 1
    file_list=("file1 file2 file_nonexistant file3") This is an array with one element... Why is it an array at all? Check your scripts with shellcheck. Commented Oct 29, 2021 at 9:30

1 Answer 1

3

Alternative 1

#!/bin/bash

for i in `seq 1 6`; do
    if test $i == 4; then
        z=1
    fi
done
if [[ $z == 1 ]]; then
  exit 1
fi

With files

#!/bin/bash

touch ab c d e
for i in a b c d e; do
    cat $i
    if [[ $? -ne 0 ]]; then
        fail=1
    fi
done

if [[ $fail == 1 ]]; then
    exit 1
fi

The special parameter $? holds the exit value of the last command. A value above 0 represents a failure. So just store that in a variable and check for it after the loop.

The $? parameter actually holds the exit status of the previous pipeline, if present. If the command is killed with a signal then the value of $? will be 128+SIGNAL. For example 128+2 in case of SIGINT (ctrl+c).

Overkill solution with trap

#!/bin/bash

trap ' echo X $FAIL; [[ $FAIL -eq 1 ]] && exit 22 ' EXIT

touch ab c d e
for i in  c d e a b; do
    cat $i || export FAIL=1
    echo F $FAIL
done

Sign up to request clarification or add additional context in comments.

3 Comments

Hmm, setting a manual error flag was my first go-to aswell, and it does solve this particular issue. But isn't there a more native way of doing this using returns or try/catch or something?
try / catch is not native to bash in any way. You could run a function with trap myfunc EXIT and handle some state there, and exit with an arbitrary code. However, you would still rely on a variable or an env to detect the fault. You could also touch a file, and check for that...
isn't there a more native way of doing this using returns or try/catch or something? So you want an answer like "no, there isn't"? If you want custom behavior (catching errors and then existing the script on the end), then you have to implement it. cat $i if [[ $? -ne 0 ]] - use if ! cat "$i"; then , if [[ $fail == 1 ]] - in bash just if ((fail))

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.