# 欢迎，新用户！就像装满六发子弹的俄罗斯轮盘赌

![](/files/ySq5zTyG9h4J0mpaVnWl)

> 有一辆 Ken Thompson 参与设计的汽车。与大多数汽车不同，这辆车没有速度表、油量表，也没有那些困扰现代司机的各种愚蠢警示灯。而如果司机犯错，仪表盘中央会亮起一个巨大的“？”问号。Thompson 说：“有经验的司机通常会知道出了什么问题。”
>
> ——佚名

计算机系统的新用户（甚至是经验丰富的用户）都需要系统给予一定程度的友好支持。至少，友好的计算机系统应为其用户提供以下便利：

* 符合功能逻辑的命令名称
* 对危险命令的谨慎处理
* 命令行为及其选项和参数解释上的一致性和可预测性
* 容易查找且易读的在线文档
* 命令失败时提供可理解且有用的反馈

Unix 在开发期间，并未接待任何访客。每位来访者都是戴着安全帽的承包商，被指派去完成营房中尚未完成的部分。不幸的是，不仅从未邀请过工程师参与设计，甚至从未预见和规划过他们的需求。因此，许多标准设施，比如抽水马桶、中央供暖和可开启的窗户，现在都极其难以且昂贵地进行改装。尽管如此，建造者们仍对它的设计赞叹不已，以至于他们甘愿躺在没有烟雾探测器的房间地板上睡觉。

在大部分历史中，Unix 是大学和工业研究人员的研究平台。随着廉价工作站的爆炸性增长，Unix 进入了新时代——交付平台时代。这个变化很容易确定时间点：当工作站厂商为了降低非开发者的价格，将 C 编译器从标准软件套件中拆分出来时。这一变化的界限在化石记录中略显模糊，但大多发生在 1990 年左右。因此，只有在过去几年中，厂商才真正开始关心终端用户的需求和期望，而非程序员。这也解释了为什么如今公司们试图编写图形用户界面来“替代”shell 的需求。我们并不羡慕这些公司的任务。

## 晦涩难懂的命令名称

Unix 新用户总是对 Unix 所选择的命令名称感到惊讶。无论在 DOS 还是 Mac 上接受多少培训，都无法为你准备好去欣赏那些神秘而简短的两个字母命令，比如 `cp`、`rm` 和 `ls` 的“壮丽之美”。

我们这些使用过七十年代初期输入/输出设备的人怀疑，这种简短命令的退化，源于当时常用的 ASR-33 电传打字机的速度、可靠性，尤其是键盘的限制。与现代键盘不同，现代键盘按键行程的设计基于反馈原理，所需力量仅是关闭微动开关的力，而当时电传打字机的按键（据记忆）需要行程超过半英寸，而且需要施加类似骑自行车发电机的力量。用这些“怪兽”触摸打字，甚至可能撞破你的指关节。

如果 Dennis 和 Ken 用的是 Selectric 打字机而不是电传打字机，我们现在可能会打“copy”和“remove”而不是“cp”和“rm”。这再次证明，技术既限制了我们的选择，亦扩展了我们的可能。

经历了二十多年，继续沿用这种传统的理由是什么？历史的不可抗拒力量，也就是现有的代码和书籍。如果某个厂商把 `rm` 替换成“remove”，那么所有介绍 Unix 的书籍将不再适用，该厂商系统上的所有调用 `rm` 的 shell 脚本也将失效。这样的厂商还不如干脆停止执行 POSIX 标准。

一个世纪以前，快速的打字员被键盘卡住，于是工程师设计了 QWERTY 键盘来减慢他们的速度。计算机键盘不会卡键，但我们至今仍在使用 QWERTY 键盘。再过一百年，世界仍然会继续使用 `rm` 命令。

## 意外总会发生

用户非常关心他们的文件和数据。他们使用计算机来生成、分析和存储重要信息，信任计算机能够保护这些宝贵的资料。没有这种信任，用户与系统的关系就会变得紧张。Unix 滥用我们的信任，固执地拒绝保护用户免受危险命令的伤害。特别是 `rm` 这个最危险的命令，其存在的唯一理由就是删除文件。

所有 Unix 新手都曾“意外”且不可恢复地删除过重要文件。即使是专家和系统管理员也会“意外”删除文件。因丢失时间、努力和文件恢复而产生的费用可能每年达数百万美元。这本应是个值得解决的问题；我们不明白为什么 Unix 用户对此视而不见。难道真的喜欢祸不单行吗？

文件在 Unix 下比在任何其他操作系统中更频繁地“死亡”并需要“重生”，原因如下：

