0

I use this loop to iterate through files or, if there are no files, read stdin:

#!/bin/bash
set -e
cat "$@" | while read arr ; do
    echo "Got this line ${arr}"
done

The problem is that if the file doesn't exist, it doesn't error out.

You can see the full example here: https://github.com/StackExchange/blackbox/blob/master/tools/mk_rpm_fpmdir

cat does return error code 1 if any file is not found. However the error doesn't cause the program to stop.

How can I iterate through $@ and fail if a file is not found?

TomOnTime
  • 4,175
  • 4
  • 36
  • 39
  • Triple quotes don't mean anything to the shell so `"""$@"""` is just `"$@"` with extra empty strings on each side. Also this isn't "iterating" in any way. `cat` is just going to dump the contents from all the files in a continuous stream until it finishes. And the reason this isn't failing is because `set -e` only works on "simple commands". See http://stackoverflow.com/q/25794905/258523 . – Etan Reisner Feb 03 '15 at 21:12
  • 2
    It's the right-hand side of a pipeline -- not the left -- that determines the exit status of that pipeline as a whole, unless `shopt -s pipefail` is set. – Charles Duffy Feb 03 '15 at 21:15
  • The bash idiom `cat """$@"""` means "all the args, but quoted so that if they contain spaces it still works". – TomOnTime Feb 03 '15 at 21:17
  • 3
    @TomOnTime, `cat "$@"` means the same thing. The extra quotes have no effect. – Charles Duffy Feb 03 '15 at 21:17
  • @lurker Yes, I see the error message. However, the script doesn't "exit 1" and therefore other automated processes don't see that the script had a problem. – TomOnTime Feb 03 '15 at 21:18
  • @CharlesDuffy Ok, I've removed the extra quotes but that's unrelated to the problem. – TomOnTime Feb 03 '15 at 21:24
  • Do you want this to exit as soon as a file can't be found or just error at the end if any files couldn't be found? – Etan Reisner Feb 03 '15 at 21:25
  • Correction to the above comment: It's `set -o pipefail`, not `shopt -s pipefail`. – Charles Duffy Feb 03 '15 at 21:27
  • 1
    BTW, `read -a arr` and then dereferencing `"$arr"` will only give you the first element of the array, rather than its complete contents. You need to use `"${arr[@]}"` to get complete contents. – Charles Duffy Feb 03 '15 at 21:32
  • @CharlesDuffy Yeah, that "-a" is an artifact of the full code example that is linked to. – TomOnTime Feb 03 '15 at 21:36

2 Answers2

3

There are several available options. One is to simply ditch cat, and thus have no pipeline (which has the advantageous side effect of avoiding subshell use, and thus allowing changes to shell state -- variables set, etc -- to last beyond the inner loop):

set -e
for f; do # in "$@" is implicit
  while read -r -a arr; do
    printf 'Got line: '
    printf '%q ' "${arr[@]}"
    printf '\n'
  done <"$f"
done

The above also goes out of its way to print the content read in a way that entirely preserves the array read, and prints its contents unambiguously (distinguishing an array containing two elements foo bar baz from one containing three elements foo bar baz).


Another is to set pipefail, which will cause a pipeline to be considered failed if any component of a pipeline returns a nonzero exit status, including cat:

set -e
set -o pipefail
cat "$@" | while IFS= read -r; do
  printf 'Got line: %q\n' "$REPLY"
done

This works by overriding the default behavior by which only the right-hand side of a pipeline matters for purposes of considering the overall exit status of the command.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
1
for f in "${@}"; do
    test -f "${f}" || exit # fail if file doesn't exist
    cat "${f}" | awk '{print "Got this line" $0}'
done
robert
  • 4,612
  • 2
  • 29
  • 39
  • 1
    With `set -e` just `test -f "$f"` is enough for this to bail out actually. And you need to quote `"$@"` to be safe. – Etan Reisner Feb 03 '15 at 21:14
  • Still needs to be `test -f "$f"`, not `test -f $f`. – Charles Duffy Feb 03 '15 at 21:16
  • There's no correctness value to the extra curly braces here, btw. They're absolutely not a substitute for quotes. – Charles Duffy Feb 03 '15 at 21:17
  • fair enough. in principle it's fine though. I wouldn't used `set -e`. curly braces are good practice in general. – robert Feb 03 '15 at 21:24
  • I personally disagree -- I find that excessive use hurts readability -- but that's bikeshedding, to be sure. I certainly can't argue that they're harmful in any correctness-impacting way. – Charles Duffy Feb 03 '15 at 21:27
  • my personal opinion is that it doesn't affect readability that badly. also I use an IDE that performs static analysis on the fly, which squiggles these when I don't do it. It is very easy for unintended consequences to take hold in shell-scripts, so it's good to use whatever protections you can. – robert Feb 03 '15 at 21:33
  • robert, I'm just curious, which IDE are you using? – user3439894 Feb 03 '15 at 22:44
  • I probably can't name the IDEs, but the plugin is called BashSupport by Joachim Ansorg, and it's very good. The IDEs in question usually have a freely available "community edition" ;) – robert Feb 03 '15 at 22:56