opscape

Shell builtins · bash builtin

set

Turn shell options such as -e, -u, -x and pipefail on or off, and replace the positional parameters.

set [-abefhkmnptuvxBCEHPT] [-o OPTION] [--] [ARG...]

13 of 13 examples run in Debian 13.7 on 2026-10-31 · manual: bash(1), SHELL BUILTIN COMMANDS

set does two unrelated jobs. With option letters it switches shell behavior on (-x) or off (+x, a plus sign): exit on error, treat unset variables as errors, trace every command, and so on. Long names go through -o, as in set -o pipefail. With other arguments it replaces the positional parameters $1, $2, ... of the current shell or function, which is handy for splitting a string or resetting $@.

The options you will meet in scripts are -e (errexit), -u (nounset), -o pipefail, -x (xtrace), -f (no globbing) and -C (noclobber). set -euo pipefail at the top of a script catches many mistakes early, but -e in particular has a long list of situations where it does nothing; read Strict mode and its caveats before relying on it.

Run with no arguments, set prints every shell variable and function, which is long. set -o lists the options with their state, and $- holds the active single-letter flags. bash has a second, separate set of options, changed with shopt -s and shopt -u, for things like nullglob and globstar; those are not changed with set.

Examples

Stop a script at the first failing command with -e

set -e
echo "start"
ls missing-file
echo "never printed"
outputexit 2
start
ls: cannot access 'missing-file': No such file or directory

ls fails with status 2, and with -e the shell exits right there with the same status, so the last echo never runs. Without -e the script would carry on.

Where -e does not trigger

set -e
if grep -q CRITICAL logs/app.log; then echo found; fi
grep -q CRITICAL logs/app.log || echo "not found, still running"
false | true
echo "reached the end"
output
not found, still running
reached the end

A failing command used as an if condition, on the left of || or &&, or anywhere but last in a pipeline does not stop the shell. That is by design, and it is why -e is a safety net, not error handling.

Catch typos in variable names with -u

set -u
backup_dir=/tmp/backups
echo "cleaning ${backup_dri}"
echo "not reached"
outputexit 127
/bin/bash: line 3: backup_dri: unbound variable

With -u, expanding a variable that was never set is an error that aborts a non-interactive shell; bash 5.2 exits with status 127 here. Without it, the typo would quietly expand to an empty string, a dangerous thing in front of rm -rf.

Allow an optional variable under -u

set -u
echo "level: ${LOG_LEVEL:-info}"
echo "args: $#"
output
level: info
args: 0

${var:-default} is allowed under -u because it says what to do when the variable is unset. $#, $@ and $* never trigger it either (since bash 4.4, an empty "$@" is fine).

Make a pipeline fail when any part fails

grep ERROR missing.log | wc -l; echo "status without pipefail: $?"
set -o pipefail
grep ERROR missing.log | wc -l; echo "status with pipefail: $?"
output
grep: missing.log: No such file or directory
0
status without pipefail: 0
grep: missing.log: No such file or directory
0
status with pipefail: 2

A pipeline's status is normally that of its last command, and wc succeeded. With pipefail it is the status of the rightmost command that failed, here grep's 2, so set -e and if can see the error.

Trace each command before it runs with -x

set -x
name="file with spaces.txt"
wc -l "$name"
set +x
echo "tracing is off"
output
+ name='file with spaces.txt'
+ wc -l 'file with spaces.txt'
1 file with spaces.txt
+ set +x
tracing is off

Each command is printed to stderr with a + prefix after expansion, with quotes added where an argument contains spaces. set +x turns tracing off again (and is itself traced). This is the first tool to reach for when a script misbehaves.

Show line numbers in the trace with PS4

PS4='+ line $LINENO: '
set -x
n=$(wc -l < todo.txt)
echo "$n tasks"
output
++ line 3: wc -l
+ line 3: n=5
+ line 4: echo '5 tasks'
5 tasks

PS4 is the prefix of trace lines and is expanded each time, so $LINENO shows where each command is. Nested command substitutions get an extra +.

Replace the positional parameters

set -- alpha "beta gamma" delta
echo "count: $#"
printf '[%s]\n' "$@"
output
count: 3
[alpha]
[beta gamma]
[delta]