1. Unix 文件系统没有版本号。自动文件版本控制，会给文件的新版本赋予新名字或编号后缀，从而保留文件的旧版本，避免新版本覆盖旧版本。而在 Unix 中覆盖经常发生。
2. Unix 程序员对错误报告和检查的态度极其宽松。许多程序根本不检查输出文件的所有字节是否能成功写入磁盘，有些甚至不确认输出文件是否创建成功。但是这些程序在完成后肯定会删除输入文件。
3. 是 Unix shell（命令行解释器），而不是客户端程序，来扩展通配符“*”。shell 扩展“*”导致客户端程序（如 `rm`）无法进行合理检查以防止灾难。即使是 DOS 也会验证像 `del *.*` 这样危险的命令，但在 Unix 中，删除文件的程序无法区分用户是输入了：

```sh
% rm *
```

还是

```sh
% rm 文件1 文件2 文件3 ...
```

如果能保存并传递原始命令行给被调用的客户端命令，这种情况会有所改善。也许可以利用环境变量来实现。

4. 文件删除是永久性的。Unix 没有“undelete”（撤销删除）命令。其他更安全的操作系统中，删除文件会将文件所占用的磁盘块标记为“可用”，并将目录项移动到一个专门的“已删除文件”目录里。仅当磁盘空间不足时，才会回收这些已删除文件占用的空间。

大多数操作系统采用这种“删除 - 清空”的两步机制来将文件占用的磁盘块归还给系统。这并不是高深的技术，甚至早在 1984 年，Macintosh 就将“扔进垃圾桶”和“清空垃圾桶”区分开了。Tenex 在 1974 年就已经实现了这一点。

DOS 和 Windows 给你的东西更像是带有存水弯的污水管道，而不是垃圾桶。它们只是简单地删除文件，但如果你想“伸手”把文件找回来，至少还有一些能购买的工具来完成这项工作。这些工具确实有效——不过只是有时有效。

这四个问题相互纠缠，导致了无谓但可预见且每天都会发生的文件删除。比 Unix 出现之前，人们已经理解并广泛使用了更好的技术。而如今，随着 Unix 被接受为世界上的“标准”操作系统，这些技术正逐渐被遗忘。

欢迎来到未来。

## “rm”是永远的灾难

上述原则结合起来，形成了现实生活中的恐怖故事。Usenet 新闻组 alt.folklore.computers 上的一系列交流说明了我们的观点：

> 日期：1990 年 1 月 10 日 星期三
>
> 发件人：<djones@megatest.uucp>（Dave Jones）
>
> 主题：`rm *`
>
> 新闻组：alt.folklore.computers2[^1]
>
> 有没有人曾打算输入：
>
> ```sh
> % rm *.o
> ```
>
> 却不小心输入成了：
>
> ```sh
> % rm *>o
> ```
>
> 现在你得到一个新的空文件叫做“o”，但它占用了大量空间！

实际上，你甚至可能不会得到名为“o”的文件，因为 shell 文档并没有明确说明输出文件“o”是在通配符展开之前还是之后创建的。shell 虽然是编程语言，但它不是很精确。

> 日期：1990 年 1 月 10 日 星期三 15:51 CST
>
> 发件人：<ram@attcan.uucp>
>
> 主题：回复：`rm *`
>
> 新闻组：alt.folklore.computers
>
> 我也经历过类似的 `rm` 灾难。有一次，我要从磁盘上删除一个文件系统，类似于 `/usr/foo/bin`。我当时在 `/usr/foo` 目录下，已经通过以下命令删除了系统的几个部分：
>
> ```sh
> % rm -r ./etc
> % rm -r ./adm
> ```
>
> ……诸如此类。但当轮到删除 `./bin` 时，我漏打了那个点。系统对此反应很糟糕。

Unix 并没有设计成能在失去其 `/bin` 目录这种致命打击后继续存活的智能操作系统。一个智能的操作系统本该给用户一个恢复的机会（或者至少确认用户是否真的想让操作系统变得无法使用）。

Unix 爱好者把偶尔的文件删除当作正常现象。例如，看看 comp.unix.questions FAQ[^2] 中的以下摘录：

> 6）如何“恢复删除”的文件？
>
> 总有一天，你会不小心输入类似这样的命令：
>
> ```sh
> % rm * .foo
> ```
>
> 结果你实际上删除了 `*` 而不是 `*.foo`。把这看作是一个成长的仪式。
>
> 当然，所有体面的系统管理员都应该定期做备份。联系你的系统管理员，看看是否有最近的备份文件可用。

“成长的仪式”？在其他任何行业，制造商都不可能对有缺陷的产品持如此轻率的态度。“但法官大人，爆炸的油箱不过是成长的仪式。”“各位陪审团成员，我们将证明因安全装置失效导致的损害……”

