⏱️ Lectura: 9 min

A colon does nothing. Literally: it’s a command that evaluates its arguments and throws the result away. And yet, that same colon has spent 55 years hiding in shell scripts solving problems that most developers solve with four or five extra lines.

📑 En este artículo
  1. TL;DR
  2. Introduction
  3. What happened
  4. Context and history
  5. Technical details and performance
  6. How to start testing it
  7. Impact and analysis
  8. What’s next
  9. Frequently Asked Questions
    1. Why do I need the colon if the expansion happens anyway without it?
    2. How is it different from VAR=${VAR:-default}?
    3. Does it work in all POSIX shells?
    4. Does it hurt the script’s readability?
    5. Where does the name null command come from?
    6. Is it useful in CI/CD scripts?
  10. References

Programmer Filip Roséen documented it in an article published on July 23, 2026 on his site refp.se, updated on July 26, 2026, where he revisits why the so-called null command (:) remains, half a century later, one of the least known and most useful tricks in bash and POSIX shell.

TL;DR

  • Filip Roséen published an article on refp.se on July 23, 2026 about the null colon in shell, updated on July 26, 2026.
  • The : command runs nothing: it only evaluates its arguments and discards the result.
  • : "${1:?missing an argument}" replaces a 4-line if block with a single line to validate required parameters.
  • The origin of the null colon goes back to 1971, in Thompson’s shell, where it also served as a label and Unix’s first comment marker.
  • : "${VAR:=value}" assigns a default value without the shell trying to execute that value as a command.
  • The pattern : > file.log truncates a file without needing truncate or echo -n.
  • trap : INT lets you ignore a signal without defining a separate handler function.

Introduction

For any developer maintaining CI/CD pipelines, Docker entrypoints, or deploy scripts, POSIX shell remains the invisible glue of infrastructure. The null colon in shell is exactly the kind of detail nobody teaches in a bootcamp but that constantly shows up in serious open source repositories: Kubernetes, Alpine, and Homebrew’s or nvm’s own install scripts use it.

Roséen didn’t discover the command (it existed before most of today’s programming languages), but his article works as a reminder that Unix’s oldest tools still solve modern problems with less code than a framework.

What happened

The original article stems from a concrete case: replacing a typical argument validation, a 4-line if that checks whether $1 is empty, prints an error, and does exit 1, with a single line using parameter expansion prefixed by the null colon.

shell code showing the null colon validating an argument
The diagnostic includes the variable name, not just a generic message. Foto de Content Pixie en Unsplash

The technical trick is that ${1:?message} already checks whether the parameter is empty or undefined, and if it’s missing, prints the message to stderr and ends the script with a non-zero exit code. The colon at the start of the line is what keeps the shell from trying to execute the result of that expansion as if it were a command.

#!/bin/sh
: "${NAME:?you must provide a name}"
echo "Hello, $NAME"

Running sh greeting.sh without the variable set, the shell responds greeting.sh: line 2: NAME: you must provide a name and exits with a non-zero status. With NAME=jane sh greeting.sh, the script prints Hello, jane normally.

Context and history

The : command isn’t an invention of bash or POSIX: it already appears in the 1971 Thompson shell, Unix’s first shell, where it served double duty: it worked as a label for jumps and, before a dedicated comment syntax existed, as the system’s first comment marker.

POSIX inherited that behavior and formalized it as the null utility: a command that always returns success, has no side effects, and serves as syntactic filler wherever the language requires a command but the programmer doesn’t need to run anything, for example inside the empty else branch of an if.

💭 Key point: the null colon and the true command do basically the same thing (do nothing and exit with success), but : is a built-in of the shell itself, while on some systems true can be an external binary. That makes : marginally faster in scripts that invoke it thousands of times.

Technical details and performance

The key to understanding the trick lies in separating two things that happen on the same line. First, the parameter expansion defined in the bash manual: it always happens, whether or not there’s a command in front of it. Second, what the shell does with the result of that expansion.

If you write ${HELLO:=123} as a full line, with nothing in front of it, the shell expands the variable, sets it to 123, and then tries to execute 123 as if it were the name of a program. The result is a command not found error. If instead you prefix the expansion with the null colon, the shell evaluates the expansion (with its side effect of assigning HELLO=123) and then runs :, which silently discards that value as an argument without trying to run it.

PatternWhat it doesExampleWhen to use it
: "${VAR:?msg}"Requires the variable to existvalidating a required argumentscripts with required parameters
: "${VAR:=value}"Assigns a default valueoptional configurationflags with a reasonable default
: > fileTruncates or creates an empty fileresetting a logrotating logs without deleting the file
trap : SIGNALIgnores a signal with no actionhandling interruptionsletting a sleep be interruptible without aborting the script

How to start testing it

