0

I am confused:

echo "i-am-sure-this-is-here" > /tmp/myfile
if [[ $(grep -q sure /tmp/myfile) ]] ; then echo "Found!" ; else echo "wtf! not found" ; fi

Which gives:

wtf! not found

So I try something even simpler:

if [[ true ]] ; then echo "true -> expected" ; else echo "true -> unexpected" ; fi
if [[ false ]] ; then echo "false -> unexpected" ; else echo "false -> expected" ; fi

Which gives me something unexpected:

true -> expected
false -> unexpected

I am clearly not understanding this. Some questions:

  1. Why is false true?
  2. Is the conditional evaluating the exit code of the command at all? I expected 0 exit code to mean true (the return code if the true command), and any non 0 exit code to mean false (the false command returns 1)

How to evaluate exit codes of commands in an if-else-fi construct in bash without having to revert to explicitly comparing the exit code ($?) to an expected value, which is ugly and not very readable?

I know this would work:

grep -q sure /tmp/myfile
if [[ $? -eq 0 ]] ; then echo "Found!" ; else echo "wtf! not found" ; fi

But this is longer (2 lines instead of 1), and uglier.

DingDong
  • 45
  • 4
  • `[[ ]]` is true if `` is anything but an empty string. `grep -q` doesn’t print anything, so `[[ $(grep -q …) ]]` will always be false. `[[ false ]]` is true because the word `false`, which doesn’t ‘mean’ anything to Bash, is not an empty string. To use `grep` as a condition just use `if grep -q …; then …`. – Biffen Sep 17 '21 at 10:09
  • _But this is longer (2 lines instead of 1), and uglier_ : Longer and uglier: Yes. As for the number of lines, this depends on you. You can write the whole bash script into one single line, if you prefer. Actually, I would also the `if` statement spread over 6 lines for better readability, instead of squeezing it into one line, but this is a matter of taste. – user1934428 Sep 17 '21 at 10:21
  • You should use the base `test` or `[` built-ins. – sjsam Sep 17 '21 at 10:47
  • 1
    See also [Why is testing “$?” to see if a command succeeded or not, an anti-pattern?](https://stackoverflow.com/questions/36313216/why-is-testing-to-see-if-a-command-succeeded-or-not-an-anti-pattern) – tripleee Sep 17 '21 at 10:59

2 Answers2

1

if tests the status code of its "argument". Therefore

if grep -q sure /tmp/myfile
then
  echo found
fi

would do the job.

user1934428
  • 19,864
  • 7
  • 42
  • 87
  • so `if [[` has as argument `[[`? – DingDong Sep 17 '21 at 10:16
  • Like `grep`, `[[ ... ]]` is also a command on it's own, as you can see when you do a (for instance) `[[ abc == *c ]] && echo true`. `[[ ... ]]` sets the exit code to 0 if the condition is fulfilled, and to 1 if it is not. – user1934428 Sep 17 '21 at 10:18
  • @DingDong Kindly have a look at how the [bash conditional expressions](https://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html) work. – sjsam Sep 17 '21 at 10:44
  • Being a bit pedantic here, but the command is `[[`. The `]]` is just an argument to that command. – DingDong Sep 17 '21 at 12:16
  • @sjsam I am not confused about conditional expressions, but about how if evaluates the result of those expressions. – DingDong Sep 17 '21 at 12:21
  • The condition of an `if` statement is the arbitrary command list between `if` and `then` (where a command list is any number of commands joined by `&&`, `||`, `&`, `;` or a newline). The exit status of that list determines which branch, if any, to execute next. The most *common* command lists used with `if` are a simple `test/[` command or the compound command `[[ ... ]]`. – chepner Sep 17 '21 at 14:27
  • @DingDong `]` is an argument to `[`; `]]` is *not* an argument to `[[`, but rather `[[` and `]]` together define the compound command. (As a compound command, it is parsed and evaluated differently from a simple command; it's how parameter expansion avoids being subject to word-splitting, for example.) – chepner Sep 17 '21 at 14:29
  • @chepner Got it! https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html. Bash syntax is convoluted! – DingDong Sep 18 '21 at 07:12
1

Please read about [[ compound command. Grep with option -q output nothing:

$ grep --help
  ...
  -q, --quiet, --silent     suppress all normal output
  ...

That is why the first check is failing. The second check is true for both the tries because this is how [[ ]] works true|false doesn't matter you can write down whatever you want here it always be true. Only the empty string will fail.

$ [[ yes ]] && echo ok || echo fail
ok

$ [[ no ]] && echo ok || echo fail
ok

$ [[ fail ]] && echo ok || echo fail
ok

$ [[ '' ]] && echo ok || echo fail
fail

But you don't actually need to use test or [[]]|[] to check grep. Use its exit codes:

grep -q sure /tmp/myfile && echo ok || echo fail
ok

To prevent what Chepner said in comments, this construction can be used:

a && (b; exit 0) || c
Ivan
  • 6,188
  • 1
  • 16
  • 23
  • 1
    Your last code is problematic: https://github.com/koalaman/shellcheck/wiki/SC2015 – DingDong Sep 17 '21 at 12:18
  • `a && b || c` is not equivalent to `if a; then b; else c; fi`. `a && b || c` will execute `c` if `b` is executed and fails. – chepner Sep 17 '21 at 14:32