Shell 冒号:什么都不做,但请尽管用
shell 冒号什么都不做,但请尽管用
我写的 shell 脚本多得数不清,但时不时还是会撞见一些让我惊掉下巴的技巧,次数多得我都不好意思说。最近让我彻底震撼的东西是什么?shell 冒号。
发布时间:2026年7月23日 03:53 UTC 最后修改:2026年7月26日 05:33 UTC 作者:Filip Roséen 标签目录
在遥远的某处 #
……曾经有一杯太凉的咖啡,旁边是一杯刚泡好、太烫的咖啡。四个终端开着,其中三个本该一小时前就关掉,还有一份我实在(实在)不想写的 shell 脚本。
谁能想到,就一个冒号,竟然成了拯救 黑夜的英雄?白天
提示:想立刻被震撼?请看更多冒号的高光时刻。
检查必需参数 #
这是再熟悉不过的操作了,几乎已成肌肉记忆。你有一个脚本,它接受几个参数,其中一些是必填的;好吧,像往常一样用 if 语句:
if [ -z "$1" ]; then
echo "missing argument, aborting." 1>&2
exit 1
fi
echo "Hello $1!"
但要是告诉你,上面那四行其实可以简化为一行呢?
: "${1:?missing argument, aborting.}"
echo "Hello $1!"
$ bash example.sh
example.sh: line 1: 1: missing argument, aborting.
$ bash example.sh refp
Hello refp!
再来看看,如果我们用有意义的变量名替代,效果完全一样,但错误信息更清晰——它直接输出了变量名!
: "${GREET_NAME:?missing argument, aborting.}"
echo "Hello $GREET_NAME!"
$ bash greet.sh
greet.sh: line 1: GREET_NAME: missing argument, aborting.
参数展开与 :? 的故事 #
上面那段代码里有两件事,你猜对了,其中一个用到了参数展开:
${name:?diagnostic}语法用于检查$name是否未设置或为空——如果是,则把诊断信息打印到 stderr,然后 shell 以非零状态退出;否则,如果变量已设置,它就等价于
$name。
另一个冒号 #
所以那是一个冒号,但另一个呢——就是独自坐在行首的那个?
:是空命令——一个内置命令,它什么都不做,只是求值参数然后丢弃结果。:历史久远,最早可追溯到 1971 年的 Thompson shell,当时它身兼两职:既作标签,又是 Unix 最早的注释标记。:像黑暗中的两只眼睛,深情凝视着你。
更多值得关注的冒号用法 #
也许我们已经知道 : 并非表面那么简单,但为了展示 空命令 的真正魔力——下面这些用法让我大开眼界。
: "${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
冒号总结 #
所以,如果你和我一样喜欢少打字(要快)—— 趁咖啡还没凉,空命令 和 参数展开 这对组合非常值得学一学。
另外——这难道不神奇吗?
set : : : : : : : : : : : : : : : : : : : : :
while : colons are more than "${1:?magic}"; do
echo "$*" && shift
done
注意:上面的示例可以安全地在本地运行,试试看!
常见问题 #
翻了翻网上的评论,发现有些地方没交代清楚,值得补充。后续有新问题会持续更新。
为什么需要 null-command?没有冒号不也能展开吗?#
参数展开无论如何都会发生,但如果没有 null-command 或类似用法,shell 会把展开后的字符串当作命令来执行。
% ${HELLO:=123} zsh: command not found: 123如果在参数展开前加上 null-command,结果会被丢弃,但表达式仍然会被求值(将
HELLO设为123)。% : ${HELLO:=123} % echo $HELLO 123既然可以用
VAR=${VAR:-default-value},为什么还要用 null-command?#核心还是个人偏好。不过用我们心爱的冒号,可以把潜在拼写错误的次数从两次降为一次:
: "${DATA_DIR:=/var/data}" # ← DATA_DIR 只出现一次 (1) DATA_DIR="${DATA_DRI:-/var/data}" # ← 手滑了,变量名写错 (2)这些写法影响可读性,为什么还要用?#
文章里举的例子——虽然刻意——但常被误解,大家会质疑它们到底有没有用,或者到底该不该用这类东西。
以下面这个为例:
if some-command; then : # 这里需要一条命令,但什么都不想干 else echo "command failed" fi当然你可以用
if ! some-command去掉分支,但这段代码的重点不是这个——它想展示的是:在需要一条命令却什么也不想做的地方,可以用 null-command 来占位。
它原本的意思并不是“哦,如果我有 if 语句,就该用空命令”,而是“哦,如果我在一个需要命令的上下文里,可以用空命令来让这个位置什么都不做”。
: 在现实世界中的巧妙用法 #
用户 ifphilipe 在 news.ycombinator.com 上发表了以下评论, 这正好对应了需要一条什么也不做的命令的场景。
当我想进行交互式变基并结合自动压缩,但又不想编辑待办列表时,我会把冒号设置为 Git 的编辑器。我为此创建了一个别名[1],叫做快速交互式变基:
riq = -c sequence.editor=: rebase --interactive
这篇文章本可以用上面的例子来代替那个备受争议的 if 语句吗?如果我有那么聪明能想到的话——是的……但这样是否还能让人理解 : 的真正含义呢?我深表怀疑。