Using the Shell Colon (:) for Parameter Expansion and Null Command Tricks
Using the Shell Colon (:) for Parameter Expansion and Null Command Tricks
The shell colon (:) is a null‑command that discards its output but still evaluates any parameter expansions or redirections attached to it. This lets you validate arguments, set defaults, truncate files, test readability/writability, and satisfy syntax where a command is required, all without running an external program.
The colon as a null‑command
The colon builtin does nothing but evaluate its arguments and discard the result, making it a safe way to run expansions without executing them.
Validating required arguments with :?
Using : "${VAR:?msg}" exits the script with an error if VAR is unset or empty, providing a compact argument check.
Setting defaults with :-
: "${VAR:=default}" assigns a default value when VAR is unset or empty while discarding the assigned value.
Truncating files with : > file
Redirecting output of the colon command truncates a file to zero length, equivalent to > file but works as a command.
Testing file readability and writability
A subshell with : < file or : >> file combined with && tests whether a file can be read or written; note that some commentators point out the subshell and colon are superfluous for plain redirection tests.
Using colon in traps and conditional placeholders
Colon satisfies syntax where a command is required, such as trap handlers or the then‑branch of an if, without performing any action.
Checking multiple variables with set -u
With set -u, colon can be used to deliberately reference variables to trigger an error if they are unset.
Colon as a true equivalent
Colon returns exit status 0, behaving like the true builtin.
Alternative views and trade‑offs
Some commentators find the colon idioms less readable and prefer explicit if statements or dedicated commands; others appreciate the brevity for one‑liners.
"Well that’s exciting. I learned a lot of uses for ':' today. However, the only one I already knew... if some-command; then : # command required else echo "command failed" fi I used to do that until I learned of if ! some-command; then echo "command failed" fi It’s in the POSIX standard so it’s not just a bashism: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V... > If the pipeline does not begin with the "!" reserved word, the exit status shall be the exit status of the last command specified in the pipeline. Otherwise, the exit status shall be the logical NOT of the exit status of the last command" — @amiga386
"Nice one! I love those weird bash tricks. Some of the examples here are interesting, but they show parameter substitution more than colon itself: https://tldp.org/LDP/abs/html/parameter-substitution.html In small scopes, I tend to inline the
:?validation inside the arg of the command. `echo "${1:? first param required}"" Another usecase is to use colon in the body of a while loop, while doing work in the condition of the loop. while rlwrap -o -S'>> ' tr a-z A-Z ; do :; done Gives you the "do X while it succeeds. stop when it returns non-0" semantics. I’ve also written about this and other bash tricks over the years in https://github.com/kidd/scripting-field-guide/blob/master/bo... . You might like them :)" — @rgrau
"Why take a perfectly readable if-statement and turn it into something, 99.9% of people would need to lookup. Concise != better. You can make it one line with: [ -z "$1" ] && { echo "missing argument, aborting." 1>&2; exit 1 }" — @archargelod
"I am not a huge fan of most of these, but a few do seem useful. : "${1:?missing argument, aborting!}" I wouldn’t use this because I would want to give $1 a name for the rest of the script, so I would assign. But it can be a nice way to give a clear error for missing required environment variables. Many of the others (like truncating files) are probably more clearly written with dedicated commands, but may come in useful if you are going to extreme lengths to avoid dependencies outside of the shell." — @kevincox
"My personal favourite use of the colon command is what I’ve written about some years ago: https://johannes.truschnigg.info/writing/2021-12_colodebug/" — @c0l0
( : < dataset.json ) && echo YES # is dataset.json readable? The subshell execution parentheses and the colon are superfluous here, just: < dataset.json && echo YES Redirections do not require a colon command to hang off of, and there is no need to fork a subshell to execute such a command.
( : >> result.json ) && echo YES # is result.json writable? As a go-to idiom for a writability test, it gives me pause. If the file didn’t exist, we created a zero-length one. That might be okay if we are going to write to it anyway as the next action. If we are testing because we intend to overwrite it, why not just "> result.json" (which is by itself an idiom for truncating a file to zero length). When would we every do this? Maybe before some command which takes the file name as a destination file argument rather than using output redirection, and which performs a lengthy computation before trying to open the file for writing. We can catch the permission error early. I don’t think I’ve ever coded such a test; normally you just do the operation that writes to the file and let that fail. In POSIX C, there is a function access() for doing these kinds of tests. But it has a special purpose: it is meant to be used by a setuid root process to perform a permission test as if it were the real user/group (the one which elevated privilege to root). I.e. it’s not can we do this operation, but should we do this operation (would we still be allowed, if we dropped privileges back to the original user)." — @kazinator
"I often use : to set default values of configuration envs. e.g, in my dotfiles bootstrap script I have: : "${DOTFILES_PATH:=$HOME/.dotfiles}" Which will use $DOTFILES_PATH value if it’s set, otherwise it’s going to be $HOME/.dotfiles" — @olexsmir
"life is way too short to deal with this nightmare of a language and its 50000 footguns for anything longer than a 2 line script, especially in the age of LLMs. Just write a python/TS/any real language script instead. Bash is great for the command line, it should be limited to use there." — @zaptheimpaler
"the colon does nothing, which makes it the only bash command an LLM can’t over-engineer" — @luciana1u
"Good to know, but looks less readable than
ifexample." — @jagadaga
"Cool! Btw, can the sequence :? be called "reverse Elvis" ?" — @kunley
"I really hate bash because of its unreadable syntax, and this does not help it in any way. We have some large bash scripts in my company, ~10,000 LOC spread across multiple files, all sourcing each other and what not. It is truly hard to read bash, which means it is truly hard to maintain bash, which means that when the one person knowing the bash scripts in your company goes away, you’re in for some "fun\). My point is, these quirks are not useful, except for some bash enthusiasts." — @orphereus
"I usually skip the get option builtin, and use : as... while : ; do case "$1" in "" ) break;; -f|-foo) shift; whatever;; *) usage; exit 1;; esac done For this... instead if something; then true else echo ERROR exit 1 fi Using : would be too much here. For anything else including json etc. I usually go to duckdb. Awesome support, single file install, readable, easy to maintain. Powershell on Linux or Unix? Just another huge dependency if you manage 1000s of machines, and good luck finding a Linux gal/guy wanting or able to touch pwsh without chemical grade gloves." — @m2f2
"Thank you for this. Also, I’ll never use it. Because a language feature that needs marketing is against readability, among those in my target audience who have not yet read the marketing. I need my shell scripts to be long enough to explain to my audience exactly what they are doing." — @jeffrallen
"I’ve read through all of the examples in the article & they all seem to serve to sole purpose of turning readable multiple line code into one-liners. One-liners are a cool little artifact of early shell culture & are sometimes still useful today if they’re short to avoid the readability problems of
/when copy pasting a quick shell command to run, but they have no place in scripts. None of this seems useful to me. > if you are like me and prefer less typing (gotta go fast) Yeah, no." — @lucideer
"Prefer if x then :; else something; fi over if ! x; then something; fi Really? Colon is the appendix of the shell." — @normie3000
Though.. what if I told you the above four lines could be replaced by just... one? I’d reject the pull request. Bash is already bad as programming language (the goodness of language for long code is inversely proportional to how nice it is for shell one-liners), this is just turning "bad" into "line noise" If your bash script takes more than one screen, rewrite it in Python, hell, rewrite it in Perl, even that’s better " — @PunchyHamster
"Articles like this are fun but they all come from posix shell syntax being fundamentally bad for scripting/programming. All the piping stuff is great, of course. And the overall ecosystem is great. But the interpretation of the script itself working by a series of string substitutions is a mechanism we wouldn’t accept in a regular programming language. And there’s no excuse for it really, except that shell syntax is really, really old. For example, what does
$foomean in shell syntax? In any reasonable language (perl or powershell, for example, or python if you drop the$), it’s an expression that evaluates to whatever value’s inside that variable. In shell,$fooisn’t an expression in that sense, and what it does depends on what’s inside it via a variety of string substitution rules. This is the main reason we have arcane articles like this. That said, nice article." — @garethrowlands
"I have probably the worst use case, but I like it. I have a very specifically structured ZDOTDIR, and I write everything in a way that is self-documenting. After a while, you probably know more commands and utilities than you know what to do with, and you’ll forget they exist when you need them. In order to not waste time looking for a program for a particular and infrequent purpose, I create “do nothing” aliases like
: alias f3probeso I can realize I just forgot I already have something for it. Nice predictable pattern to grep with^: aliasto look through all of these." — @ocd
"I actually did absolutely need it recently when I golfed together a shell script that is simultaneously a valid YAML file [0]. Sometimes having no-op tokens is nice! [0]: https://domi.work/blog/posts/compose_polyglot/" — @dominiwe
"there are some early unix tapes floating around, and in those early shells, i’m fairly certain the colon was one of only two special-cased code paths after the command line was parsed. does anyone recall more specifically?" — @del
"For decades I used bash and never knew and saw this syntax. And recently discovered that by a code generated by Claude. And now I see this article. So I guess that it is a construct suddenly popularized by llm." — @greatgib
"This “hidden knowledge” is fun to read but pain to remember and use, especially when working with multi-platform environments // Switched from bash to plain python scripts for shell stuff everywhere several years ago, and never looked back into bash zoo anymore. Stable syntax across Win/Mac/Linux, no bash/zsh/msys2 obscure differences, normal errors and Clause writes scaffolds quick and flawless anyway" — @search_facility
"I once created a set of shell functions which I wanted to have a docstring-like functionality. The solution I came up with was to have each shell function start with the : command with a string as an argument. Since : is an actual command, not a comment, it was preserved as part of the function, and could be extracted at runtime using relatively simple parsing to do introspection. Example: foo(){ : "This is a docstring for the foo() function" bar --verbose | baz --quiet } (Repost of < https://news.ycombinator.com/item?id=29152308 >)" — @teddyh
"I always wanted to learn more about scripting. But today I am not as passionate as before because LLMs write working scripts most of the time. I am wondering if it is true for most programming techniques and quirks. Are we going to write code to solve low level problems? The other day there was a blog post about learning SIMD. I think in future “programmers” will just nag about the speed of the program and the coding assistant will eventually introduce SIMD to the source code. It is a little sad but we have to go with the current if we want to survive." — @darkoob12
"Is : the same as
true?" — @mqus
"Here’s another tip I picked up last millenium: If you’re using a GUI and like decorating your shell prompt with information, format it like this: : stuff; Then you can copy/paste entire lines of commands. (Yes, this assumes you don’t put naughty fragile stuff in your prompt. Buy you’re smart enough not to do that.)" — @kps
"Related: https://stackoverflow.com/q/79454372/63550" — @dfasifsaf
"One of the things I do with : is infinite while-loops, like: while :; do
sleep n done Maybe to mainstream to make the cut!?" — @mandus
"Whats the advantage of the colon for the truncation example >file1 >file2 versus : >file1 >file2 I’ve done quick and dirty interactive truncation like the former for many years, no colon" — @1vuio0pswjnm7