“电锯对它的用户来说也只是一条成长的必经之路。”“法庭阁下，我们将证明，被基廷先生骗走毕生积蓄的退休人员，也只是经历了一条成长的必经之路。”没错。

## 无法通过改变 `rm` 的行为来解决问题

被 `rm` 咬了几次之后，人们往往会冲动地给 `rm` 命令设置别名，使其变成 `rm -i`，或者更好的是，用别的程序替换 `rm` 命令，将要删除的文件移动到一个特殊的隐藏目录，比如 `~/.deleted`。这些做法会让无辜的用户产生一种虚假的安全感。

> 日期：1990 年 4 月 16 日星期一 18:46:33
>
> 发件人：Phil Agre <agre@gargoyle.uchicago.edu>
>
> 收件人：Unix 痛恨者
>
> 主题：删除
>
> 在我们的系统上，`rm` 并不删除文件，而是以某种晦涩的方式重命名文件，以便所谓的“undelete”（不是 `unrm`）可以恢复它。
>
> 这让我在删除文件时有些不小心，因为我总以为自己可以随时恢复它们。其实不然。Emacs 中的删除文件命令并不是这样工作的，Dired 中的 D 命令也不是如此。原因当然是因为撤销删除的协议并不是操作系统文件模型的一部分，而只是某个叫做“rm”的 shell 命令中被人为拼凑进去的一部分。
>
> 因此，我必须在脑海中保持两个不同的概念——“删除”文件和“用 `rm` 删除”文件，并时刻提醒自己，当我的大脑对我的手说“删除它”时，我实际上是在执行哪个操作。

一些 Unix 专家顺着 Phil 的论点推向逻辑上的荒谬，坚持认为最好不要让像 `rm` 这样的命令变得哪怕稍微友好一点。他们的论点虽然不是用我们所用的表达方式，但认为试图让 Unix 更加友好，赋予它基本的便利设施，实际上会让它变得更糟。不幸的是，他们是对的。

> 日期：Thu, 11 Jan 90 17:17 CST
>
> 来自：<merlyn@iwarp.intel.com>（Randal L. Schwartz）
>
> 主题：不要重载命令！（原主题：Re: `rm *`）
>
> 新闻组：alt.folklore.computers
>
> 我们打断本新闻组的正常内容，带来以下信息……
>
> ```c
> #ifdef SOAPBOX_MODE
> ```
>
> 请，拜托，请不要鼓励大家用“安全”命令去重载标准命令。
>
> （1）人们通常把它放在 `.cshrc` 文件错误的位置，导致那些想用“rm”命令删除文件的脚本莫名其妙地要确认，或者误以为文件已被删除，结果把磁盘填满了。
>
> （2）没有办法保护所有可能意外删除文件的情况，如果你保护了一个常用的，用户就会认为“任何操作都可以撤销”（绝对不是真的！）。
>
> （3）如果用户请系统管理员（我现在正干这个活）在终端帮忙，命令不按正常方式运行，当你有用户要帮忙，还有四个“紧急：现在需要处理”的任务排队时，这真的令人抓狂。
>
> 如果你想要 `rm` 命令要求确认，请用：
>
> ```sh
> % alias del rm -i
> ```
>
> 并且不要用 `rm`！哎，人们，这有多难？！？
>
> ```sh
> #endif
> ```
>
> 现在恢复你们原定的“我敲代码敲得那么久，机器只有 0，没有 0 和 1”的讨论……
>
> 只是另一个系统黑客

最近，有人在 comp.unix.questions 发布请求，征集系统管理员们最喜欢的管理员恐怖故事。在 72 小时内，收到了 300 条留言。其中大多数都涉及本章所述的方法导致文件丢失。有趣的是，这些都是经验丰富的 Unix 用户，他们本应更为谨慎。更奇怪的是，尽管这些消息中报告了数百万美元的损失，但当 Unix 被批评“不够用户友好”时，绝大多数系统管理员却站出来为 Unix 辩护。

用户不友好？Unix 甚至都不是“系统管理员友好”！例如：

> 日期：1988 年 9 月 14 日 星期三 01:39 EDT
>
> 发件人：Matthew P Wiener <weemba@garnet.berkeley.edu>
>
> 收件人：RISKS-LIST\@kl.sri.com4[^3]
>
> 主题：回复：“单击键”
>
> 在 Unix 上，即使是有经验的用户也可能因为 `rm` 造成很大破坏。我从未费心写过安全的 `rm` 脚本，因为我不会误删文件。然后有一天，我倒霉地敲了 `!r` 来重复历史命令列表中的某条命令，惊恐地看到屏幕回显 `rm -r *` ——那是我之前在另一个目录下执行过的命令，那个目录我花时间清理过。
>
> 也许 C shell 可以增加一个 `nohistclobber` 选项？这仍然是我唯一一次误删或覆盖文件，而且这完全是个最低级别的意外陷阱！
>
> 巧合的是，就在前几天，我听到一个天真的用户对运行 `rm *` 删除了他刚刚从邮件中错误创建的文件“\*”的恐惧。所幸，按字母顺序排得靠前的文件没有写权限，所以删除操作很快就停止了。

