遇到不会的命令,先问这三个来源

难度 ★☆☆☆☆ · 前置:第一次打开终端:提示符那行字在说什么 | 一句话结论:--help 是程序自己实现的快速回忆,man 是系统手册(分九卷,man 2 查系统调用),type 查的是「这个名字到底是什么」。三样会用,比记住命令本身有用得多。

tar 大概是我在命令行里栽跟头最多的一个命令,最早只会一条 tar -xzvf package.tar.gz,用着挺顺,直到某天拿到一个 .tar.bz2 的包,我照葫芦画瓢还是敲 -xzvf,命令行回了我三行。

1
2
3
gzip: stdin: not in gzip format
tar: Child returned status 1
tar: Error is not recoverable: exiting now

文件就摆在当前目录,ls 看得见,我又敲了一遍,还是一样,于是开始了长达二十多分钟的错误排查。先以为文件在传输里损坏,md5sum 对了两遍,两个值一模一样,又以为编码有问题,file 说是 bzip2 compressed data,我当时还嫌它答得牛头不对马嘴,最后怀疑 NFS 挂载丢字节,跑到另一台机器上把包重新拷了一份,结果当然还是一样,那一刻我甚至认真考虑过是不是服务器上有人手贱改过文件。真正想通是在准备放弃之前的一秒钟,z 是 gzip,j 才是 bzip2,这两个字母是硬编码在参数里的,跟文件在不在、坏没坏、传没传全都无关,那句 not in gzip format 是 gzip 在抱怨这堆字节不是它认识的格式,它一直在很准确地告诉我答案,只是我没听懂。

正确的做法其实只要一行,去问程序自己,它会把所有压缩格式对应的参数一条条列出来,而我没查,凭记忆抄了一串字母,然后花二十分钟在错误的方向上找文件。

1
tar --help

(那次之后我养成了一个习惯,动手之前先敲一记 --help,成本不超过十秒,比事后排查便宜太多。)

这篇想说的不是 tar 有多少参数,是遇到不会的命令时,去哪儿问、按什么顺序问。

三层信息,各管一段

很多人只用 --help,然后在为什么这个参数完全没效果上卡半小时,原因是这三层的职责根本不同,--help 管怎么敲,man 管为什么,type 管这是谁。

来源 回答什么问题 速度 完整度
cmd --help 常用参数有哪些、怎么拼 秒出 短,只有摘要
man cmd 完整语义、边界条件、返回值、已知 bug 慢 长,权威
type cmd 这东西是二进制、别名、函数,还是内建? 秒出 只回答身份

–help 到底是什么

它不是 shell 的特性,也不是内核的,它只是每个程序自己实现的一个参数,这个认识能省很多事,GNU 系工具基本都支持 --help,多数还兼支持 -h,BSD 系工具(macOS 上那批)经常干脆不支持 --help,只认 man。还有些场合 -h 跟帮助毫无关系,mysql -h、psql -h 里的 -h 是 host,后面得跟主机名,我第一次在 macOS 上敲 tar -h,出来一串跟帮助八竿子打不着的东西,才反应过来这玩意是每个程序各写各的,谁也没义务跟你保持一致(这件事我记了很久,因为它推翻了我当时认定参数都有通用含义的那个假设)。所以同一个字母在不同程序上意思完全不同,这挺常见,不确定就别猜,直接 man。

man 是分卷的

man 手册不是一坨文本,它按内容分了九卷,这个分法不是历史包袱,而是它查起来比任何文档都快的前提。

区 内容 例子
1 用户命令 ls、grep
2 系统调用 open、fork、socket
3 库函数 printf、malloc
4 设备文件 null、zero、tty
5 文件格式与配置 passwd、proc、fstab
6 游戏 基本没人看
7 杂项与约定 signal、tcp、epoll(一个等网络事件的高效接口,后面会讲)
8 系统管理命令 mount、iptables
9 内核内部接口 少见

为什么这件事重要,因为名字会撞车,open 在第 2 卷是那个系统调用,而第 1 卷里还有个 /usr/bin/open(macOS 上就有),printf 更典型,1 卷是 shell 里那个命令,3 卷是 C 库函数,两者的参数和返回值完全不是一回事。你直接 man open 拿到哪一本,取决于 manpath 的目录顺序和系统排序,机器换了答案可能就换了,所以指定卷号是消除歧义唯一靠谱的办法,man 2 open 或者 man 3 printf 这种写法不是给老手用的快捷键,它是在指定你问的是哪个问题。

1
2
3
man 2 open     # 系统调用 open
man 5 proc # /proc 文件系统的格式说明
man 7 signal # 信号一览表

man 2 这一卷尤其值得记住,它跟着内核版本走,别人博客里的参数可能三年前就改了。

which 不够用,得用 type

which ls 人人都会,但它有个本质限制,它只扫 PATH 里的可执行文件,看不见别名、函数、内建命令,所以 which cd 在比较新的系统上什么都不输出,不是没装,是它压根不在 PATH 里,cd 是 shell 自己实现的,which 连方向都不对。这个坑我踩过一次,当时的判断是系统缺了 which 这个包,还照着论坛的说法去装了一遍,装完当然是空的,因为问题从来不在那个方向上。更麻烦的是别名,你的 ls 可能带着 --color=auto,which 返回的却是 /usr/bin/ls,你以为它没带参数,实际上你每天敲的根本不是这个文件。

1
which cd

正确的工具是 type。

