Consider the following:

echo "hello" > file.txt
is_match1 () {
  local m
  m=$(cat "file.txt" | grep -F "$1")
  if [ -z "$m" ]; then
    return 1
is_match2 () {
  local m
  m=$(cat "file.txt" | grep -F "$1")
  test -z "$m" && return 1
is_match1 "hello"
echo "$?"
is_match2 "hello"
echo "$?"

Why does is_match2 return 1?


1 Answer 1


Under the circumstances of your question, m gets the value hello in both functions.

Now look at

test -z "$m" && return 1

What should happen here? The -z test is false, right? So return 1 does not execute. Instead, what does the function return? Every function returns the value of $? at the end. In this case, that value is 1, the result of the && list.

What you might have wanted to test is

if [ -z "$m" ]; then return 1; fi

compared to

if test -z "$m"; then return 1; fi

The exit status of both these if statements are zero when $m is non-empty, since none of the statements' branches were taken.

From the POSIX standard:

The exit status of the if command shall be the exit status of the then or else compound-list that was executed, or zero, if none was executed.

Note that we may condense both your functions down into

is_match () {
    grep -q -F -e "$1" file.txt

Here, we let grep provide the exit status to the caller. We also do not necessarily read the file.txt file to the end, as grep -q quits as soon as it finds a match.

  • 1
    the exit status of an if statement is zero, if none of the then/else branches were taken. if false; then false; fi leaves the exit status to 0, but if true; then false; fi leaves the exit status to 1. (even though in both cases the last command to run was false, and yes, I think it could return some other nonzero status too.)
    – ilkkachu
    Oct 4, 2021 at 14:51
  • @ilkkachu Thanks. I was definitely not attempting to make generic statements about this. I have amended the text to use more careful language.
    – Kusalananda
    Oct 4, 2021 at 14:58
  • Also is_match should probably just be is_match() { grep -qFe "$1" file.txt; } or is_match() { grep -qF -- "$1" file.txt; }, unless you do also want to store the matching line in $m (but then that would be poorly chosen name for that global reply variable) Oct 4, 2021 at 15:08
  • 1
    I have edited the question to make $m a local variable.
    – fuumind
    Oct 4, 2021 at 15:17
  • 1
    @they, yeah. I think I just find the behaviour a bit odd, i.e. why would it not just leave the exit status of the last command in effect, regardless of if it was in a condition or in the inner branches. There's probably a sensible reason, but in a way it warrants a comment in my mind...
    – ilkkachu
    Oct 4, 2021 at 17:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .