A shell colon does nothing. Use it anyway
A shell colon does nothing. Use it anyway
Shell 中的冒号什么都不做,但你还是应该用它
A shell colon does nothing. Use it anyway. I’ve written more shell scripts than I can count, but I still stumble upon tricks that honestly blow my mind far too often than I care to admit. Latest thing that blew my head clean off? The shell colon.
Shell 中的冒号什么都不做,但你还是应该用它。我写过的 Shell 脚本数不胜数,但依然经常会发现一些让我大开眼界的技巧,频率高到让我不好意思承认。最近让我彻底震惊的是什么?就是 Shell 中的冒号。
In a land far-far away..
在遥远的地方……
… there was once a far too cold cup of coffee next to a freshly brewed far-too-hot one. Four different terminals where three could have been closed an hour ago, and a shell script which I really (really) did not want to write. Who would have thought a single colon would be the one to save the day night?
……曾经有一杯凉透了的咖啡,旁边放着一杯刚冲好、烫得要命的咖啡。四个不同的终端窗口,其中三个其实一小时前就可以关掉了,还有一个我真的(真的)不想写的 Shell 脚本。谁能想到,一个简单的冒号竟然能成为拯救这个夜晚的关键?
Checking for required arguments
检查必需参数
This is a familiar dance, it’s pretty much muscle memory by this point. You have a script, it takes a few arguments, and some of them are mandatory; alright, an if-statement like so many times before:
这套流程大家都很熟悉,几乎已经成了肌肉记忆。你有一个脚本,它需要几个参数,其中一些是强制性的;好吧,像往常一样写个 if 语句:
if [ -z "$1" ]; then
echo "missing argument, aborting!" 1>&2
exit 1
fi
echo "Hello $1!"
Though.. what if I told you the above four lines could be replaced by just… one?
不过……如果我告诉你上面这四行代码可以只用一行来代替呢?
: "${1:?missing argument, aborting!}"
echo "Hello $1!"
$ bash example.sh
example.sh: line 3: 1: missing argument, aborting!
$ bash example.sh refp
Hello refp!
Parameter expansion and the story of :?
参数扩展与 :? 的故事
There are two things going on in the previous snippet, and you are correct in identifying that one part is using parameter expansion: The syntax ${name:?diagnostic} checks whether $name is unset or empty — if it is, the diagnostic is printed to stderr and the shell exits with a non-zero status, otherwise; if the variable is set, it is equivalent to $name.
在上面的代码片段中发生了两件事,你没看错,其中一部分使用了参数扩展:语法 ${name:?diagnostic} 会检查 $name 是否未设置或为空——如果是,则会将诊断信息打印到标准错误(stderr)并以非零状态退出 Shell;否则,如果变量已设置,它就等同于 $name。
That.. other colon
那个……“另一个”冒号
So that’s one colon, but what about that other one, the one who sits alone at the beginning of the line? : is the null command — a builtin that does nothing but evaluate its arguments and discard the result. : is old — it goes all the way back to the 1971 Thompson shell where it doubled as a label and Unix’s very first comment marker. : two eyes staring at you in the dark, with love.
那是其中一个冒号,但另一个呢?就是那个独自坐在行首的冒号?: 是空命令(null command)——一个内置命令,除了评估其参数并丢弃结果外,什么都不做。: 很古老——它一直追溯到 1971 年的 Thompson shell,当时它既是标签,也是 Unix 的第一个注释标记。: 就像黑暗中注视着你的双眼,带着爱意。
Other colons worth remembering
其他值得记住的冒号用法
Perhaps we have already established that there is more to : than meets the eye, but to prove the real magic of the null-command — here are a few usages that blew my mind.
也许我们已经确定 : 的作用远不止表面看起来那么简单,为了证明空命令的真正魔力,这里有几个让我大开眼界的用法:
: "${DATA_DIR:=/var/data}" # 设置默认值,: 会吞掉结果
: "${RETRIES:=3}" # 而不是将其作为命令运行
: > error.log # 清空 error.log
: > error.log > access.log # 同时清空 error.log 和 access.log
( : < dataset.json ) && echo YES # dataset.json 可读吗?
( : >> result.json ) && echo YES # result.json 可写吗?
trap : INT # trap 需要一个命令
sleep 60 # sleep 是可中断的
set -u # 变量未设置时报错
: "$DEPLOY_ENV" "$HOST" # 检查 DEPLOY_ENV 和 HOST
if some-command; then
: # 需要命令占位
else
echo "command failed"
fi
Con-colon-sion
总结(Con-colon-sion)
So, if you are like me and prefer less typing (gotta go fast) — the null command and parameter expansion are a good pair to remember.
所以,如果你像我一样喜欢少打字(追求速度)——空命令和参数扩展是一对值得记住的好搭档。
while :; do
echo "I love colons"
sleep 1
done
Frequently Asked Questions
常见问题解答
Why do you need the null-command? Doesn’t the expansion happen without the colon? 为什么需要空命令?没有冒号扩展就不会发生吗?
The parameter expansion will happen regardless, but without a null-command or similar usage the shell will treat the resulting string as a command to run. 参数扩展无论如何都会发生,但如果没有空命令或类似用法,Shell 会将生成的字符串视为要运行的命令。
% ${HELLO:=123}
zsh: command not found: 123
If we prefix our parameter-expansion with the null-command, the result is discarded, but the expression is still evaluated (setting HELLO to 123). 如果我们给参数扩展加上空命令前缀,结果会被丢弃,但表达式仍然会被评估(将 HELLO 设置为 123)。
% : ${HELLO:=123}
% echo $HELLO
123
Why use the null-command when I could do VAR=${VAR:-default-value}? 既然可以用 VAR=${VAR:-default-value},为什么还要用空命令?
This at its core boils down to personal preference, but given that the below only states the variable name once, you are far less likely to make a typo on either side of the assignment operator which b0rks your script. 这归根结底是个人偏好问题,但考虑到下面的写法只写了一次变量名,你不太可能在赋值运算符的两侧出现拼写错误,从而导致脚本崩溃。
DATA_DIR="${DATA_DRI:-/var/data}" # <- 哎呀,拼错了
: "${DATA_DIR:=/var/data}" # <- 更安全