Skip to content

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
  • -i matched INFO and info. -v printed the lines without INFO, and -iv dropped both spellings.
  • -w darkfi matched darkfi-wallet. The - after darkfi ended the word. -w dark matched nothing and exited 1, because dark is only the start of the word darkfi.
  • -n put the line number in front. -o printed only what matched, one match per line: darkfi and darkfi-wallet from darkfi[-a-z]*.
  • With two files, -c and -l answer per file, and a matching line carries its file's name. -h drops the name.
  • -r with a directory put ./ in front of every name on both machines. The output goes through sort so 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.
  • -E makes | an alternation: ERROR|WARN.
  • A pattern that starts with -. -e -wallet and -- -wallet both found the line. Without them, grep -wallet web.log printed nothing and exited 1, with no error: grep read -wallet as a bundle of one-letter options. Spelled out, -w -a -l -l -e t, a search for the word t, 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 **/*.log matched only db/db.log, one directory down, and missed web.log with no message. That line is the same on both machines.
  • On Linux, shopt -s globstar returned 0, and **/*.log then matched the log files at every depth, web.log included.
  • On macOS, /bin/bash 3.2 rejected it: invalid shell option name, status 1, and the glob stayed one directory deep. The line 6 in the message is the script's say helper.

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

See also