成为一个过滤器
几乎所有实用程序都最适合想像为过滤器,尽管有一些非常有用的实用程序不符合这个模型。(例如,某个程序在执行计数时可能非常有用,尽管它作为过滤器工作得并不好。仅接受命令行参数作为输入并潜在地产生复杂输出的程序可能非常有用。)然而,大多数实用程序都应该作为过滤器来工作。根据惯例,过滤器对文本的行起作用。大多数过滤器都应该支持多个输入文件。
记住实用程序需要在命令行和脚本中运行。有时,理想的行为会稍有不同。例如,大多数版本的 ls 都会在向终端写出时自动将输入排序到多个列中。grep 的默认行为是在指定多个文件的情况下打印从其中找到匹配项的那个文件名称。这样的差别应该与用户希望的实用程序工作方式有关,而不是与其他事项有关。例如,旧版本的 GNU bc 在启动时显示强迫性的版权标记。请不要那样做。让您的实用程序仅做它应该做的事情。
实用程序喜欢生活在管道中。管道允许实用程序专注于自己的工作,而不是去关注旁枝末节。为了生活在管道中,实用程序需要从标准输入读取数据,然后向标准输出写出数据。如果您希望处理记录,那么您最好能够使每一行成为一个“记录”。诸如 sort 和 join 之类的现有程序已经在那样考虑了。它们将会因为您这样做而感谢您。
我偶尔使用这样一个实用程序,它针对一个文件树反复调用其他程序。这充分利用了标准的 UNIX 实用程序过滤器模型,但是该模型仅适用于读取输入然后写出输出的实用程序;不能将它用于就地操作或接受输入输出文件名的实用程序。
可以使用标准输入来运行的大多数程序也完全可以针对单个文件或一组文件运行。注意,可以证明这样违背了反对重复工作的规则;显而易见,这可以通过将 cat 的输出馈送给该系列中的下一个程序来解决。然而这在实践中似乎是合理的。
有些程序可能合法地读取一种格式的记录,但是却产生完全不同的输出。这样的一个例子就是将输入材料划分为列的实用程序。这样一个实用程序可能将输入中的行视为记录,但是却在输出中的每行上产生多个记录。
并非每个实用程序都完全符合这个模型。例如,xargs 不是接受记录而是接受文件名作为输入,并且所有的实际处理都是由其他程序完成的。
通用化
尝试将任务看作与您实际执行的任务类似;如果您能找出这些任务的通用描述,那么最好尝试编写一个符合该描述的实用程序。例如,如果您发现自己一天在根据词法对文本排序,而另一天在根据数字对文本排序,那么考虑编写一个通用排序实用程序也许是有意义的。
对功能进行通用化有时会导致您发现:某个看起来似乎像单个实用程序的程序,实际上却是配合起来使用的两个实用程序。这很好。编写两个设计良好的实用程序可能要比编写一个丑陋的或复杂的实用程序更容易。
做好一件事情并不意味着 仅仅 做一件事情。它意味着处理一致但有用的问题空间。许多人都使用 grep。然而,它的大量效用在于执行相关任务的能力。grep 的各种选项完成许多小实用程序的工作,如果这些工作都由单独的小实用程序来完成,最终会造成大量共享的、重复的代码。
这条规则,以及做好一件事情的规则,都是一个根本原理的必然结果:无论何时都要尽可能避免代码重复。如果您编写半打程序,其中每个都对行排序,您最终可能必须六次修复六个类似的 bug,而不是去使用一个得到更好维护的 sort 程序。
这是编写实用程序的一部分,即把大多数工作添加到完成该实用程序的过程中。您也许没有时间在最初就完全通用化一个实用程序,但是当您一直使用该实用程序就会获得相应的回报。
有时,向某个程序添加相关功能是很有用的,即使这个功能并不是用来完成完全相同的任务。例如,当运行在终端设备上时,对原始二进制数据进行完美打印的程序可能更为有用,因为它使终端进入原始模式。这样使得测试涉及键盘映射、新键盘等的问题变得容易多了。不确定为什么当您按 delete 键时却得到代字号(~)吗? 这是弄清实际发送了什么内容的容易途径。这并不是完全相同的任务,但它足够类似,因而可能成为一个附加特性。清单 2 中的 errno 实用程序就是通用化的很好例子,因为它同时支持数字和符号名称。