2

I'm trying to write a compound if-statement using the new-test in Bash. I want to check that a file exists and that two other files have particular contents. My grep searches don't seem to work as well when nested within a test.

echo "a" >> file_a
echo "b" >> file_b 
echo "c" >> file_c
if [[ -f file_a && \
  $(grep -q "b" file_b) && \
  $(grep -q "c" file_c) ]]; then
  echo "Got 'em"
fi

4 Answers 4

6

When testing the exit status of a command (e.g. grep), you don't need or want [[ ]] or $( ) or any of that stuff. if checks the exit status of whatever's between if and then, and takes the appropriate branch based on whether it succeeded or failed.

$( ) exists to capture the output of a command (i.e. what it prints as it runs -- grep -q does not print anything), and use it as part of another command. That's not what you want at all.

[[ ]] evaluates a test expression (which can test whether e.g. a file exists, whether two strings are equal, etc), and converts the result into an exit status. But with grep -q it's already producing an exit status, so you don't need some sort of test to get it into that form.

You do need [[ ]] to check whether file_a exists, but that's the only role it'll play here. And you need && to make a compound command that succeeds only if all parts of it succeed. So what you want is:

...
if [[ -f file_a  ]] && 
  grep -q "b" file_b &&
  grep -q "c" file_c; then
...
2

You can remove the -q from grep and test if the expression inside the Command Substitution is not empty using the -n flag.

if [[ -f file_a && -n $(grep "b" file_b) && -n $(grep "c" file_c)  ]]; then
  echo "Got 'em"
fi
  • Although the -n can be omitted, which will work as if you have the -n flag inside the test.

  • Generally the if clause and the [[ or [ is not needed if you're just going to test for a pattern match using grep.

1

Alternatively, get rid of the "if" entirely:

test -f file_a && grep -q "b" file_b && grep -q "c" file_c && echo "Got em"
0

grep has exit code 0 on success, but the test wants a truthy value. You have to invert the exit code of grep or check that it exited with 0:

if [[ -f file_a && \
      ! $(grep -q "b" file_b) && \
      ! $(grep -q "c" file_c) ]]; then
  echo "Got 'em"
fi

or

if [[ -f file_a && \
      $(grep -q "b" file_b) -eq 0 && \
      $(grep -q "c" file_c) -eq 0 ]]; then
  echo "Got 'em"
fi

Ideally there's a more readable way than putting it into a subshell with $().

1
  • 2
    That doesn't work; $(cmd) returns stdout not exit status. Plus as Jetchisel notes, [[ (like [, and test is actually [ not [[) defaults to string-nonempty not arith-nonzero. You can do $(grep -q b file_b; echo $?) -eq -0 -- or using a more useful output $(grep -c b file_b) -gt 0. Oh and [[ .. ]] is shell syntax so you don't need to backslash the newlines. Apr 28, 2020 at 6:05

You must log in to answer this question.

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