← 文章 / 未分类
Hacker News 2026/7/27 · 2026-07-27 01:22:48 · 13 阅读

Shell 冒号:什么都不做,但请尽管用

在遥远的地方.. #

一杯过凉的咖啡旁边放着一杯刚泡好的滚烫咖啡。四个终端窗口,其中三个一小时前就该关了,还有一个我实在(真的真的)不想写的 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

注意:上述示例可在本地安全运行,试试看!

常见问题 #

看了网上的一些评论,我发现自己确实漏掉了一些值得解释的地方。我会随着问题的出现持续更新这一部分。

  • 为什么需要空命令?没有冒号,扩展不也一样发生吗? #

    参数扩展无论如何都会发生,但如果没有空命令或类似用法,Shell 会把结果字符串当作命令来执行。

    % ${HELLO:=123}
    zsh: command not found: 123
    

    如果在参数扩展前面加上空命令,结果会被丢弃,但表达式仍然会被求值(将 HELLO 设为 123)。

    % : ${HELLO:=123}
    % echo $HELLO
    123
    
  • 既然可以用 VAR=${VAR:-default-value},为什么还要用空命令? #

    这本质上是个人的偏好问题,但使用我们心爱的冒号,可以把潜在的拼写错误减少到一处(而不是两处):

    : "${DATA_DIR:=/var/data}"           # <- DATA_DIR 只出现一次 (1)
    DATA_DIR="${DATA_DRI:-/var/data}"    # <- 哎呀,写错了 (2)
    
  • 既然会影响可读性,为什么还要用这些技巧? #

对于本文中那些非常刻意编造的例子,存在一些常见误解,比如质疑它们的实用性,或者是否真该用它们。

以下面这个为例。

if some-command; then
    :                            # command required
else
    echo "command failed"
fi

当然,你可以用 if ! some-command 去掉分支,但这不是这段代码的重点——它想说明的是,在需要一个命令但什么都不想做的地方,可以用空命令来占位。

它从来不是让你理解为 “哦,有 if 语句就应该用空命令”,而是让你理解为 “哦,在需要一个命令的上下文中,我可以用空命令来什么都不做”

: 在实际场景中的巧妙用法 #

用户 ifphilipenews.ycombinator.com评论了以下内容, 这正好是需要一个完全不做任何事的命令的场景。

当我想要进行交互式变基并自动压缩,又不想编辑待办列表时,我会把冒号设为 Git 的 EDITOR。为此我创建了一个别名[1],叫快速交互式变基:

riq = -c sequence.editor=: rebase --interactive

这篇文章是否本可以用上面那个例子,而不是备受争议的 if 语句?如果我有那么聪明能想到它——是的……但这样能让人更好地理解 : 到底是什么吗?我强烈怀疑不行。

原始来源: Hacker News

评论 (0)