写完前几篇 Linux 命令教程,你可能已经发现一个规律,紧接着又发现一堆反例:grep -v 是反向匹配,curl -v 是显示详细过程,换成 node -v 却变成了查版本号。同一个字母,三个命令三种含义。

到底是谁定的这些规则?答案可能有点意外:没有人统一定。这篇讲清楚这套”有历史惯例但没有强制标准”的设计约定。

这是 Linux 常见命令系列第五篇,承接 Linux 常见命令与工具地图chmod/chowngrepfindtar/zip/unzip

两种选项风格:POSIX 短选项与 GNU 长选项

命令行选项有两套并存的写法:

  • 短选项:单个字符前面加一个短横线,比如 -v-f。这是 POSIX 定义的标准写法,对应的解析函数 getopt 本身就是 POSIX 规范的一部分——历史上敲命令都是手动输入,单字符选项打字快
  • 长选项:完整单词前面加两个短横线,比如 --verbose--file。这是 GNU 项目后来引入的扩展——对应的 getopt_long 函数不属于原始 POSIX 规范,是 GNU 对 POSIX 的补充。GNU 官方编码规范给出的理由不只是”更好读”:目标是让同一个概念在不同程序之间保持一致的拼写——任何 GNU 程序如果有”详细输出”这个选项,都应该精确地写成 --verbose,用户不用每个程序重新猜一遍这个功能叫什么

这不是”新标准取代旧标准”的关系,是互补——大多数 GNU 命令行工具两套写法都支持,短的敲起来快,长的写脚本时更清楚意图,用哪个看场景。

短选项能挤在一起写,长选项不能

短选项有一个长选项做不到的能力:可以把几个不需要参数的选项拼在同一个短横线后面。

1
2
3
4
5
6
7
8
9
tar -cvf archive.tar.gz files/
# -c -v -f 三个短选项挤在一起,等价于 tar -c -v -f archive.tar.gz
# 只有最后一个(-f)需要带参数,前面的 c、v 不需要,所以能挤

ls -la
# 等价于 ls -l -a

tar --create --verbose --file=archive.tar.gz files/
# 长选项没法挤成 tar --createverbosefile=archive.tar.gz,只能一个个分开写

短选项能挤的前提是:挤在一起的这些选项里,只有排在最后的那个可以带参数——这也是上一篇 tar 文章里”-f 必须放最后一位”这条规则的根源。

没有中央标准:同一个字母在不同命令里可能完全不是一回事

这是全文最重要的一点。-v 在不同命令里的真实含义对比:

命令 -v 的含义
grep -v invert,反向匹配
curl -v verbose,显示详细请求过程
node -v version,查看版本号

没有任何组织规定”-v 必须表示什么”。grepcurlnode 是完全独立的项目,各自的作者按自己的直觉选了这个字母——curl 的作者觉得 -v 该是 verbose,node 的作者觉得 -v 该是 version,两边都没错,只是没有对上。

这不是巧合或者哪边”用错了”,纯粹是因为命令行选项的字母分配从来没有一个中央注册机构去协调。唯一可靠的做法是:遇到不确定的选项,查这个命令自己的文档--helpman),别指望”我在别的命令里见过 -v“这种经验能直接套用。

长短选项不是一一对应关系

容易踩的另一个误区是假设”每个短选项都有对应的长选项,反过来也一样”——实际上两边是各自独立设计的集合:

1
2
3
4
5
6
7
8
find . --exclude="*.log"
# find 的 --exclude 只有长选项写法,没有对应的短选项

grep -P '\d+' file.txt
# -P 有对应的长选项 --perl-regexp,两种写法都行

curl -o output.txt https://example.com
# -o 有对应的长选项 --output

具体某个选项有没有短/长两种写法,完全取决于这个命令的作者当初怎么设计,没有规律可循,用之前查一下这个命令自己的文档最稳妥。

--:告诉命令”后面全部当普通参数,别再解析成选项”

-- 是 POSIX 明确定义的一个特殊标记:出现在命令行里之后,后面所有内容都当成普通参数处理,即使它们长得像选项(以 - 开头)也不再被解析。

最经典的应用场景:删除一个文件名本身以 - 开头的文件。

1
2
3
4
5
rm -filename
# 报错:rm 会把 "-filename" 当成一个未知选项去解析,而不是文件名

rm -- -filename
# 正确写法:-- 之后的 -filename 被当成普通参数(文件名),不再解析成选项

只要命令是通过标准的 getopt 系列函数解析参数的,都支持 --,这是 POSIX Utility Conventions 里定义的通用行为,不是某个命令单独发明的。

但这条规则不是无条件成立的:bash 内建的 echo 就是个例外——它不走 getopt 那套解析逻辑,-- 传给它不会被当成”选项结束”标记,而是原样当成要输出的文本打印出来。真正通用可靠的做法是遇到不确定的命令先查一下它的文档,别假设”所有命令都认 --“。

选项带参数的几种等价写法

短选项和长选项各自还有细分的写法:

1
2
3
4
5
6
7
8
9
10
11
tar -f archive.tar.gz files/
# 短选项 -f 后面留空格接参数

tar -farchive.tar.gz files/
# 短选项也可以不留空格,直接把参数粘在字母后面,效果一样

tar --file archive.tar.gz files/
# 长选项后面留空格接参数

tar --file=archive.tar.gz files/
# 长选项也可以用等号连接参数,两种写法都行

日常写惯了哪种就用哪种,四种写法在大多数 GNU 工具里都能正常解析,不用纠结选哪个更”正确”。

回看前四篇:这套规律怎么解释你已经踩过的坑

前四篇里出现过的几个选项设计细节,用这篇的框架回头看就都说得通了:

  • tar 的 -f 必须放 clumped 短选项最后一位:因为短选项挤在一起时只有最后一个能带参数,-f 恰好是需要带参数的那个,位置不能乱放
  • grep 的 -E--extended-regexp 是同一个选项:短/长两种写法,效果完全一样,选哪个纯粹是个人习惯
  • find 的 --exclude 没有对应短选项:印证了”长短选项不是一一对应”这条规律,find 的作者就没给它设计短选项

这些当初看起来是”这个命令特有的坑”,其实都是同一套通用设计约定的具体体现。

命令行选项之外

选项讲的是”怎么给一个命令传参数”,下一篇要讲的是”怎么把多个命令组合起来干活”——xargs、管道,以及背后那套”每个工具只做一件事、靠文本流互相拼接”的 Linux 命令设计哲学。