Shell 里的冒号什么都不做,但你还是该用它

查看原文 HN 讨论

文章摘要

作者 Filip Roséen(HN 上的 refp)写道:他写过的 shell 脚本多到数不清,但仍然会时不时被一些技巧震住,最近这个技巧是 shell 里的冒号。

文章从一个所有人都写过的场景开始:脚本需要必填参数,于是你条件反射地写一个 if 判断——参数为空就往 stderr 打一句「missing argument, aborting.」然后 exit 1。四行。作者说这四行可以压缩成一行:

: "${1:?missing argument, aborting.}"
echo "Hello $1!"

不带参数运行时 bash 直接报错退出;带参数运行则正常打印。如果把位置参数换成一个有名字的变量(比如 GREET_NAME),诊断信息里还会自动带上变量名,更容易定位。

这一行里其实有两个东西在起作用。第一个是参数展开:${name:?diagnostic} 会检查 $name 是否未设置或为空,是的话把 diagnostic 打到 stderr 并以非零状态退出 shell;否则等价于 $name。第二个才是主角——行首那个孤零零的冒号。: 是空命令(null-command),一个内建命令,它什么都不做,只负责求值自己的参数然后把结果丢掉。作者特意提到它的年龄:: 可以一路追溯到 1971 年的 Thompson shell,在那里它兼作标签,同时也是 Unix 最早的注释标记。

文章后半段给出了一串「冒号高光时刻」的例子:用 : "${DATA_DIR:=/var/data}" 设置默认值(冒号把展开结果吞掉,而不是拿去当命令执行);: > error.log 截断文件,甚至 : > error.log > access.log 一次截断两个;( : < dataset.json ) && echo YES 测试文件是否可读、( : >> result.json ) && echo YES 测试是否可写;trap : INT 因为 trap 必须要有一个命令;在 set -u 之后用 : "$DEPLOY_ENV" "$HOST" 来集中检查必需变量;以及在 if 分支里当占位符用,因为那个位置语法上必须有命令。

因为发布后收到不少质疑,作者补了一节 FAQ。为什么需要空命令、展开难道不是本来就会发生吗?——展开确实会发生,但没有空命令的话 shell 会把展开结果当成命令去执行,${HELLO:=123} 会得到「command not found: 123」。为什么不直接写 VAR=${VAR:-default}?——核心是个人偏好,但用冒号的写法变量名只出现一次,可能打错的地方从两处缩到一处。为什么要用这种伤害可读性的写法?——作者澄清了最常见的误读:那个 if 分支的例子当然可以用 if ! some-command 消掉,但那不是重点;这个片段要表达的不是「有 if 语句就该用空命令」,而是「当我处在一个语法上要求有命令、但我什么都不想做的上下文里,可以用空命令把它变成 no-op」。

文章末尾还收录了一个来自 HN 评论的真实用法:用户 fphilipe 把 : 当作 git 的 EDITOR,从而在做 interactive rebase + autosquash 时不必真的去编辑那个 todo 列表,他为此起了个别名叫「快速 interactive rebase」。

HN 评论精华

这条讨论最鲜明的特征是:作者本人几乎回复了每一条批评,而讨论区的多数意见其实是反对在生产代码里使用这些技巧。

「太不可读了」是压倒性的主流意见

技术性纠错与作者回应

真正在用的人

PowerShell 支线

IdiotSavage 借机安利:「如果有一种更不晦涩、更难写错的方式来声明必填参数呢?」并展示了 PowerShell 的 [parameter(mandatory)] 声明——缺参数时交互式会提示你补,非交互式会给出清楚的错误。作者的反应相当热情:他前几天刚写了人生第一个真正的 .ps1,「PowerShell 感觉像是我从未拥有过的情人。顶部那些可读注释就能生成可用文档?该死、该死、该死。我心里有一部分想拿它当日常主力用两周。」但他补充自己是 zsh vi-mode 党,「一直都是,也永远会是」。delta_p_delta_x 补充了 PowerShell 被严重低估的一点:它跑在 CLR 上,能直接访问 .NET 对象和类型,因而能用上 P/Invoke 直至 Windows API。garethrowlands 泼冷水:「我看不出 pwsh 能在这个生态里找到位置。连 oil 和 fish 都很挣扎。那个位置基本被 Python 占了。」

关于 shell 本身的两句总结

garethrowlands 在多处给出了同一个平衡观点:客观地说 POSIX shell 语法确实糟糕,尽管它促成的整个系统很了不起;历史对它宽容是因为它解决了当时的问题——但 awk 年纪相仿,它的存在本身就证明了 shell 语法并非非得犯这些错。wpm 则为 shell 辩护:「它不是『不可读』的语法,它是简洁而陌生的语法。很多语言的语法对我来说都陌生难读,只是因为我不熟。事实上大多数编程语言第一眼都不可读,也许除了……AppleScript?也许 Scratch。连 Python 著名的可读性也不是保证的,它那些函数式编程设施跟 *sh 一样晦涩。我 50% 的工作时间在 shell 里,这篇文章里我没觉得有任何东西不可读。」

最后是 luciana1u 的一句妙评:「冒号什么都不做,这让它成为唯一一个 LLM 无法过度设计的 bash 命令。」