这条信息的作者建议通过给 shell 添加选项“nohistclobber”（禁止历史命令覆盖）来进一步修改，以弥补操作系统对星号名称展开机制的根本缺陷。不幸的是，这种“修补”就像用一层新油漆来修复受水损坏的墙壁，效果差强人意。

## 始终如一的不如一

可预测的命令共享选项名称，参数顺序大致相同，并且在可能的情况下产生类似的输出。保持一致性需要某个中央机构集中精力制定标准。Macintosh 上的应用程序之所以一致，是因为它们遵循苹果发布的一本指导手册。而 Unix 工具则从未有过这种机构。因此，有些工具的选项前面带有短横线（`-`），有些则没有；有些读取标准输入，有些则不；有些写入标准输出，有些则不；有些创建的文件是全局可写的，有些则不是；有些报告错误，有些不报告；有些选项与文件名之间有空格，有些没有。

Unix 最初是一次实验，旨在构建一款尽可能简洁干净的操作系统。作为实验，它成功了，但作为生产系统，AT\&T 的研究人员逾越了他们的目标。为了让更多人能够使用，操作系统必须功能丰富。如果系统本身不提供这种基本的丰富性，用户就会在底层框架上添加功能。Dave Mankins 认为，Unix 的一致性和可预测性问题的根本原因，可能是它没有为 AT\&T 以外的程序员提供用于扩展的知识框架。

> 日期：1989 年 3 月 4 日 星期六 19:25:58 EST
>
> 发件人：<dm@think.com>
>
> 收件人：Unix 痛恨者
>
> 主题：Unix 软蛋们的自娱自乐
>
> Unix 软蛋们喜欢吹嘘每个命令的概念简单性。大多数人可能认为是子程序的东西，Unix 软蛋却把它们包成完整命令，带有自己的参数语法和选项。
>
> 这并非完全是蠢主意，因为在没有其他解释器的情况下，通过将这些小子程序连接起来，可以写出相当强大的程序。
>
> 可惜没人想到把这些命令做成真正的子程序，这样你就可以把它们链接到自己的程序中，而不用自己写正则表达式解析器（这也是为什么 `ed`、`sed`、`grep` 和各种 shell 对正则表达式的理解相似但又略有不同的原因）。
>
> Unix 审美的最高成就就是拥有一款只做一件事且做得很好的命令。纯粹主义者反对的是，在伯克利的初级程序员折腾过之后，用于将多个文件连接输出 的程序 `cat` 现在有了选项。（正如 Rob Pike——也许是终极 Unix 极简主义者所说：“Cat 从伯克利回来时挥舞着旗帜。”）
>
> 这种哲学在业余者手中，导致了令人难以理解且令人昏昏欲睡的混乱，比如有两款程序 `head` 和 `tail`，它们分别打印文件的开头部分或结尾部分。尽管它们的操作是互补的，但 `head` 和 `tail` 是不同的程序，由不同作者编写，并且选项也不同！

如果热力学定律在这里适用，那么 Unix 就会像其他那些随着时间慢慢堆积演化的系统一样，拥有同样缺乏一致性的结构和相同程度的熵，也不会比它们更好或更差。然而，架构上的缺陷却进一步加剧了混乱和意外的程度。特别是，程序被禁止访问调用它们的命令行，以防它们“自燃”。Shell 充当了中介的角色，根据用户的输入对命令行进行清洗和重构。不幸的是，Shell 的表现更像克鲁索探长（Inspector Clouseau），而不是弗洛伦斯·南丁格尔（Florence Nightingale）。

我们提到过，Shell 会执行通配符展开操作，也就是说，它会将星号（`*`）替换成目录中所有文件的列表。这是缺陷一：程序本应调用一个库来执行通配符展开。

按照约定，程序会把选项作为第一个参数，通常前面带一个短横线（`-`）。这是缺陷二：选项（开关）和其他参数本应是不同的实体，就像在 VMS、DOS、Genera 和其他许多操作系统中一样。

最后，Unix 的文件名几乎可以包含任何字符，包括不可打印字符。这是缺陷三。

这些架构决策之间会产生糟糕的相互作用。当 Shell 在展开 `*` 时，它会按字母顺序列出文件名，而短横线（`-`）在这种字典顺序的等级制度中排在最前。因此，当使用 `*` 时，以短横线开头的文件名会最先出现。这些文件名会被当作传递给程序的选项，从而导致不可预测、令人惊讶，甚至危险的行为。