set -- throws away the current $1, $2, ... and sets new ones. The quoted argument stays one parameter. In a function this changes only the function's own arguments.

Split a string into fields with set

line="2026-03-14 08:00:01 ERROR db timeout"
set -- $line
echo "date=$1 level=$3 rest=${*:4}"
output
date=2026-03-14 level=ERROR rest=db timeout

The unquoted $line is word-split into five parameters. Unquoted expansion also globs, so a * in the data would turn into file names; use set -f first or read -a when the input is not trusted.

Why -- matters when the data starts with a dash

value="-x"
set $value
echo "\$1 is now: [${1-unset}]"
set +x
set -- $value
echo "\$1 is now: [$1]"
output
+ echo '$1 is now: [unset]'
$1 is now: [unset]
+ set +x
$1 is now: [-x]

Without --, set -x is taken as the option and tracing switches on (watch the + lines) instead of setting $1. With -- everything after it is a positional parameter.

Disable filename expansion with -f

pattern='*.txt'
echo $pattern
set -f
echo $pattern
output
file with spaces.txt notes.txt todo.txt
*.txt

Normally an unquoted *.txt expands to the matching files. After set -f (noglob) it is left alone. Quoting the variable is still the better fix in most cases.

Refuse to overwrite files with -C

set -C
echo "new notes" > notes.txt
echo "status: $?"
echo "forced" >| notes.txt && head -n 1 notes.txt
output
/bin/bash: line 2: notes.txt: cannot overwrite existing file
status: 1
forced

With noclobber, > will not truncate an existing file and the redirection fails. >| overrides it for one command. >> (append) is still allowed.

See which options are on

set -euo pipefail
echo "flags: $-"
set -o | grep -E '^(errexit|nounset|pipefail|xtrace|noclobber) '
output
flags: ehuBc
errexit        	on
noclobber      	off
nounset        	on
pipefail       	on
xtrace         	off

$- lists the active single-letter flags (h and B are on by default, c means the shell was started with -c). set -o prints every long option with on or off.

Common pitfalls

  • set -e is ignored inside if/while conditions, &&/|| lists, and functions called from them, so a function that relies on it can keep running after an error. See Strict mode and its caveats.

  • local x=$(cmd) and export x=$(cmd) always succeed, hiding a failing cmd from set -e. Declare first, then assign on its own line.

  • With set -u, "${arr[@]}" on an empty array was an error before bash 4.4, and $1 in a script called without arguments always is. Use ${1:-} for optional arguments.

  • set -x output goes to stderr and includes secrets passed on command lines. Turn it off around such commands, or send it elsewhere with BASH_XTRACEFD.

  • set $var without -- misbehaves when the value begins with - or +: it changes shell options instead of $1.

GNU vs BSD and macOS

The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).

  • -e, -u, -x, -f, -C, -v and set -- are POSIX and work in dash. -o pipefail is in POSIX.1-2024 and supported by bash, zsh, ksh and the dash 0.5.12 in Debian 13; older /bin/sh implementations may reject it, so test before relying on it in a #!/bin/sh script.

Options worth knowing

A selection, described in our own words. For the full list, see bash(1), SHELL BUILTIN COMMANDS.

OptionWhat it does
-eerrexit: exit when a command fails, outside of conditions and lists.
-unounset: treat expanding an unset variable as an error.
-xxtrace: print each command to stderr, after expansion, before running it.
-o OPTIONTurn on the named option (pipefail, errexit, noclobber, vi, ...); +o turns it off.
-fnoglob: disable filename expansion.
-Cnoclobber: make > fail on existing files; >| overrides.
-vverbose: print input lines as they are read, before expansion.
-nnoexec: read commands without running them (syntax check in scripts).
-aallexport: export every variable that is assigned.
-Eerrtrace: let functions and subshells inherit the ERR trap.
-Tfunctrace: let functions inherit DEBUG and RETURN traps.
-bnotify: report finished background jobs immediately.
-mmonitor: enable job control.
-hhashall: remember command locations (on by default).
-Bbraceexpand: perform brace expansion (on by default).
-Hhistexpand: enable ! history expansion (interactive default).
-Pphysical: do not follow symlinks when changing directory.
--End of options; remaining arguments become the positional parameters, and with none, they are cleared.

Related