shell scripting

Why the shell colon matters: no-op, side effects, and safer scripts

Why the shell colon matters: no-op, side effects, and safer scripts

The colon that doesn’t “do” anything (but still changes everything)

Picture this: we’ve got a script to write, a deadline to hit, and a handful of required arguments that must not be missing. The obvious approach is the one we’ve all used—an if statement that checks $1, prints an error, and exits.

And then we notice a weird alternative: a lone : at the start of a line.

What does a colon at the start of a shell script even mean? The surprising answer is that in POSIX shells, : is the null command: it expands its arguments, discards the result, and returns success (0). (pubs.opengroup.org)

So yes, it “does nothing”… except it gives us a place to run expansions, redirections, and other side effects in a way the shell won’t accidentally misinterpret.

A one-line required-argument check: ${1:?…}

Let’s start with the part most people remember: parameter expansion.

Parameter expansion is what shells do when they replace text like ${name} with the current value of a variable named name. POSIX defines several “forms” of parameter expansion that can also enforce rules.

The form we care about is:

  • ${parameter:?word}: if parameter is unset or null (empty), the shell writes a diagnostic to standard error and exits with a non-zero status. ()

Here’s the familiar muscle-memory version:

if [ -z "$1" ]; then
 echo "missing argument, aborting." 1>&2
 exit 1
fi

echo "Hello $1!"

Now the “colon” version:

: "${1:?missing argument, aborting.}"

echo "Hello $1!"

The key idea: the check happens inside the ${1:?…} expansion, and the colon provides a safe command context for it.

Also, that subtle detail matters: with the :? variant, the : in the expansion syntax means the test covers both “unset” and “null/empty”. POSIX explicitly distinguishes “unset only” from “unset or null” using that colon. (pubs.opengroup.org)

The : command: “discard the output, keep the evaluation”

Now we get to the headline trick.

In POSIX shells, the null utility : expands command arguments and returns success. (pubs.opengroup.org)

That sounds harmless—until we remember what happens when the shell parses a line.

Without a command name, the shell may treat the resulting expansion text as if it were something to run.

So this pattern exists:

: "${VAR:?something went wrong}"

The parameter expansion is evaluated, and if it triggers an error it stops the script. If it succeeds, the expansion produces a value… but the : command discards it.

This is also why you’ll often see the colon used with double quotes: quotes keep empty strings and spaces intact during expansion.

Defaults without a separate if: ${var:=word}

Once we’re comfortable with ${var:?word}, the next natural step is defaults.

Parameter expansion also supports:

  • ${parameter:=word}: if parameter is unset or null, assign word to it. (adintr.com)

And again, we pair it with : to ensure the assignment happens while the shell doesn’t try to “run” the resulting text.

Example:

: "${DATA_DIR:=/var/data}"
: "${RETRIES:=3}"

These lines are conceptually “initialize configuration variables with sane defaults,” but they do it using shell-native expansion instead of additional control flow.

Redirection side effects: : > file truncates

Here’s where the colon feels less like trivia and more like a superpower.

In the shell, redirections like > and < are processed by the shell before the command runs. So even though : itself writes nothing, the redirection can still have a real effect.

That gives us a clean truncation idiom:

: > error.log

This works because > truncates (or creates) error.log, and then the : command runs and discards everything. POSIX even documents that : can be used with variable assignments and redirections together. (manpages.ubuntu.com)

You can chain more redirections too:

: > error.log > access.log

“Does a file exist?” tests via redirections

Sometimes we want boolean checks without calling test explicitly.

Because : returns success and the shell treats redirections as part of command setup, this becomes a neat pattern:

(: < dataset.json ) && echo "YES"
(: >> result.json ) && echo "YES"

What’s happening?

  • < dataset.json is an input redirection; it succeeds if the file is readable.
  • >> result.json is an output append redirection; it succeeds if the file is writable (or creatable).

The : command itself doesn’t read or write anything, but the redirection still determines success/failure, which is exactly what &&/|| need.

Traps need a command: trap: INT

Another reason the colon shows up everywhere: some shell features require an actual “command operand,” even if the intended action is “do nothing.”

POSIX requires that trap has at least one command operand in POSIXly-correct mode. (manpages.ubuntu.com)

So when we want a trap that effectively disables the action for a signal, : is a tidy fit:

trap: INT

The trap runs : when the signal arrives. And : is a success-returning no-op, so the signal doesn’t crash the script with an unexpected action.

Pair it with stricter modes: probing unset variables

Many people enable stricter error handling in shells that support it. In Bash-like shells, set -u (nounset) turns expanding an unset variable into an error. (man7.org)

Now combine that with : to force expansion at a controlled spot.

For example:

: "$DEPLOY_ENV" "$HOST"

If either variable is unset, the expansion happens while building arguments to :, and the shell complains according to the strict mode. If both are set (even to empty strings, depending on strict settings), the line succeeds and the script continues.

That’s often more reliable than sprinkling $var checks throughout the code.

The beginner-friendly way to remember it

After a few scripts, the colon stops feeling like “a magic symbol” and starts feeling like a tiny tool with a job:

  • : is a null command: it doesn’t do application work. (pubs.opengroup.org)
  • But it still lets the shell evaluate expansions and perform redirections.
  • POSIX parameter expansion forms like ${x:?word} can enforce required inputs and fail fast. ()
  • :${…} is the safe pairing: evaluate rules, discard their “normal result,” and let failures exit.

There’s a reason experienced shell authors reuse this pattern: it keeps the “real logic” readable, while the “messy correctness checks” live in shell-native syntax.

Conclusion: using the colon is less about being clever and more about being precise

The shell colon doesn’t “do nothing” in the way humans mean it. In practice, it’s a deliberate staging area: a safe command where expansions can run, redirections can take effect, and the shell can enforce correctness.

Once that clicks, ${parameter:?…} and ${parameter:=…} stop being isolated tricks and become building blocks for scripts that fail loudly, default sanely, and avoid the kind of silent misbehavior that turns a one-day script into a two-week debugging session.

ahsan

ahsan

Hello! I am Mr Ahsan, the writer of the Website. I am from Netherland. I like to write about technology and the news around it.

Comments (0)

No comments yet. Be the first to respond!

Leave a Comment

Your comment will be visible after review.