What is the purpose of a command that does nothing, being little more than a comment leader, but is actually a shell builtin in and of itself?
It\'s slower than inse
You can also use :
to embed documentation in a function.
Assume you have a library script mylib.sh
, providing a variety of functions. You could either source the library (. mylib.sh
) and call the functions directly after that (lib_function1 arg1 arg2
), or avoid cluttering your namespace and invoke the library with a function argument (mylib.sh lib_function1 arg1 arg2
).
Wouldn't it be nice if you could also type mylib.sh --help
and get a list of available functions and their usage, without having to manually maintain the function list in the help text?
#!/bin/bash # all "public" functions must start with this prefix LIB_PREFIX='lib_' # "public" library functions lib_function1() { : This function does something complicated with two arguments. : : Parameters: : ' arg1 - first argument ($1)' : ' arg2 - second argument' : : Result: : " it's complicated" # actual function code starts here } lib_function2() { : Function documentation # function code here } # help function --help() { echo MyLib v0.0.1 echo echo Usage: mylib.sh [function_name [args]] echo echo Available functions: declare -f | sed -n -e '/^'$LIB_PREFIX'/,/^}$/{/\(^'$LIB_PREFIX'\)\|\(^[ \t]*:\)/{ s/^\('$LIB_PREFIX'.*\) ()/\n=== \1 ===/;s/^[ \t]*: \?['\''"]\?/ /;s/['\''"]\?;\?$//;p}}' } # main code if [ "${BASH_SOURCE[0]}" = "${0}" ]; then # the script was executed instead of sourced # invoke requested function or display help if [ "$(type -t - "$1" 2>/dev/null)" = function ]; then "$@" else --help fi fi
A few comments about the code:
declare -f
to enumerate all available functions, then filters them through sed to only display functions with the appropriate prefix.mylib.sh function1
and it gets translated internally to lib_function1
. This is an exercise left to the reader.$1
. At the same time, it will clutter your namespace if you source the library. If you don't like that, you can either change the name to something like lib_help
or actually check the args for --help
in the main code and invoke the help function manually.:
can also be for block comment (similar to /* */ in C language). For example, if you want to skip a block of code in your script, you can do this:
: << 'SKIP'
your code block here
SKIP
Another way, not yet mentioned here is the initialisation of parameters in infinite while-loops. Below is not the cleanest example, but it serves it's purpose.
#!/usr/bin/env bash
[ "$1" ] && foo=0 && bar="baz"
while : "${foo=2}" "${bar:=qux}"; do
echo "$foo"
(( foo == 3 )) && echo "$bar" && break
(( foo=foo+1 ))
done
A useful application for : is if you're only interested in using parameter expansions for their side-effects rather than actually passing their result to a command. In that case you use the PE as an argument to either : or false depending upon whether you want an exit status of 0 or 1. An example might be : "${var:=$1}"
. Since :
is a builtin it should be pretty fast.
It's also useful for polyglot programs:
#!/usr/bin/env sh
':' //; exec "$(command -v node)" "$0" "$@"
~function(){ ... }
This is now both an executable shell-script and a JavaScript program: meaning ./filename.js
, sh filename.js
, and node filename.js
all work.
(Definitely a little bit of a strange usage, but effective nonetheless.)
Some explication, as requested:
Shell-scripts are evaluated line-by-line; and the exec
command, when run, terminates the shell and replaces it's process with the resultant command. This means that to the shell, the program looks like this:
#!/usr/bin/env sh
':' //; exec "$(command -v node)" "$0" "$@"
As long as no parameter expansion or aliasing is occurring in the word, any word in a shell-script can be wrapped in quotes without changing its' meaning; this means that ':'
is equivalent to :
(we've only wrapped it in quotes here to achieve the JavaScript semantics described below)
... and as described above, the first command on the first line is a no-op (it translates to : //
, or if you prefer to quote the words, ':' '//'
. Notice that the //
carries no special meaning here, as it does in JavaScript; it's just a meaningless word that's being thrown away.)
Finally, the second command on the first line (after the semicolon), is the real meat of the program: it's the exec
call which replaces the shell-script being invoked, with a Node.js process invoked to evaluate the rest of the script.
Meanwhile, the first line, in JavaScript, parses as a string-literal (':'
), and then a comment, which is deleted; thus, to JavaScript, the program looks like this:
':'
~function(){ ... }
Since the string-literal is on a line by itself, it is a no-op statement, and is thus stripped from the program; that means that the entire line is removed, leaving only your program-code (in this example, the function(){ ... }
body.)
I use it to easily enable/disable variable commands:
#!/bin/bash
if [[ "$VERBOSE" == "" || "$VERBOSE" == "0" ]]; then
vecho=":" # no "verbose echo"
else
vecho=echo # enable "verbose echo"
fi
$vecho "Verbose echo is ON"
Thus
$ ./vecho
$ VERBOSE=1 ./vecho
Verbose echo is ON
This makes for a clean script. This cannot be done with '#'.
Also,
: >afile
is one of the simplest ways to guarantee that 'afile' exists but is 0 length.