grep exits 0, 1 or 2¶
Level: 101 · you wrote if grep -q ... and want to know what it cannot tell you
One line: grep exits 0 when it selected a line, 1 when it selected none, and 2 when something went wrong: a missing file, a bad pattern. if grep -q sees only zero or not zero, so a file that is not there reads as "not found"; a case on $?, or fish's switch $status, tells the three apart. grep -c prints 0 and still exits 1, and set -e stops a script at a grep that matched nothing.
Measured¶
On demo/hosts.txt, four lines, two of them with darkfi:
Verified output of grepexit_sh.sh, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ grep darkfi hosts.txt; echo "status $?"
darkfi 10.0.0.20 node
darkfi 10.0.0.21 wallet
status 0
$ grep zzz hosts.txt; echo "status $?"
status 1
$ grep darkfi nosuch.txt; echo "status $?"
grep: nosuch.txt: No such file or directory
status 2
$ grep "[" hosts.txt 2>/dev/null; echo "status $?"
status 2
$ grep -q darkfi hosts.txt; echo "status $?"
status 0
$ grep -c zzz hosts.txt; echo "status $?"
0
status 1
$ grep -s darkfi nosuch.txt; echo "status $?"
status 2
$ grep -s darkfi hosts.txt nosuch.txt; echo "status $?"
hosts.txt:darkfi 10.0.0.20 node
hosts.txt:darkfi 10.0.0.21 wallet
status 2
$ grep -qs darkfi hosts.txt nosuch.txt; echo "status $?"
status 0
$ if grep -q darkfi hosts.txt; then echo "found"; else echo "not found"; fi
found
$ if grep -q darkfi nosuch.txt 2>/dev/null; then echo "found"; else echo "not found"; fi
not found
$ grep -q darkfi nosuch.txt 2>/dev/null; case $? in 0) echo "found" ;; 1) echo "not found" ;; *) echo "grep failed" ;; esac
grep failed
$ if ! grep -q zzz hosts.txt; then echo "no zzz"; fi
no zzz
$ bash -c "set -e; grep -q zzz hosts.txt; echo reached"; echo "status $?"
status 1
$ seq 1 1000000 | grep -q 1; echo "PIPESTATUS ${PIPESTATUS[*]}"
PIPESTATUS 141 0
$ set -o pipefail; seq 1 1000000 | grep -q 1; echo "status $?"; set +o pipefail
status 141
- 0, 1 and 2 are found, not found, and could not look. A pattern grep cannot compile,
[, is also 2; its message is not the same on the two machines, which is why this line throws it away. GNU grep and BSD grep shows both wordings. -qprints nothing, so the status is the whole answer.grep -c zzzprinted0and exited 1. The count is an answer, but no line was selected, and the status says so.-ssilences the message, not the status.grep -s darkfi nosuch.txtprinted nothing and still exited 2.- A match in one file does not cancel an error in another.
grep -s darkfi hosts.txt nosuch.txtprinted both lines and exited 2. Add-qand the same search exits 0: with-q, one selected line is enough. ifcannot see the difference.if grep -q darkfi nosuch.txttook the "not found" branch, although nothing was searched. Thecaseon$?said "grep failed".set -etreats 1 as a failure. The child bash stopped atgrep -q zzzand never printedreached; its status was grep's 1.grep -qstops at the first match, and the writer pays for it.seqstill had lines to write when grep exited, and the next write killed it with SIGPIPE, 141. The pipeline's status is grep's 0; underpipefailit is 141, a failure nobody made. A pipeline reports its last command measures the same trap withyes | head.
On a Mac¶
Nothing changes: the key above is shared, and BSD grep uses the same three numbers as GNU grep for every line on it. The difference between the two is in a message: with -q and a missing file after the match, BSD grep prints the missing-file message and GNU grep prints nothing. The status is 0 on both, and the line above passes -s so the key can be shared. GNU grep and BSD grep records both.
In zsh and fish¶
zsh spells all of it as bash does, and keeps every stage's status in $pipestatus:
Verified output of grepexit_zsh.zsh, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ grep -q darkfi hosts.txt; echo "status $?"
status 0
$ grep -q zzz hosts.txt; echo "status $?"
status 1
$ grep -q darkfi nosuch.txt 2>/dev/null; echo "status $?"
status 2
$ if grep -q darkfi hosts.txt; then echo "found"; fi
found
$ grep -q darkfi nosuch.txt 2>/dev/null; case $? in (0) echo "found" ;; (1) echo "not found" ;; (*) echo "grep failed" ;; esac
grep failed
$ zsh -fc "setopt errexit; grep -q zzz hosts.txt; echo reached"; echo "status $?"
status 1
$ seq 1 1000000 | grep -q 1; echo "pipestatus $pipestatus"
pipestatus 141 0
The case patterns are written (0), with an opening parenthesis, which zsh accepts. setopt errexit is zsh's set -e, and it stopped at the grep that matched nothing just as bash did.
fish has its own words for every piece:
Verified output of grepexit_fish.fish, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ grep -q darkfi hosts.txt; echo "status $status"
status 0
$ grep -q zzz hosts.txt; echo "status $status"
status 1
$ grep -q darkfi nosuch.txt 2>/dev/null; echo "status $status"
status 2
$ if grep -q darkfi hosts.txt; echo "found"; else; echo "not found"; end
found
$ grep -q darkfi hosts.txt; and echo "found"
found
$ grep -q zzz hosts.txt; or echo "not found"
not found
$ if not grep -q zzz hosts.txt; echo "no zzz"; end
no zzz
$ grep -q darkfi nosuch.txt 2>/dev/null; switch $status; case 0; echo "found"; case 1; echo "not found"; case "*"; echo "grep failed"; end
grep failed
$ set n (grep -c zzz hosts.txt); echo "count $n, status $status"
count 0, status 1
$ if test (grep -c darkfi hosts.txt) -gt 1; echo "more than one"; end
more than one
$ seq 1 1000000 | grep -q 1; echo "pipestatus $pipestatus"
pipestatus 141 0
$ if [[ -s hosts.txt ]]; echo "not empty"; end
fish: Unknown command: '[[ -s hosts.txt ]]'
fish:
if [[ -s hosts.txt ]]; echo "not empty"; end
^~~~~~~~~~~~~~~~~^
(exit status 0)
$ [[ -s hosts.txt ]]; echo "status $status"
fish: Unknown command: '[[ -s hosts.txt ]]'
fish:
[[ -s hosts.txt ]]; echo "status $status"
^~~~~~~~~~~~~~~~~^
status 127
$status, not$?.$?is a parse error in fish, measured in the pipeline lesson.if grep -q ...; ...; endhas nothenand nofi.and,orandnottest the previous command's status, andswitch $statuswithcase "*"is the three-way answer.set n (grep -c zzz hosts.txt)kept grep's status:count 0, status 1.- There is no
[[ ]]. fish looked for a command named[[ -s hosts.txt ]]and printedUnknown command. On its own that is status 127. Insideif, the failed condition just skipped the body, and the child fish exited 0: a bash test pasted into a fish script prints an error and carries on as if the answer were no.test, as in thetest (grep -c darkfi hosts.txt) -gt 1line, works in fish. On an Ubuntu machine with thecommand-not-foundpackage installed, GitHub's runners among them, fish hands an unknown name to that package's helper instead, and the first line reads[[ -s hosts.txt ]]: command not found. The example pins fish's own handler so both machines show fish's words.
| bash | zsh | fish | |
|---|---|---|---|
| last status | $? |
$? |
$status |
| found? | if grep -q p f; then ...; fi |
the same | if grep -q p f; ...; end |
| not found? | if ! grep -q p f |
the same | if not grep -q p f |
| 0, 1 or 2 | case $? in 0) ... ;; 1) ... ;; *) ... ;; esac |
the same | switch $status; case 0; ...; case 1; ...; case "*"; ...; end |
If you are coming from another library¶
- Rust. Standard error, and exit status ↗ is the other side of this page: the status a program leaves behind is the one number a shell's
ifreads, and a program that prints an error and exits 0 has reported success to everything but a person. - Encodings.
grepon text that is not ASCII ↗ has the case where 0 is not the whole truth: in a UTF-8 locale, BSD grep skips a line it cannot decode, says nothing, and still exits 0.
See also¶
- A pipeline reports its last command —
PIPESTATUS,pipefail, and SIGPIPE cat file | grepis one process too many —cat nosuch.txt | grepexits 1, not 2- GNU grep and BSD grep
- GNU grep manual, Exit Status ↗
- bash manual, Conditional Constructs ↗ —
ifandcase - fish
switch↗ and fishif↗