There’s nothing to install: the null colon is part of any POSIX-compliant shell. You can try it today on all three major operating systems.

  • Linux: any terminal with bash, dash, or sh already supports it, including minimal Alpine images that use BusyBox’s ash.
  • macOS: Terminal has shipped with zsh by default since Catalina, and the null colon works the same as in bash.
  • Windows: it doesn’t exist in cmd.exe or native PowerShell (there the equivalent is the # operator as a no-op, or $null), but it works unchanged inside Git Bash or a WSL distro with bash/dash installed.

A realistic deploy script combining the two main variants, required argument and default value, looks like this:

#!/bin/sh
: "${DEPLOY_ENV:?you must set DEPLOY_ENV (staging|production)}"
: "${DOCKER_REGISTRY:=registry.mycompany.com}"
: "${MAX_RETRIES:=3}"

echo "Deploying to $DEPLOY_ENV using $DOCKER_REGISTRY (retries: $MAX_RETRIES)"

Running DEPLOY_ENV=staging sh deploy.sh gives you Deploying to staging using registry.mycompany.com (retries: 3). If you omit DEPLOY_ENV, the script aborts before reaching the echo, with the exact name of the missing variable in the error message.

flowchart of the null colon command checking an environment variable
The script aborts before the echo if DEPLOY_ENV isn’t set. Foto de George Girnas en Unsplash

To confirm the pattern works in your specific shell, just run : "${TEST_VAR:=ok}"; echo $TEST_VAR in the terminal: if it prints ok, the behavior is as expected.

flowchart TD
 A["Script start"] --> B{"Variable defined?"}
 B -- "Yes" --> C["Continue normal execution"]
 B -- "No" --> D["Print message to stderr"]
 D --> E["Exit with non-zero code"]

Impact and analysis

The concrete benefit isn’t just saving lines: it’s reducing the surface for error. With VAR=${VAR:-default}, the variable name appears twice on the same line, and a typo in either occurrence (DATA_DIR versus DATA_DRI, for example) produces a silent bug where the original variable stays empty and the default never applies. With : "${DATA_DIR:=/var/data}", the name appears only once.

💡 Tip: if your script uses set -u to fail on undefined variables, you can validate several at once with : "$VAR_A" "$VAR_B" "$VAR_C": if any of them doesn’t exist, set -u aborts the script right there.

The real cost is readability for someone unfamiliar with the idiom. A developer who’s never seen : "${1:?...}" can take minutes to understand what that line does, while an if [ -z "$1" ]; then ... fi reads clearly even without prior shell experience.

⚠️ Heads up: on teams with high turnover or junior developers, it’s worth pairing the pattern with a short comment the first time it appears in the repository, or documenting it in the internal style guide.

What’s next

Roséen keeps an FAQ section he promises to update as readers’ questions come in, suggesting the article will keep growing. For teams already using shellcheck in CI, it’s worth considering whether these patterns should enter the internal style guide, since they’re 100% POSIX and work the same in dash, ash (BusyBox), bash, zsh, and ksh, which matters for Docker images that prioritize size and use minimal shells.

Frequently Asked Questions

Why do I need the colon if the expansion happens anyway without it?

Because without a command in front of it, the shell interprets the result of the expansion as the name of a program to run, and if that value isn’t a valid command, it fails with command not found. The null colon evaluates the expansion and discards the result without trying to run it.

How is it different from VAR=${VAR:-default}?

It’s largely personal preference, but : "${VAR:=default}" mentions the variable name only once, cutting in half the places where a typo can slip in.

Does it work in all POSIX shells?

Yes, the null command is part of the POSIX shell command language standard and works the same in dash, BusyBox’s ash, bash, zsh, and ksh.

Does it hurt the script’s readability?

It can, especially for someone unfamiliar with the idiom. It’s recommended to comment it the first time it appears in a shared repository.

Where does the name null command come from?

From Thompson’s 1971 shell, where : already existed and doubled as a jump label and as Unix’s first comment marker.

Is it useful in CI/CD scripts?

Yes: it’s exactly the kind of validation worth putting at the start of a Docker entrypoint or a pipeline job, to fail fast if a critical environment variable is missing.

📖 Summary on Telegram: View summary

Try it yourself: open a terminal right now and run : "${TEST_VAR:=ok}"; echo $TEST_VAR to see the pattern working in your own shell.

References

📱 Like this content? Follow @programacion on Telegram for daily tech content in Spanish: quick summaries, fresh content every day. @programacion

Imagen destacada: Foto de RoonZ nl en Unsplash

Categories: Noticias Tech

Andrés Morales

Developer and AI researcher. Writes about language models, frameworks, developer tooling, and open source releases. Covers ML papers, the tech startup ecosystem, and programming trends.

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.