2

I have the following bash code (running on Red Hat) that is exiting when I enable set -o errexit and the variable in the code is empty, BUT works fine when the variable is set; the code is designed to test if a screen session matching .monitor_* exists, and if so do something.

I have the following turned on:

set -o errexit
set -x xtrace; PS4='$LINENO: '

If there is a session matching the above pattern it works; however, if nothing matches it just exits with no information other than the following output from xtrace

someuser:~/scripts/tests> ./if_test.sh
+ ./if_test.sh
+ PS4='$LINENO: '
4: set -o errexit
5: set -o pipefail
6: set -o nounset
88: /usr/bin/ls -lR /var/run/uscreens/S-storage-rsync
88: grep '.monitor_*'
88: awk '{ print $9 }'
88: /usr/bin/grep -Ev 'total|uscreens'
8: ms=

I tested the command I am using to set the ms var and it agrees with the xtrace output, it's not set.

someuser:~/scripts/tests> test -n "${mn}"
+ test -n ''

I have tried using a select statement and got the same results... I can't figure it out, anyone able to help? Thanks.

I read through all the possible solution recommendations, nothing seems to address my issue.

The code:

#!/usr/bin/env bash

set -o xtrace; PS4='$LINENO: '
set -o errexit
set -o pipefail
set -o nounset

ms="$(/usr/bin/ls -lR /var/run/uscreens/S-"${USER}" | /usr/bin/grep -Ev "total|uscreens" | grep ".monitor_*" | awk '{ print $9 }')"

if [[ -z "${ms}" ]]; then
    echo "Handling empty result"
elif [[ -n "${ms}" ]]; then
    echo "Handling non-empty result"
fi

The following answer was proposed: Test if a variable is set in bash when using "set -o nounset"; however, it doesn't address the issue at all. In my case the variable being tested is set and as stated in my detail, it's set to "", or nothing. Thank you; however, it doesn't help.

It really seems to be the variable declaration that it isn't liking.

ms="$(/usr/bin/ls -lR /var/run/uscreens/S-"${USER}" | /usr/bin/grep -Ev "total|uscreens" | grep ".monitor_*" | awk '{ print $9 }')"
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Likely a duplicate of https://stackoverflow.com/questions/7832080/test-if-a-variable-is-set-in-bash-when-using-set-o-nounset – Léa Gris Aug 28 '19 at 18:49
  • Possible duplicate of [Test if a variable is set in bash when using "set -o nounset"](https://stackoverflow.com/questions/7832080/test-if-a-variable-is-set-in-bash-when-using-set-o-nounset) – Léa Gris Aug 28 '19 at 18:50
  • This doesn't solve the issue, thank you. It has something to do with the variable being set via a command I think. I am still working on this - but the above linked answer doesn't answer it thank you. – Brandon Monterosso Aug 28 '19 at 19:30
  • 1
    You're running `set -o pipefail`. When `grep` doesn't match anything, it has a nonzero exit status, and with `pipefail`, that fails the entire pipeline. This is all behaving exactly the way you're telling your shell it *should* behave. – Charles Duffy Aug 28 '19 at 20:32
  • 1
    BTW, before deciding to use `set -o errexit`, I *strongly* suggest going through the exercises in [BashFAQ #105](http://mywiki.wooledge.org/BashFAQ/105#Exercises). – Charles Duffy Aug 28 '19 at 20:33
  • Thank you Charles. I will do that. I appreciate the help. Do you have any suggestions for resolving my snafu? – Brandon Monterosso Aug 28 '19 at 20:36
  • 1
    Well, my *main* suggestion is not to use `set -e`. You can, of course, always add a conditional to the specific pipeline component(s). – Charles Duffy Aug 28 '19 at 20:37
  • Charles was spot on, thank you. I commented out set -o pipefail, and it confirmed what he said. – Brandon Monterosso Aug 28 '19 at 20:38

2 Answers2

3
  • You're running set -o pipefail, so if any component in a pipeline has a nonzero exit status, the entire pipeline is treated as having a nonzero exit status.
  • Your pipeline runs grep. grep has a nonzero status whenever no matches are found.
  • You're running set -o errexit (aka set -e). With errexit enabled, the script terminates whenever any command fails (subject to a long and complicated set of exceptions; some of these are presented in the exercises section of BashFAQ #105, and others touched on in this excellent reference).

Thus, when you have no matches in your grep command, your script terminates on the command substitution running the pipeline in question.


If you want to exempt a specific command from set -e's behavior, the easiest way to do it is to simply append ||: (shorthand for || true), which marks the command as "checked".

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • For those still learning or needing more detail on exactly how Charles' answer applies to my code, I modified my variable to apply the `|| true` to it (full var shown above: ```... grep ".monitor_*" | awk '{ print $9 }' || true)"``` Or, ```... grep ".monitor_*" | awk '{ print $9 }' ||:)"``` – Brandon Monterosso Aug 28 '19 at 20:46
  • 1
    Consider `awk '/total|uscreens/ && /.monitor_*/ { print $9 }'`, getting rid of the `grep`s altogether. – Charles Duffy Aug 28 '19 at 20:50
  • ...that said, do note that [parsing `ls` output is itself best avoided](http://mywiki.wooledge.org/ParsingLs). – Charles Duffy Aug 28 '19 at 20:51
1

"You're running set -o pipefail. When grep doesn't match anything, it has a nonzero exit status, and with pipefail, that fails the entire pipeline. This is all behaving exactly the way you're telling your shell it should behave." – Charles Duffy

Charles above comment was exactly what was going on, my script was working as intended and I need to adjust the logic to work differently if I wish to keep set -o pipefail set.

Thank you for the help.