← 文章 / 编程开发
Hacker News 4小时前 · 2026-07-27 01:22:48 · 0 阅读

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 语句,就该用空命令”,而是“哦,如果我在一个需要命令的上下文里,可以用空命令来让这个位置什么都不做”。

: 在现实世界中的巧妙用法 #

用户 ifphilipenews.ycombinator.com 上发表了以下评论, 这正好对应了需要一条什么也不做的命令的场景。

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

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

这篇文章本可以用上面的例子来代替那个备受争议的 if 语句吗?如果我有那么聪明能想到的话——是的……但这样是否还能让人理解 : 的真正含义呢?我深表怀疑。

原始来源: Hacker News

评论 (0)