> 日期：1990 年 1 月 10 日 星期三 10:40 CST
>
> 发件人：<kgg@lfcs.ed.ac.uk>（Kees Goossens）
>
> 主题：回复：`rm *`
>
> 新闻组：alt.folklore.computers
>
> 还有一名可怜学生的故事，他的主目录里正好有一个名为 `-r` 的文件。他想要删除所有非目录文件（我猜是这样），于是他输入了：
>
> ```sh
> % rm *
> ```
>
> ……没错，它确实删除了除了那个可爱的 `-r` 文件以外的所有东西……
>
> 幸运的是，我们的备份系统还算不错。

有些 Unix 受害者把这个“将文件名当成选项”的 bug 变成了一个“特性”，通过在目录中放一个名为 `-i` 的文件来实现。输入 `rm *` 时，shell 会将其展开为 `rm -i 文件名列表`，理论上它会在删除每个文件前请求确认。这个解决方案倒也不坏——前提是你不介意在每个目录里都放一个名为 `-i` 的文件。

也许我们应该修改 mkdir 命令，让它自动创建这个 `-i` 文件。然后再修改 `ls` 命令，使它不显示它。

### 令人头痛的文件名

我们认识好几个人，在重命名文件时打错字，结果创建了一个以破折号开头的文件名：

```sh
% mv 文件1 -文件2
```

现在试着把它改回来：

```sh
% mv -文件2 文件1
usage: mv [-if] f1 f2 or mv [-if] f1 ... fn d1
(‘fn’ 是文件或目录)
```

这个文件名在其他 Unix 命令中并不会出问题，因为 Unix 命令之间几乎没有一致性。例如，文件名“-文件 2”对 Unix 的“标准文本编辑器”ed 来说是完全合法的。下面这个例子就能正常工作：

```sh
% ed -文件2
4347
```

但即使你把文件另存为其他名字，或者决定彻底放弃这个文件、只想把它删掉，你的困境依然存在：

```sh
% rm -文件
usage: rm [-rif] 文件 ...
% rm ?文件
usage: rm [-rif] 文件 ...
% rm ?????
usage: rm [-rif] 文件 ...
% rm *文件2
usage: rm [-rif] 文件 ...
%
```

rm 会将文件名的第一个字符（破折号）解释为命令行选项；然后它会抱错字符“l”和“e”不是合法的选项。一个文件名以连字符开头——尤其是在这个破折号是通配符匹配的结果时——却被当作一连串选项来看待，这难道不有点疯狂吗？

Unix 提供了两种彼此独立且不兼容的临时“黑科技”方法，用来删除这个命名错误的文件：

```sh
% rm - -文件
```

```sh
% rm ./-文件
```

rm 的 man 手册中说明，一个单独的破折号位于 rm 命令和其第一个文件名之间时，会让 rm 把所有后续的破折号都当作文件名，而不是选项。但不知出于什么原因，rm 和它的近亲 mv 的用法说明中都没有列出这个“特性”。

当然，用破折号来表示“请忽略所有后续破折号”的做法，并不是通行的惯例，因为命令解析是每个程序各自处理的，并没有使用统一的标准库。像 `tar` 这样的程序，会用破折号来表示标准输入或标准输出。而其他一些程序则干脆无视它：

```sh
% touch -文件
touch: bad option -i
% touch - -文件
touch: bad option -i
```

### 逗乐你的朋友！让你的敌人抓狂

Unix 命令经常给出看似合理的结果：只有当你尝试去使用它们时，你才会意识到它们实际上是多么荒谬。

```sh
next% mkdir foo
next% ls -Fd foo
foo/
next% rm foo/
rm: foo/ directory
next% rmdir foo/
rmdir: foo/: File exists
```

这里有个逗乐和取悦你朋友的方法（感谢 Leigh Klotz 提供）。

首先，悄悄地做以下操作：

```sh
% mkdir foo
% touch foo/foo~
```

然后把这些咒语的结果展示给你的“受害者”看：

```sh
% ls foo*
foo~
% rm foo~
rm: foo~ nonexistent
% rm foo*
rm: foo directory
% ls foo*
foo~
%
```

最后，为了获得真正的快乐，试试这个：

```sh
% cat - - -
```

（提示：按三次 Ctrl-D 就能返回提示符！）

## 在线文档

人们投票选总统的次数比阅读印刷文档的次数还多。唯一真正起作用的文档，是那些在线上、只需按一个按键或点击鼠标即可获得的内容。Unix 文档的现状，以及其严重不足的程度，在本书中有专门一章讨论，这里我们仅指出 Unix 的 man 系统在最需要帮助的地方——新手用户面前——表现最差。

