skip to content

In bash, what does `if` actually evaluate — for example in `if grep -q ERROR app.log; then ...` — and what does the `!` do in `if ! grep -q ERROR app.log`?

level: middleimportance: must knowfreq 68%

answer

  1. it runs a command, not an expression
  2. branch is chosen by the status
  3. zero takes the then branch
  4. ! flips zero and non-zero
  5. no boolean type exists in the language

basics

~20 s

Bash's if runs a command and branches on its exit status: status 0 takes the then branch, anything else takes elif/else. There is no boolean expression type. A leading ! inverts that status, so the then branch runs when the command fails.

solid answer

~50 s

`if` in bash is not an expression evaluator — it runs a command list and looks at the exit status of the last command in it. Status `0` means the `then` branch runs; any non-zero status falls through to `elif` or `else`. That is why `if grep -q ERROR app.log; then` works directly: `grep -q` prints nothing and reports `0` when it matched. The square-bracket forms people usually write are just commands too, which is why `if [ ... ]` fits the same rule with no special casing. A leading `!` negates the status of the list, mapping `0` to `1` and any non-zero to `0`, so `if ! grep -q ERROR app.log` means "if there was no match". The practical consequence is that you should almost never write `cmd; if [ $? -eq 0 ]` — put the command in the `if` and let the shell read the status for you.

code

bash · 10 lines
bash
#!/usr/bin/env bash
printf 'INFO started\nERROR disk full\n' > app.log

if grep -q ERROR app.log; then
  echo "errors present"
fi

if ! grep -q FATAL app.log; then
  echo "no fatal entries"
fi

go deeper

for a junior

Be able to say that if runs a command and takes the then branch when the command's status is 0, and write if ! cmd; then for the negative case.

for a middle

Explain the mechanic precisely: the condition is a command list, only the last command's status decides the branch, and the bracket forms are themselves commands slotted into that position.

for a senior

Demonstrate the review habit — replace cmd; if [ $? -eq 0 ] with the command inside the if, keep side effects out of condition lists, and use status-only commands such as command -v for dependency checks.

for a principal

Own the readability argument: conditionals that test the real operation instead of a proxy for it eliminate a class of drift bugs, and that is worth encoding in the team's shell style guide and lint configuration.

## `if` takes a command, not a condition The grammar of the statement is: ```bash if list; then list; [elif list; then list;]... [else list;] fi ``` Every slot is a **command list** — one or more commands. Bash executes the list after `if`, takes the exit status of the *last* command in that list, and branches: `0` runs the `then` list; anything non-zero moves on to `elif`, then `else`, then falls out of the statement. There is no boolean type in the language, no expression parser, and no truthiness rule for strings. A conditional in bash is literally "run this, then look at the number it returned". Internalising that sentence explains almost every surprise beginners hit with shell conditionals. ## Consequences worth stating in an interview **Any command can be a condition.** These are all ordinary, idiomatic bash: ```bash if grep -q ERROR app.log; then echo "errors present"; fi if ping -c1 -W1 db.internal >/dev/null 2>&1; then echo up; fi if mkdir /var/lock/mine 2>/dev/null; then echo "got the lock"; fi ``` The third is instructive: `mkdir` is being used purely for its status, and its output is discarded. Commands that print nothing and exist to report a yes/no answer — the `-q` family of flags — exist precisely because the shell branches on status. **The bracket forms are commands as well.** `[ ... ]` is an ordinary command whose final argument is `]`, and `[[ ... ]]` is a shell keyword; the differences between them are a topic of their own. What matters here is that `if` does not know or care which one you used — it applies the same "run it, read the status" rule to both, which is why they slot into the same position as `grep`. **Multiple commands in the condition are legal.** `if cd /srv/app && ./health.sh; then` runs both and branches on the status of the whole list. Only the final status decides the branch, so keep the condition list short and readable. **Testing `$?` explicitly is a smell.** Because the value of `$?` is replaced by every completed command, `cmd; echo "ran"; if [ $? -eq 0 ]` silently tests `echo`. Putting the command inside the `if` removes the whole failure mode. ## What `!` does `!` is the shell's negation operator for a pipeline or list: it runs the thing, then flips the status. `0` becomes `1`, and *any* non-zero becomes `0` — note that the specific failure code is lost, so `!` is a boolean inverter, not a code translator. ```bash if ! grep -q ERROR app.log; then echo "log is clean" fi ``` This reads better than an empty `then` branch with the real work in `else`, and it composes with anything: `if ! command -v jq >/dev/null; then echo "jq is required" >&2; exit 1; fi` is the standard dependency-check idiom. Note that `!` must be a separate word — `!command` is not negation, and inside a double-quoted string `!` may be interpreted by history expansion in an interactive shell, though scripts are unaffected. ## The status of the `if` statement itself The compound statement produces its own exit status: that of the last command executed in whichever branch ran. If no branch runs — the condition failed and there is no `else` — the `if` statement's status is `0`. This surprises people who expect a failed condition to make the `if` "fail"; it does not, and that is deliberate, because a conditional that took the non-match path has not itself gone wrong. One related interaction is worth naming and no more: a command used as an `if` condition is exempt from the abort-on-error behaviour of `set -e`, which is why strict-mode scripts can still test commands that are allowed to fail. The detail of that exemption belongs to strict mode and error handling. ## Style notes Prefer `if ! cmd; then` over an inverted comparison against `$?`. Prefer running the actual command over asking a second command about it — `if [ -d "$dir" ]` when you truly want a file test, but `if cd "$dir"` when you were going to change into it anyway and care whether that worked. And keep the condition free of side effects that a reader would not expect, because the condition list really does execute. ## What to say in an interview "`if` runs a command and branches on its exit status — 0 takes the then branch. There is no boolean expression type; the bracket forms are themselves commands. `!` inverts the status, so `if ! cmd` runs the then branch when the command fails."

  • What exit status does the `if` statement itself produce when the condition fails and there is no else branch?
    Zero. If no branch runs, bash reports success for the compound statement, because a conditional that simply took the no-match path has not failed. When a branch does run, the `if` reports the status of the last command in that branch. This matters at the end of a script, where the final `if` can quietly determine the script's own exit code.
  • Why is `if command -v jq >/dev/null; then` preferred over checking whether a path exists?
    `command -v` asks the shell the same question the shell will ask when it runs the command: it honours PATH, functions, aliases and builtins, and reports non-zero when nothing resolves. Testing a hardcoded path such as /usr/bin/jq encodes one layout and misses tools installed elsewhere. Redirect its output away, since you want only the status.
  • Can the condition of an `if` contain more than one command?
    Yes — the condition is a list, so `if cd /srv/app && ./health.sh; then` is valid, and the branch is chosen by the status of the last command in that list. It executes for real, side effects included, so keep it short and avoid hiding meaningful work there; a reader scanning the script expects the condition to test, not to do.

saying these in an interview costs you the question

  • Says bash conditionals evaluate a boolean expression
  • Thinks non-empty strings are automatically true
  • Writes cmd then tests $? instead of putting cmd in the if
  • Believes [ is special syntax rather than a command
  • Thinks ! negates the command's output rather than its status

context