122

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?

3
  • 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. Commented Dec 15, 2016 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
    Commented Jan 14, 2017 at 14:42
  • 1
    Knowing now that you intended to capture the output does clarify some things. As presented, it was confusing to me. Commented Jan 14, 2017 at 15:19

8 Answers 8

112
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).

5
  • 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
    Commented Dec 15, 2016 at 16:45
  • 2
    @DopeGhoti, true is a built-in in some shells (at least on bash 4.3 on RHEL)
    – iruvar
    Commented Dec 15, 2016 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
    Commented Jan 17, 2019 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
    Commented Jul 26, 2021 at 16:33
  • This hides true errors as well, i.e. errors when exit code is 2.
    – nishantjr
    Commented Mar 9, 2023 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.
3
  • 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. Commented Aug 29, 2019 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: unix.stackexchange.com/a/581991/58450.
    – Tim Malone
    Commented Sep 22, 2020 at 23:17
  • if using bash, status of echo can be checked via ${PIPESTATUS[0]} Commented Sep 23, 2020 at 3:54
29

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.

3
  • is this bash specific?
    – CervEd
    Commented May 18, 2022 at 9:52
  • 1
    @CervEd The solution is generally applicable, but the pipefail option as motivation is bash-specific yes.
    – OJFord
    Commented Aug 11, 2022 at 11:28
  • 1
    This should be the accepted answer since it only evades the 1-exit code failure of grep, not that of previous commands.
    – CodeMonkey
    Commented Jan 9, 2023 at 13: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.

5
  • This worked for me when piping find into grep from Node.js.
    – Tom
    Commented Jan 13, 2020 at 3:53
  • 2
    Maybe this is a case of Useless Use of Cat Award. If I need a command that does not fail I'd rather use : (or true) as stated in @cyker answer which doesn't have any other meaning than that. The : symbol infact is just a alias for true (i.e. don't fail). More info here.
    – a.barbieri
    Commented Feb 7, 2020 at 10:29
  • 2
    @a.barbieri Not useless; you can't use : in a pipe. If you would chain the command with a logical or, than yes, but that's not what this solution does. Commented Feb 7, 2020 at 13:40
  • 1
    I see. You are right, I didn't mension that I would use || : in place of | cat, meaning I would not have done | :.
    – a.barbieri
    Commented Feb 7, 2020 at 13:47
  • 9
    This won't work if using -o pipefail.
    – Asclepius
    Commented Nov 20, 2020 at 15:25
12

Another option:

...
set +e
echo "anything" | grep e
set -e
...
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.

1
  • It should be { grep e || :; } rather than grep e || :.
    – Asclepius
    Commented Nov 27, 2020 at 5:40
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.
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.

You must log in to answer this question.

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