The everyday options: -i -v -w -n -o -c -l -h -r¶
Level: 101 · you use grep every day and move between a Linux server and a Mac
One line: The options typed every day — -i -v -w -n -o -c -l -h -r -E -e — printed the same bytes with GNU grep and BSD grep, as long as -r is given a directory. Two of them surprise on both machines alike: -w counts a hyphen as the end of a word, and a pattern that starts with - is read as options unless -e or -- comes first. What differs is the shell: ** as a recursive glob needs shopt -s globstar, which macOS's bash 3.2 does not have.
Measured¶
On two log files, demo/web.log and demo/db/db.log:
Verified output of grepopts_sh.sh, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ grep INFO web.log
INFO darkfi node started
$ grep -i info web.log
INFO darkfi node started
info alma9 update applied
$ grep -v INFO web.log
WARN disk almost full
ERROR darkfi-wallet crashed
info alma9 update applied
$ grep -iv info web.log
WARN disk almost full
ERROR darkfi-wallet crashed
$ grep -w darkfi web.log
INFO darkfi node started
ERROR darkfi-wallet crashed
$ grep -w dark web.log; echo "status $?"
status 1
$ grep -n ERROR web.log
3:ERROR darkfi-wallet crashed
$ grep -o "darkfi[-a-z]*" web.log
darkfi
darkfi-wallet
$ grep -c ERROR web.log db/db.log
web.log:1
db/db.log:1
$ grep -l ERROR web.log db/db.log
web.log
db/db.log
$ grep -h ERROR web.log db/db.log
ERROR darkfi-wallet crashed
ERROR connection refused
$ grep -r ERROR . | sort
./db/db.log:ERROR connection refused
./web.log:ERROR darkfi-wallet crashed
$ grep -rl darkfi . | sort
./web.log
$ grep -E "ERROR|WARN" web.log
WARN disk almost full
ERROR darkfi-wallet crashed
$ grep -e -wallet web.log
ERROR darkfi-wallet crashed
$ grep -- -wallet web.log
ERROR darkfi-wallet crashed
$ grep -wallet web.log; echo "status $?"
status 1
$ grep -w -a -l -l -e t web.log; echo "status $?"
status 1
-imatchedINFOandinfo.-vprinted the lines withoutINFO, and-ivdropped both spellings.-w darkfimatcheddarkfi-wallet. The-afterdarkfiended the word.-w darkmatched nothing and exited 1, becausedarkis only the start of the worddarkfi.-nput the line number in front.-oprinted only what matched, one match per line:darkfianddarkfi-walletfromdarkfi[-a-z]*.- With two files,
-cand-lanswer per file, and a matching line carries its file's name.-hdrops the name. -rwith a directory put./in front of every name on both machines. The output goes throughsortso that the order does not depend on how each machine lists a directory. Without the directory the two greps print the names differently: GNU grep and BSD grep.-Emakes|an alternation:ERROR|WARN.- A pattern that starts with
-.-e -walletand-- -walletboth found the line. Without them,grep -wallet web.logprinted nothing and exited 1, with no error: grep read-walletas a bundle of one-letter options. Spelled out,-w -a -l -l -e t, a search for the wordt, gave the same result.
On a Mac¶
Nothing changes in grep: the key above is shared. What changes is a shell feature a reader might reach for instead of -r, the recursive glob **:
Verified output of grepopts_globstar_sh.sh on Linux — regenerated by tools/run_examples.py, never hand-typed.
$ echo **/*.log
db/db.log
$ shopt -s globstar; echo "status $?"; echo **/*.log
status 0
db/db.log web.log
Verified output of grepopts_globstar_sh.sh on macOS — regenerated by tools/run_examples.py, never hand-typed.
$ echo **/*.log
db/db.log
$ shopt -s globstar; echo "status $?"; echo **/*.log
grepopts_globstar_sh.sh: line 6: shopt: globstar: invalid shell option name
status 1
db/db.log
- Without
globstar, bash reads**as*, so**/*.logmatched onlydb/db.log, one directory down, and missedweb.logwith no message. That line is the same on both machines. - On Linux,
shopt -s globstarreturned 0, and**/*.logthen matched the log files at every depth,web.logincluded. - On macOS,
/bin/bash3.2 rejected it:invalid shell option name, status 1, and the glob stayed one directory deep. Theline 6in the message is the script'ssayhelper.
grep -r --include='*.log' ERROR . needs nothing from the shell, and GNU grep and BSD grep measures --include on both machines.
In zsh and fish¶
The options are grep's, so neither shell changes them. Both have a recursive glob that is always on. zsh spells it **/:
Verified output of grepopts_zsh.zsh, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ grep -c ERROR web.log db/db.log
web.log:1
db/db.log:1
$ echo **/*.log
db/db.log web.log
$ grep -c ERROR **/*.log
db/db.log:1
web.log:1
$ grep -e -wallet **/*.log
web.log:ERROR darkfi-wallet crashed
fish spells it ** on its own:
Verified output of grepopts_fish.fish, identical on Linux and macOS — regenerated by tools/run_examples.py, never hand-typed.
$ grep -c ERROR web.log db/db.log
web.log:1
db/db.log:1
$ echo **.log
db/db.log web.log
$ grep -c ERROR **.log
db/db.log:1
web.log:1
$ grep -e -wallet **.log
web.log:ERROR darkfi-wallet crashed
**/*.log in zsh and **.log in fish both matched db/db.log web.log on both machines, so grep -c ERROR counted both files. A pattern that starts with - still needs -e in either shell, because it is grep, not the shell, that reads it as options.
| bash 3.2 (macOS) | bash 5.2 (Linux) | zsh | fish | |
|---|---|---|---|---|
every .log below here |
grep -r --include='*.log' p . |
shopt -s globstar, then grep p **/*.log |
grep p **/*.log |
grep p **.log |
If you are coming from another library¶
- Rust.
fd,rg,bat: what the Rust rewrites actually bought ↗ measuresrgagainstgrep -ron a real repository, and names the default that makesrgskip filesgrep -rreads:.gitignore.
See also¶
- GNU grep and BSD grep —
-rwith no directory,-R,-Z,-P - grep exits 0, 1 or 2 —
-qand-s cat file | grepis one process too many —-c,-l,-nand-hwhen grep reads a pipe- GNU grep manual ↗ and FreeBSD
grep(1)↗ - bash manual, The Shopt Builtin ↗ —
globstar - fish language, wildcards ↗