1
type cd
1
cd is a shell builtin
1
type ls
1
ls is aliased to `ls --color=auto'

原来你每天敲的 ls 是个别名,这也是为什么 \ls 和 command ls 都能绕开颜色输出,它们跳过了别名这一层。

而 type 有几个变体值得记,-a 列出所有同名候选,-t 只输出类型(alias / builtin / file / function),-p 只输出文件路径,-f 忽略函数和别名,日常最实用的是 -a,它回答的是这个名字到底有几个来源,以及我现在用的是哪一个。

man 页可以挑着读

第一次打开 man 页,最常见的感受是满屏缩写,不知从哪看起,它其实有固定结构,按需跳就行。

小节 内容 什么时候看
NAME 一句话说明 先看,确认找对了
SYNOPSIS 语法骨架 看参数怎么拼
DESCRIPTION 详细说明 主要看这个
OPTIONS 每个参数的语义 查具体参数
EXIT STATUS 返回什么退出码 写脚本时必看
FILES 涉及的配置文件 想改配置时看
NOTES / BUGS 已知的坑 最有价值,也最少人看
SEE ALSO 相关命令 顺着往下挖

SYNOPSIS 里有一套约定记号,看懂它效率会翻倍。

写法 意思
[ ] 可选
| 或者
... 可以重复
粗体 原样输入
斜体 换成你自己的值

所以 tar [OPTION...] [FILE]... 读作,可选的选项,可以给多个,后面跟可选的文件,也可以给多个,这一行看懂,后面所有命令的语法骨架都不用再猜。

man 默认用 less 打开,所以 less 的键全都能用,空格翻页、b 回上一页、/keyword 搜索、n / N 跳匹配、g / G 到首尾、q 退出。还有个技巧值得单独说,几乎所有 man 页都有 EXIT STATUS 一节,打开之后先敲 /EXIT 跳过去,比从 DESCRIPTION 一路翻下来快得多。

(这几个键是同事手把手教的,在那之前我一直拿鼠标滚轮硬滚,滚到一半就完全不知道自己在第几屏。)

man 也没写清楚的时候

三种情况会碰上,一是 man 页过时了,程序更新了手册没跟上,这时候反而 --help 更新,二是根本没有 man 页,比如你自己写的脚本,或者容器镜像为了瘦身把它删了,三是你想要的是为什么这么设计,man 不负责回答这个,它只告诉你输入什么、输出什么。

对付第二种,装上基础手册就行。

1
2
sudo apt install man-db        # Debian / Ubuntu
sudo dnf install man-db # Fedora

很多最小化镜像(Alpine、distroless)是故意不装的,因为它们的目标就是越小越好,这种时候你只能靠 --help,或者把镜像里的二进制捞到本地查。

(我第一次在一个 distroless 镜像里敲 man 的时候,还以为是自己少敲了半条命令,反复试了几遍才想起来,这类镜像连 shell 都不一定有,指望它有手册是想多了。)

三个来源指向同一个命令

找一台做过 UsrMerge 的机器,同一个名字居然有三个来源。

1
type -a ls
1
2
3
ls is aliased to `ls --color=auto'
ls is /usr/bin/ls
ls is /bin/ls

第三行是第二行的符号链接,说明这台机器做过 UsrMerge,上一篇讲过。

1
type -a cd
1
cd is a shell builtin

只有一个,因为它是内建,压根不在文件系统里。

再试试搜手册,-k 是按关键词搜所有手册,等价于 apropos。

1
man -k "copy file"
1
2
3
cp (1)               - copy files and directories
cpio (1) - copy files to and from archives
install (1) - copy files and set attributes

这个命令被我低估了很久,想干一件事但不知道用哪个命令时,man -k 比搜索引擎准,它搜的是本机真实存在的手册,不是别人三年前写的博客,不过它依赖一个索引数据库,第一次用很可能只回来一句。

1
man: nothing appropriate.

别再翻内核参数了,这条消息的意思很直白,数据库打得开,但里头没有能对上这组关键词的条目,不是你权限不够,也不是命令敲错了。真碰上索引压根没建的情况,man 反而不会这么说,它会退回直接扫描手册源,慢,但照样出结果,所以这句话有时候也意味着你搜的词太怪,手册里确实没人这么写。

想补齐索引,跑一下就好。

1
sudo mandb

最后看一眼真实的手册长什么样。

1
man 7 signal | head -40

里面有一张表,把所有信号、编号、默认动作列得清清楚楚,这张表比任何博客的整理都可信,因为它是跟着内核一起更新的。

which 查别名,只会给你空

--help 和 man 是同一份东西的两个入口?不是,前者是程序自己打印的摘要,随手就能改,后者是独立的手册文件,两者会不一致,甚至可能一个有另一个完全没有。which 能查所有命令?查不到别名、函数、内建,这是它的设计范围决定的,不是 bug,真想查身份就用 type。man 2 xxx 是程序员才看的?恰恰相反,man 2 open 会讲清 O_CLOEXEC 的语义、EINTR 什么时候返回,man 7 tcp 讲的是 TCP 连接有哪些状态,这些正是排查线上问题最硬的依据。手册太长看不完?不用从头看,SYNOPSIS 确认语法、OPTIONS 查参数、NOTES 和 BUGS 找坑,三节就够。网上搜更快?搜到的可能是别的发行版、别的内核版本,man 是你手上这台机器的,它不会骗你。

(我把 man -k 推荐给过两个新同事,两个人的第一反应都是这命令的名字太不直观,而 apropos 又长得像法语,所以它一直不火。)

到这儿,命令行这一关过了

你现在能判断自己站在哪一层、看得懂提示符、认得目录树、也知道该去哪查。

从这里往下,第一件事是搞清楚你敲下回车之后,从键盘到内核真正动手,中间到底转了几道手。