# Strict Mode: set -euo pipefail

set -euo pipefail makes a script stop at the first sign of trouble: -e stops at a command that fails, -u at a variable that was never set, and -o pipefail at a pipeline with a failing part, with a few surprises to know about: a grep that finds nothing, ((count++)) and a missing $1.

Freelancer · Unix · Linux · Updated November 10, 2026

## After this lesson you can

- Turn on strict mode with set -euo pipefail, and say what -e, -u and -o pipefail each do
- Work around strict mode's surprises in a script, such as a grep that finds nothing, ((count++)) and a missing $1

## Key takeaways

- -e stops the script at the first command that fails, instead of carrying on in the wrong folder or with the wrong data.
- -u makes a variable that was never set an error, so a typo such as $photo_dri stops the script instead of becoming nothing.
- -o pipefail makes a pipeline fail when any part of it fails, not only the last command.
- The surprises: grep that finds nothing fails, ((count++)) fails when count is 0, and "$1" fails when there's no argument. Write || true, count=$((count + 1)) and "${1:-}".
- Strict mode is for scripts. A failing test after if, or a command before || or &&, doesn't stop the script.

A script runs every line you give it, even after one has failed. That's like a cook who finds the oven broken and carries on, putting the cake mix in a cold oven and setting the timer anyway. If the failed line was a `cd`, every line after it works in the wrong folder; if it was a typo in a variable's name, the script works on nothing at all. Careful scripts start with one line that changes this, `set -euo pipefail`, often called **strict mode**: stop at the first sign of trouble. Administrators put it at the top of deployment and backup scripts, where carrying on after a failure does real damage.

The examples are small scripts in the `mover` practice folder from *Errors a User Can Act On*. Make each with `nano`, `chmod +x` it, and run it.

## -e: stop at the first failure

`tidy.sh` is meant to tidy the inbox, but the folder's name is mistyped:

```bash
#!/usr/bin/env bash
cd inbx
echo "Tidying $(pwd)"
```

```bash
./tidy.sh
echo $?
```

```text
./tidy.sh: line 2: cd: inbx: No such file or directory
Tidying /home/sam/terminal-practice/mover
0
```

`cd` failed, and the script carried on in the wrong folder, then reported success. If the next line deleted old files, it would delete them there. Add `set -e` (read "set, exit on error") as the second line, and run it again:

```text
./tidy.sh: line 3: cd: inbx: No such file or directory
1
```

It stopped at the failure, and its exit code says so.

## -u: a misspelled variable is an error

In `where.sh`, the variable is `photo_dir` but the `ls` line says `photo_dri`:

```bash
#!/usr/bin/env bash
photo_dir=photos
ls "$photo_dri/"
```

```bash
./where.sh
```

```text
bin   dev  home  lib    lost+found  mnt  proc  run   snap  sys  usr
boot  etc  init  lib64  media       opt  root  sbin  srv   tmp  var
```

A variable that was never set counts as empty, so `"$photo_dri/"` became `/`, and `ls` listed the top of the whole computer. With `rm` in place of `ls`, that's how scripts have deleted far more than they meant to. Add `set -u` ("unset variables are errors") as the second line:

```text
./where.sh: line 4: photo_dri: unbound variable
```

*Unbound* is bash's word for "never set".

## -o pipefail: every part of a pipe counts

A pipeline's exit code is normally its last command's. In `waiting.sh`, with `set -e` already on, `ls` fails, but `wc` counts nothing happily:

```bash
#!/usr/bin/env bash
set -e
waiting=$(ls inbx | wc -l)
echo "$waiting photos are waiting."
```

```bash
./waiting.sh
```

```text
ls: cannot access 'inbx': No such file or directory
0 photos are waiting.
```

Change the second line to `set -eo pipefail` (read "set option pipefail"), and the pipeline fails when any part of it fails: the script stops after `ls`'s message, with exit code 2, `ls`'s own. All three together are written `set -euo pipefail`, on the line after the comment at the top of a script.

## The surprises

Strict mode is strict about some things that aren't mistakes:

- **A `grep` that finds nothing** gives exit code 1, so `pngs=$(ls inbox | grep -c png)` stops the script silently when there are no PNG files. Add `|| true` (read "or else, carry on"): `pngs=$(ls inbox | grep -c png || true)` prints `0 PNG files`.
- **`((count++))`**, a shortcut for adding 1 that you'll see in other people's scripts, gives exit code 1 when `count` was 0, and the script stops silently after the first item. Write `count=$((count + 1))`, which always works.
- **`"$1"` with no arguments** is an unset variable. Here's `mover.sh` with strict mode on line 3, run with nothing:

```text
./mover.sh: line 11: $1: unbound variable
```

The usage message never got its chance. Write `"${1:-}"` on that line (read "the value of 1, or nothing if there isn't one"):

```bash
if [[ "${1:-}" == "--help" ]]; then
```

```bash
./mover.sh
echo $?
```

```text
Usage: mover.sh FROM-FOLDER TO-FOLDER
2
```

Two things don't stop a strict script, on purpose: a failing test after `if` or `while`, and a command before `||` or `&&`. That's why `[[ -d "$src" ]] || die "..."` still works. And keep strict mode in scripts: typed at your prompt, `set -e` closes the terminal at your first typo.

## Check yourself

2 questions to try on the lesson page.

## So what

`set -euo pipefail` turns "carry on and hope" into "stop and say why", and knowing its three surprises means it never stops a script that was doing the right thing.

## Key terms

- [Strict Mode](https://www.ask-codey.com/glossary/strict-mode-unix)
- [Exit Code](https://www.ask-codey.com/glossary/exit-code)
- [Pipeline](https://www.ask-codey.com/glossary/pipeline-unix)
- [Shell Variable](https://www.ask-codey.com/glossary/shell-variable)

## Cheat sheets

- [Strict Mode: set -euo pipefail](https://www.ask-codey.com/cheat-sheets/bash-strict-mode-cheat-sheet)

## Related

- [Errors a User Can Act On](https://www.ask-codey.com/lessons/bash-script-errors)
- [Cleaning Up: trap and mktemp](https://www.ask-codey.com/lessons/bash-trap-and-temp-files)
- [Did It Work? Exit Codes, && and ||](https://www.ask-codey.com/lessons/bash-exit-codes)
- [Pipes: |](https://www.ask-codey.com/lessons/bash-pipes)

Source: https://www.ask-codey.com/lessons/bash-strict-mode
