Shell 脚本里的冒号:无用却必用
A shell colon does nothing. Use it anyway

我写过无数 Shell 脚本,却总被一些冷门技巧惊得目瞪口呆。最近让我大开眼界的,就是那个看似毫无作用的冒号。它不仅是参数扩展中检查必填参数的利器,能一键替代冗长的 if 判断;作为 null command,它还能在设置默认值、截断文件或检查文件权限时发挥奇效。这个源自 1971 年 Thompson shell 的古老符号,如今依然是 Unix 哲学中优雅简洁的典范。
Shell 里的冒号看似什么都不做,但你依然应该使用它。
- kazinator
> ( : < dataset.json ) && echo YES # dataset.json 可读吗?
这里的子 shell 执行括号和冒号命令完全是多余的,直接写:
< dataset.json && echo YES
重定向不需要挂一个冒号命令,也没必要 fork 一个子 shell 来执行这种命令。
> ( : >> result.json ) && echo YES # result.json 可写吗?
作为一个检查可写性的惯用写法,这让我有点犹豫。如果文件不存在,我们就会创建一个零长度的文件。如果接下来的操作反正就是要往里面写东西,那可能还行。
如果我们是为了覆盖它而做测试,为什么不直接用 "> result.json"(这本身就是一个将文件截断为零长度的惯用写法)呢?
我们什么时候会用到这种写法?也许是在某个命令之前,该命令把文件名作为目标文件参数传入(而不是使用输出重定向),并且在尝试打开文件写入之前会进行耗时的计算。这样我们可以尽早捕获权限错误。
我想我从来没写过这种测试;通常的做法是直接执行写入操作,让它失败就是了。
在 POSIX C 中,有一个 access() 函数用于执行这类测试。但它有特殊用途:它是给 setuid root 进程用的,用来以真实用户/组(即提升权限到 root 的那个用户/组)的身份执行权限检查。也就是说,它检查的不是“我们能不能做这个操作”,而是“我们应该做这个操作吗”(如果我们把权限降回原始用户,是否仍然被允许)。
- zaptheimpaler
人生苦短,没必要为了超过两行的脚本去忍受这门语言及其五万个“地雷”,尤其是在大模型时代。直接写 Python、TS 或任何真正的编程语言脚本吧。Bash 在命令行上很棒,但也应该仅限于此。
- kevincox
我对其中大多数用法都不太感冒,但有几个确实挺有用。
: "${1:?missing argument, aborting!}"
我一般不会这么用,因为我会想把 $1 赋值给一个变量,以便在脚本后续部分使用。但这确实是一个给出清晰错误信息的好方法,用于处理缺失的必需环境变量。
其他很多用法(比如截断文件)可能用专用命令写得更清楚,但如果你极力避免依赖 shell 之外的东西,它们可能会派上用场。