并非所有命令都是平等的：有些是由 shell 调用的独立程序，有些则是内置于 shell 中的命令。部分命令有自己的 man 页面，而有些则没有。Unix 期望你知道它们的区别。例如，`wc`、`cp` 和 `ls` 是 shell 之外的程序，有对应的 man 页面；但 `fg`、`jobs`、`set` 和 `alias`（这些名字哪来的？）则是 shell 内置命令，因此没有自己的 man 页面。

新手在被告知用“man 命令”查看文档时，很快会感到困惑，因为有些命令有文档，有些则没有。如果她使用的 shell 和第三方书籍中介绍的不同，想要理解更是难上加难，除非去请教专家。

## 错误消息和错误检查，压根没有

新手难免会出错，要么用了错误的命令，要么使用了正确的命令却带了错误的选项和参数。计算机系统必须能够检测这些错误并反馈给用户。不幸的是，Unix 程序很少这样做。相反，Unix 似乎刻意让错误相互叠加，最终导致致命后果。

在上一节中，我们展示了用 `rm` 命令不小心删除文件是多么容易。但你可能没有意识到，不用 `rm` 命令，也同样很容易删除文件。

### 要删除你的文件，试试编译器吧

某些版本的 cc 编译器经常会“坑”本科生，在检查输入文件的明显问题之前就删除之前的输出文件。

