118

This script does not output after:

#!/bin/bash -e

echo "before"

echo "anything" | grep e # it would if I searched for 'y' instead

echo "after" exit

I could solve this by removing the -e option on the shebang line, but I wish to keep it so my script stops if there is an error. I do not consider grep finding no match as an error. How may I prevent it from exiting so abruptly?

Kusalananda
  • 333,661
iago-lito
  • 2,751
  • 1
    This is an observation meant only for consideration. Perhaps the logic of this script should be thought through again. If it is not important to find the string, why search for it? grep's definition is such that one makes decisions based upon a string's presence or absence. If you don't care either way, then it isn't important. Also, it would seem -e presupposes you do care: so much so that any problem is catastrophic. – Andrew Falanga Dec 15 '16 at 19:38
  • 3
    @AndrewFalanga I do care either way since I am actually analysing the content of var=$(complex command | grep complex_pattern) which may be null (in which case my program should not terminate). This is just a boiled down script which makes the problem occur. No metaphysical blackhole in the logic here, right? ;) – iago-lito Jan 14 '17 at 14:42
  • 1
    Knowing now that you intended to capture the output does clarify some things. As presented, it was confusing to me. – Andrew Falanga Jan 14 '17 at 15:19

8 Answers8

109
echo "anything" | { grep e || true; }

Explanation:

  • This will throw an error
    $ echo "anything" | grep e
    $ echo $?
    1
    
  • This will not throw an error
    $ echo "anything" | { grep e || true; }
    $ echo $?
    0
    
  • DopeGhoti's "no-op" version (Potentially avoids spawning a process, if true is not a builtin), this will not throw an error
    $ echo "anything" | { grep e || :; }
    $ echo $?
    0
    

The || means "or". If the first part of the command "fails" (meaning grep e returns a non-zero exit code) then the part after the || is executed, succeeds and returns zero as the exit code (true always returns zero).

AdminBee
  • 22,803
John N
  • 1,951
  • 4
    A slightly shorter version of the same that doesn't spin up /bin/true is: command || : (so in your case, set -e; grep 'needle' haystack || :). – DopeGhoti Dec 15 '16 at 16:45
  • 2
    @DopeGhoti, true is a built-in in some shells (at least on bash 4.3 on RHEL) – iruvar Dec 15 '16 at 16:57
  • 11
    Not valid because if first command fails it will hide the error. A correct solution should return non zero if the first command in the pipe fails. – sorin Jan 17 '19 at 17:47
  • 1
    If you use set -o pipefail it does properly fail if the first command in the pipe fails. If this option is not used, it is normal that only exit value of the last command in a pipe defines the exit code of the pipe. If you do set +o pipefail; false | true (the default), you get success, if you do set -o pipefail; false | true, you get failure. – Vampire Jul 26 '21 at 16:33
  • This hides true errors as well, i.e. errors when exit code is 2. – nishantjr Mar 09 '23 at 20:00
61

A robust way to safely and optionally grep messages:

echo something | grep e || [[ $? == 1 ]] ## print 'something', $? is 0
echo something | grep x || [[ $? == 1 ]] ## no output, $? is 0
echo something | grep --wrong-arg e || [[ $? == 1 ]] ## stderr output, $? is 1

According to posix manual, exit code:

  • 1 means no lines selected.
  • > 1 means an error.
Freddy
  • 25,565
  • 11
    This should be the accepted answer, since it only suppresses the warning exit code (1) if grep does not find anything, yet it passes on true errors (exit codes > 1). The other solutions here always suppress true errors, which is usually bad. – HaroldFinch Aug 29 '19 at 21:26
  • 3
    This is great, but suffers from one issue - if the echo command exits with 1, the whole line will exit with 0, not just the grep. It might be worth checking this answer: https://unix.stackexchange.com/a/581991/58450. – Tim Malone Sep 22 '20 at 23:17
  • if using bash, status of echo can be checked via ${PIPESTATUS[0]} – James Z.M. Gao Sep 23 '20 at 03:54
28

If you're using grep with set -euo pipefail and don't want to exit if no record is found (exit code = 1), but still want it to fail if it's another exit code, you can use this:

#!/bin/bash
set -euo pipefail

echo "anything" | { grep e || test $? = 1; } | { grep e2 || test $? = 1; }

The greps inside the pipes will ONLY ignore exit status = 1. This way you don't need to lose the benefits of using set -euo pipefail.

In this example I purposely included two piped greps that could fail to illustrate the concept.

Refer to myrdd's post there.

Wadih M.
  • 1,746
  • 1
  • 17
  • 23
25

Another option is to add another command to the pipeline - one that does not fail:

echo "anything" | grep e | cat

Because cat is now the last command in the pipeline, it's the exit status of cat, not of grep, that will be used to determine if the pipeline failed or not.

12

Another option:

...
set +e
echo "anything" | grep e
set -e
...
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
8

Solution

#!/bin/bash -e

echo "before"

echo "anything" | grep e || : # it would if I searched for 'y' instead

echo "after"
exit

Explanation

set -e or set -o errexit

Exit immediately if a pipeline (which may consist of a single simple command), a list, or a compound command (see SHELL GRAMMAR above), exits with a non-zero status. The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test following the if or elif reserved words, part of any command executed in a && or || list except the command following the final && or ||, any command in a pipeline but the last, or if the command's return value is being inverted with !. If a compound command other than a subshell returns a non-zero status because a command failed while -e was being ignored, the shell does not exit. A trap on ERR, if set, is executed before the shell exits. This option applies to the shell environment and each sub‐ shell environment separately (see COMMAND EXECUTION ENVIRONMENT above), and may cause subshells to exit before executing all the commands in the subshell.

Plus, : is the no-effect command in Bash.

Nick T
  • 318
Cyker
  • 4,274
  • 7
  • 36
  • 46
1

The issue I have with most, and perhaps all the answers here, is that they are losing the true exit code ($?) and mostly returning $? as 0. There is no way to differentiate if there was a match (exit code 0) or no match (exit code 1). This solution addresses that concern:

#!/bin/bash -e

echo "before"

exit_code=0 echo "anything" | grep e || exit_code=$?; if [[ $exit_code -ne 1 ]]; then (exit $exit_code); fi

echo "after" exit

Explained

grep's exit status is either 0, 1 or 2:

  • 0 means there was a match
  • 1 means there was no match
  • 2 means an error occurred

Now, with the above solution:

  • If grep returns 0, the test is not run and returns the true $? exit code of 0, and also sets $exit_code=0.
  • If grep returns 1, the test is run and returns a fake $? exit code of 0, but we also sets $exit_code=1 which can be used by future code to differentiate a match vs no match.
  • If grep returns any other value, the test is run and returns the true greater than 1 error exit code that should correctly halt the script, we also set $exit_code appropriately, but that won't matter because the script will halt.
rouble
  • 1,951
1

Alternative: Use sed instead of grep:

echo "anything" | sed -n '/e/p'  # prints nothing
echo "anything" | sed -n '/y/p'  # prints "anything"

Explanation: sed is forced to suppress any input (-n), except to print it (p) when it matches the regex.

The return value is 0 in both cases. Works on a modern Linux and an old Solaris 8.

Johannes
  • 373