The Shell Colon: Why You Should Use a Command That Does Nothing

The colon (:) is a built-in command in virtually every POSIX-compliant shell — Bash, Zsh, Dash, even the original Bourne shell. Its sole purpose is to do absolutely nothing. Yet, as the article on refp.se explains, this seemingly useless command is one of the most versatile tools in a shell scripter's arsenal. In this article, we will explore why the colon exists, how it works, and why you should use it anyway — often in places where you wouldn't expect a no-op to be valuable.

What Is the Shell Colon?

The colon is a shell built-in — meaning it is executed directly by the shell without calling an external program. Its exit status is always 0 (success). According to the POSIX specification, the colon command is defined as a special built-in utility that does nothing but returns a zero exit code. Historically, it was used as a label marker in early shell scripts before getopts existed. The name "colon" comes from the character itself (:), and it is also sometimes referred to as the "no-op" or "null command."

A simple demonstration:

:
echo "This runs after the colon."

The colon executes, returns success, and then the next command runs. On the surface, this is useless — you could just omit the line. But as we will see, the colon shines when combined with other shell features.

The Many Uses of :

1. Infinite Loops

One of the most common uses of the colon is to create an infinite loop:

while :
do
    echo "Looping forever..."
    sleep 1
done

The while command expects a condition that returns zero for success. The colon always returns zero, so the loop never ends. You could use while true, but true is an external command (or a built-in in modern shells) that also returns zero. The colon is slightly more efficient because it is always a built-in, while true might invoke /bin/true in some contexts. In practice, the difference is negligible, but many scripters prefer : for its brevity and shell purity.

2. Placeholder in Conditionals

Sometimes you need a conditional branch that intentionally does nothing — for example, when debugging or stubbing out logic:

if [ -f "$file" ]
then
    : # TODO: process the file later
else
    echo "File not found"
exit 1
fi

Here the colon acts as a placeholder, keeping the then branch syntactically valid without executing anything. The # comment is not enough because the shell would complain about an empty branch in some constructs.

3. Variable Expansion and Default Values

This is where the colon becomes truly powerful. The colon can be combined with parameter expansion operators to perform side effects. For example:

: ${MY_VAR:="default"}

This sets MY_VAR to "default" if it is unset or empty. The colon provides a command context for the expansion; without it, the shell would try to execute the expanded value as a command. The colon safely "discards" the expansion result. Similarly:

: ${USER:?"USER variable is not set"}

This triggers an error and exits the script if USER is unset. The colon ensures the error message is displayed cleanly.

The article on refp.se highlights this usage as a "silent assignment" — a way to set default values without producing output or executing anything unintended.

4. Comment with Expansion (Better than #)

Regular comments (#) do not allow variable expansion. The colon can be used to create a "comment" that still undergoes expansion, useful for debugging:

: "Current timestamp: $(date)"

The string is expanded — the date command runs — but the result is discarded. This is handy for logging side effects without altering script behavior.

5. Function Body Placeholder

When defining a function, you must provide a body. If you are not ready to implement it, use the colon:

my_function() {
    :
}

This creates an empty function that does nothing but can be called without error. Later you replace the body with real code.

6. Truncate Files

Combining the colon with output redirection truncates a file to zero length without changing its permissions:

: > /var/log/old_app.log

This is a common idiom to clear log files. The colon produces no output, so the redirection simply truncates the file. It is safer than cat /dev/null > file because it does not spawn an extra process.

7. Ensuring a Command Succeeds

Sometimes you need to guarantee that a pipeline or command sequence returns success even if the previous command fails. Prepending a colon does not help directly, but you can use : && to force success. More practically, the colon is used in conjunction with || to provide a fallback:

some_command || :

If some_command fails, the colon runs and returns success, preventing the script from exiting if set -e is active. However, this pattern is controversial — many experts argue that explicit error handling is preferable. Use it judiciously.

Comparison with true

The commands : and true are functionally identical: both return exit status 0 and do nothing. However, there are subtle differences:

Feature : (Colon) true
Type Special built-in (POSIX) Usually a built-in or external command
Performance Slightly faster (always built-in) Negligible difference
Readability Cryptic to beginners Self-documenting
Use in parameter expansion Natural (e.g., : ${var:=val}) Awkward syntax (true ${var:=val} works but is unusual)
History Originated as a label marker Introduced later

In practice, most experienced shell scripters use : for parameter expansions and infinite loops, and true for conditional expressions where readability matters. The refp.se article notes that the colon is "the Swiss Army knife of shell scripting" precisely because of its dual role as a no-op and an expansion trigger.

Real-World Example: Debugging Scripts

Imagine you have a complex CI/CD pipeline script that runs a series of commands. You suspect that environment variables are not being set correctly. You can insert a colon-based debug line:

: "DEBUG: PATH=$PATH, HOME=$HOME, CUSTOM_VAR=${CUSTOM_VAR:-undefined}"

This line expands the variables and prints nothing visible, but if you run the script with set -x (verbose mode), the shell prints every line before execution. You will see:

+ : 'DEBUG: PATH=/usr/bin:/bin, HOME=/root, CUSTOM_VAR=undefined'

This allows you to inspect variable values without altering the script's behavior. The article calls this technique "silent assertion" — a way to verify state during development.

Security and Best Practices

While the colon is harmless, overusing it can lead to obscure scripts. Here are some guidelines:

  • Prefer explicit commands when readability matters. Use while true over while : if your team is not comfortable with shell internals.
  • Avoid : || command as a crutch. Suppressing errors without handling them can hide bugs. Instead, use proper error handling with if statements or trap.
  • Be careful with parameter expansion inside colon. The expansion runs in the current shell; if the expansion has side effects (e.g., command substitution), they will execute. This can be surprising.
  • Do not rely on colon for security. It does not sanitize input; the expansion still runs arbitrary commands.

The POSIX standard (IEEE Std 1003.1) defines the colon in Chapter 2 — Shell Command Language. You can consult the standard for authoritative details.

Conclusion

The shell colon is a paradox: a command that does nothing yet achieves so much. It is a hallmark of seasoned shell scripting, enabling patterns that are concise, efficient, and elegant. Whether you use it for infinite loops, parameter defaults, placeholder branches, or file truncation, the colon is a tool worth mastering. The next time you write a shell script, consider where a no-op might actually be the most thoughtful solution.

For deeper exploration, read the original article on refp.se: Your Shell and the Magic Colon.

← All posts

Comments