> 日期：1992 年 11 月 26 日 星期四 16:01:55 GMT
>
> 发件人：<tk@dcs.ed.ac.uk>（Tommy Kelly）
>
> 主题：求助！
>
> 新闻组：cs.questions9
>
> 机构：计算机科学基础实验室，
>
> 英国爱丁堡
>
> 我刚才执行了：
>
> ```sh
> % cc -o doit.c doit  
> ```
>
> 而不是：
>
> ```sh
> % cc -o doit doit.c  
> ```
>
> 不用说，我丢了文件 `doit.c`。
>
> 有没有办法能找回来？（从今天早上开始这个文件经过了大量修改）。
>
> \:-(

其他程序也表现出类似行为：

> 发件人：Daniel Weise <daniel@dolores.stanford.edu>
>
> 收件人：UNIX 痛恨者
>
> 日期：1993 年 7 月 1 日 星期四 09:10:50 -0700
>
> 主题：被 `tar` 和羽毛伺候了
>
> 经过多次尝试，我终于通过一条不稳定的欧洲链路 `ftp` 下来了这个 3.2MB 的文件。现在要解压它。
>
> 我输入：
>
> ```sh
> % tar -cf thesis.tar  
> ```
>
> …却没有任何反应。
>
> 哎呀。
>
> 是用 `c` 而不是 `x`吗？
>
> 是的。
>
> `tar` 因为没有指定文件而报错了吗？
>
> 没有。
>
> `tar` 发现问题了吗？
>
> 没有。
>
> `tar` 真的是没有打包任何文件吗？
>
> 是的。
>
> `tar` 覆盖了 `tar` 文件变成了垃圾吗？
>
> 当然，这就是 Unix。
>
> 我要不要再浪费 30 分钟从欧洲重新下载这个文件？
>
> 当然，这就是 Unix。
>
> 太神奇了。我确信这个设计缺陷已经让很多人吃过亏。有很多简单的方法可以避免这种损失：错误报告、文件版本号、二次确认用户是否真的要覆盖已有文件，等等。感觉他们就是故意努力营造这种损失。

这个漏洞尤其严重地影响那些使用 `tar` 来备份系统的系统管理员。不止一个系统管理员在备份脚本中写成了 `tar xf …` 而不是 `tar cf …`。

这是一个诚实的错误。磁带在旋转。系统管理员几乎不会怀疑 `tar` 实际上是在尝试从磁带读取指定的文件，而不是把它们写入磁带。事实上，一切看似都在按计划进行，直到有人真正需要恢复某个文件。然后，惊喜来了：备份根本不是备份。

由于几乎没有错误检查，许多“程序员的工具”给高级用户提供了多种丢失重要信息的可能。

> 日期：1992 年 10 月 4 日 星期日 00:21:49 PDT
>
> 来自：Pavel Curtis <pavel@parc.xerox.com>
>
> 收件人：Unix 痛恨者
>
> 主题：这么多混蛋可以选择……
>
> 我有个程序，叫 foo，持续运行在我的机器上，提供网络服务，并且每 24 小时检查点保存其（庞大）内部状态。
>
> 我切换到包含该程序运行版本的目录，由于这不是程序的开发目录，我好奇到底运行的是哪个版本的代码。该代码用 RCS 维护，所以我自然尝试输入：
>
> ```sh
> % ident foo  
> ```
>
> 以查看可执行文件中包含了哪些源文件的版本。\[别管 RCS 明显不合适，或者 `ident` 的工作方式极其野蛮；我还有更重要的事……]
>
> 当然，这次我打错了字，因为手指自动反应，更喜欢单词 `indent` 而不是无意义的 `ident`：
>
> ```sh
> % indent foo  
> ```
>
> 结果，`indent` 是 UNIX 那个脑残的 C 代码格式化工具的名字。写这个烂东西的混蛋有没有考虑检查它的输入是否是 C 文件（比如，天啊，检查文件名是不是以 `.c` 结尾）？我想你已经知道答案了。更进一步，这个混蛋还决定，如果给 `indent` 只传一个参数，那你一定是想直接在源文件上做格式化，覆盖旧文件内容。
>
> 不过别担心，混蛋知道你可能担心这么做会破坏文件，所以它会保存一份旧内容的备份到 `foo.BAK`。它是直接把 `foo` 重命名成 `foo.BAK` 吗？当然不是，它傻乎乎地把 `foo` 的所有内容复制到 `foo.BAK`，然后截断 `foo` 文件，再写入新的格式化代码。
>
> 混蛋。
>
> 你现在应该明白这个故事的重点了……
>
> 当一款 Unix 程序运行时，如果它正在从它的可执行文件分页（paging out），你要是乱动文件的各个部分，它会非常生气。特别是它会崩溃，且无法恢复。我因此丢失了程序 20 小时的状态变更。
>
> 当然，那些设计（咳）Unix 的混蛋们根本不关心像版本化文件系统这种复杂东西，否则我的命就能救回来了。那些混蛋们也根本想不到要锁定任何你当前正在分页的文件，对吧？
>
> 混蛋这么多，干脆全杀了算了？
>
> Pavel
>
> 想象一下，如果有一种外墙油漆，在干燥时会释放氯气。根据说明，在外面用没问题，但你如果用它来粉刷卧室，可能会因此丧命。你觉得这种油漆能在市场上撑多久？肯定撑不到 20 年。

## 报错的笑话

当服务员把一盘满是碟子的托盘掉到地上时，你会笑吗？Unix 粉丝会笑。

他们总是第一个嘲笑那些无助的用户，试图理解那些根本和他们刚输入的内容无关的错误信息。

有人把 Unix 中一些荒谬的错误信息当作笑话发布出来。以下这些 Unix 双关语在 Usenet 上传播，没有署名作者。它们适用于 Csh。

```sh
% rm meese-ethics
rm: meese-ethics 不存在

% ar m 上帝
ar: 上帝不存在

% "你如何评价 Dan Quayle 的无能？
引号不匹配。

% ^性别转换^ 操作进行得怎么样？
修改失败。

% 如果我每次国会花费 $ 都有 (，我会有什么？
( 太多。

% make love
Make：不知道如何 make love。停止。

% 和我睡觉
非法字符

% 能借个火吗？
无匹配项。

% man：你为什么离婚？
man：参数过多。

% ^什么是糖精？
错误的替换。

% %blow
%blow：没有这个任务。
```

这些幽默尝试适用于 Bourne shell：

```sh
$ PATH=pretending! /usr/ucb/which sense  
在 pretending 毫无意义！  

$ drink <bottle; opener  
bottle：无法打开  
opener：未找到  

$ mkdir matter; cat >matter  
matter：无法创建  
```

## Unix 的态度

我们描绘了一幅相当惨淡的画面：晦涩难懂的命令名称、不一致且不可预测的行为、缺乏对危险命令的保护、勉强及格的在线文档，以及对错误检查和健壮性的松懈态度。访问 Unix 之家的人不会有愉快的体验。他们像是第三世界联合国救援任务的访客，而不是迪士尼乐园的游客。Unix 是怎么变成这样的？部分原因是历史因素，正如我们已经指出的。但答案还有另一部分：多年来构建和扩展 Unix 的人的文化。这种文化被称为“Unix 哲学”。

Unix 哲学并不是贝尔实验室和 Unix 系统实验室发布的书面建议，它是一种自由流动的道德准则。不同作者列举了它的不同属性。Don Libes 和 Sandy Ressler 在《Life with Unix》（Prentice Hall，1989）一书中对此做了特别好的总结：

* 小即美。
* 10％ 的工作解决了 90％ 的问题。
* 面对选择时，选择更简单的方案。

根据对 Unix 程序和工具的经验观察，更准确地总结 Unix 哲学是：

* 小程序比功能齐全或正确的程序更受欢迎。
* 粗制滥造完全可以接受。
* 面对选择时，选择逃避。

Unix 并没有哲学，只有一种态度。那种态度认为，简单且半途而废的工作比复杂且执行良好的工作更有价值。那种态度宣称程序员的时间比用户的时间更重要，即使每个程序员对应成千上万的用户。它是一种赞美最低公分母的态度。

> 日期：1989 年 12 月 24 日 星期日 19:01:36 EST
>
> 来自：David Chapman \<zvona\@ai..mit.edu>
>
> 收件人：Unix 痛恨者
>
> 主题：终止作业；Unix 设计范式。
>
> 我最近学会了如何在 Unix 上终止作业。在此过程中，我学到了很多关于 Unix 的智慧和强大之处，想和你们分享。
>
> 当然，你们大多数人并不使用 Unix，所以知道如何终止作业可能没什么用。然而，像我这样的一些人可能会偶尔运行 TeX 作业，这种情况下，知道如何终止作业就至关重要。无论如何，“kill”命令背后的设计原则在整个 Unix 中被严格应用，所以这条信息可能更具普遍意义。
>
> Unix 允许你用 ^Z 挂起一个作业，或用 ^C 退出并终止。然而，LaTeX 会捕获 ^C。结果，我过去经常堆积几十个 LaTeX 作业。这倒不是真的让我困扰，但我觉得还是学会如何清理它们比较邻里友好。
>
> 大多数操作系统都有“kill”命令，Unix 也有。在大多数操作系统中，`kill` 命令终止一个进程。而 Unix 的实现更加通用：“kill”命令向进程发送消息。这体现了第一个 Unix 设计原则：
>
> * 通过使操作完全通用来赋予用户权力。
>
> `kill` 命令非常强大；它允许你向进程发送各种消息。例如，你可以向进程发送一个让它自我终止的消息，这个消息是 `-9`。`-9` 当然是最大的单数字消息，这体现了另一个重要的 Unix 设计原则：
>
> * 选择反映功能的简单名称。
>
> 据我所知，在所有其他操作系统中，没有参数的 `kill` 命令会终止当前作业。然而，Unix 的 `kill` 命令总是需要一个作业参数。这个明智的设计选择体现了另一个明智的设计原则：
>
> * 通过要求长命令和危险操作的确认，防止用户意外搞砸自己。
>
> 这个原则在 Unix 中的应用极为广泛且文档完善，这里我就不详细说明了，只是顺便提及 Unix 中的注销和文件删除实现。
>
> 在我所知的所有其他操作系统中，`kill` 命令的作业参数是作业名称。这是一个不充分的接口，因为你可能会有多个 LaTeX 作业（例如），它们的名字都是“latex”，因为它们都是 LaTeX 作业。因此，`kill -9 latex` 会产生歧义。
>
> 像大多数操作系统一样，Unix 有一个列出你作业的命令，助记符叫“jobs”。`jobs` 命令的输出大致如下：
>
> ```sh
> zvona@rice-chex> jobs  
> [1] - Stopped latex  
> [2] - Stopped latex  
> [3] + Stopped latex  
> ```
>
> 这让你很容易将特定的 LaTeX 作业与作业号对应起来，作业号显示在方括号内。
>
> 如果你的思维受到设计不佳操作系统的影响，你可能会认为 `kill -9 1` 会终止列表中的第 1 号作业。但你会发现它实际上会给你一个友好的错误信息：
>
> ```sh
> zvona@rice-chex> kill -9 1  
> 1: not owner  
> ```
>
> `kill` 命令正确的参数是进程 ID。进程 ID 是像 18517 这样的数字。你可以用“ps”命令找到作业的进程 ID，“ps”命令会列出作业及其进程 ID。找到正确的进程 ID 后，你只需：
>
> ```sh
> zvona@rice-chex> kill -9 18517  
> zvona@rice-chex>  
> [1] Killed latex  
> ```
>
> 注意 Unix 会先给你提示符，然后才告诉你作业已经被终止。（用户输入会出现在以“\[1]”开头的行之后。）这体现了另一个 Unix 设计原则：
>
> * 只告诉用户他需要知道的信息，并且不要提前告诉。不要用多余信息增加用户的认知负担。
>
> 希望这个小练习对你有所启发。我从学习中深刻体会到 Unix 的设计哲学。Unix `kill` 命令的优雅、强大和简洁应该成为我们所有人的一课。

[^1]: 由 Chris Garrigues 转发至 Unix 痛恨者。

[^2]: comp.unix.questions 是块国际性的公告板，新的 Unix 古拉格用户会在这里向那些待得太久、已经不知道其他世界的人提问。FAQ 是一份常见问题列表，汇集了众多用户“自伤”的报告。

[^3]: 由 Michael Travers 转发到 Unix 痛恨者。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://book.bsdcn.org/unix-tong-hen-zhe-shou-ce/di-yi-bu-fen-yong-hu-you-hao/huan-ying-xin-yong-hu-jiu-xiang-zhuang-man-liu-fa-zi-dan-deeluo-si-lun-pan-du.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
