How to perform a command without causing the script to abort, either by not executing the command or forcing a success result.

The assumption is this set as start of the script such that any errors would cause the script to abort.

set -e

Default

Assume that CMD references a failing command such as running copy, move or remove on a file or folder that is not valid to operate on. e.g. rm foo.txt when foo.txt does not exist.

CMD

Run echo $? after and you’ll see 1 or another error status code.

You’ll also have any error messages logged on stderr.

Continue without aborting

OR and true to force success

If a step is optional or will only be needed sometimes (like deleting a temporary file which won’t exist on the very first run).

CMD || true

If you run echo $? you’ll see 0 for success, as it uses the exit code for the entire line above it (which will come from the last executed piece).

This works for variables and subshells too.

Example - here using npm outdated which gives an error if there are packages to update, and capturing the output in a variable without aborting.

$ OUTDATED=$(npm outdated)
$ echo $?
1
$ OUTDATED=$(npm outdated) || true
$ echo $?
0

Note, you could use ; instead, but then the second bit will always run, which is unnecessary.

If statement

Here we check if packages are up to date (code 0) or outdated (code 1). Also output is silenced.

if npm outdated > /dev/null; then
  echo 'Nothing to update'
  exit 0
fi

echo 'Upgrading'
npm update

Here capturing the output. And assuming set -e not set, so that a command can fail and its exit status can be used.

OUTDATED=$(npm outdated)

if [[ "$?" -eq 0 ]] > /dev/null; then
  echo 'Nothing to update'
  exit 0
fi

echo 'Outdated packages:'
echo "$OUTDATED"
echo

echo 'Upgrading'
npm update

Hide error output

Silence stderr. Keep stdout.

CMD 2> /dev/null

See Redirection cheatsheet.

Continue without aborting and fail silently

Combine the sections above:

CMD 2> /dev/null || true

Run conditionally

You could add a targeted if statement to check that a file or folder is already there or not, rather than using a catchall as in the previous sections.

You could check if file exists and is readable and is executable. Or just pick one of those tests.

# Optional command
if [[ -f foo.txt ]]; then
  rm foo.txt
fi

# More commands

Here is a directory check in one line.

# Optional command.
[[ -d foo ]] && rm -rf foo

# More commands

Maybe you want to check it is empty.

[[ -z foo.txt ]]

There could be multiple reasons for the command to fail and you’d have to catch them all.

Here is a DRY approach (replace source with echo to just print).

test -f ~/.git-completion.sh && source $_

That uses the last argument of previous command on the same line and uses it in place of $_.

Now this only works with the test CONDITION syntax. Using [[ CONDITION ]] ends up using ]] instead of the path.

Compare with this (which works only works in interactive mode I think):

test -f ~/.git-completion.sh
source !$

Also in ZSH, you have to press enter a second time at the end to confirm the substitution.

Set error flag

You could also reverse the error flag so that errors are not fatal. This can be especially useful for ignoring errors on a series of lines.

set -e

# Do stuff.

set +e
# Do stuff that can fail.

set -e

# Do more stuff.