linux cat 的妙用

当我编写转换文件内容的 shell 单行程序时,它们通常看起来像这样

cat access.log | head -n 500 | grep mail | perl -e …

很多人都习惯性地称这是 cat 的无用用法,因为 head 可以将文件名作为参数,我们不需要额外的管道和 cat 命令。事实上,几乎所有命令都可以直接使用文件名,只有当我们想连接文件内容时才真正需要 cat

但无论如何,我这样做是有原因的。

我现在正在重读大卫-帕纳斯(David Parnas)关于模块化的一篇经典论文。每个软件工程师都应该读一读这篇文章,它非常精彩。在这篇文章中,我们将集中讨论一件事:我们都知道代码变更应该是孤立的。例如,我们应该能够通过添加代码来增加新功能,而不是修改现有代码。Parnas 以一种有趣的方式阐述了这一点。

我们必须认识到,[……]删除程序中的代码总是可以得到可运行的结果,[而且]任何软件系统都可以扩展。问题是,这些子集和扩展程序并不是我们设计的程序,如果我们一开始就只设计这种产品的话。此外,获得产品所需的工作量似乎与变化的性质完全不成比例。

他理想中的设计是,我们可以添加或删除代码,但程序看起来仍然是为现在正在做的事情而设计的;也就是说,你无法看出后来添加或删除了其他东西,一切看起来都像是原始设计的一部分。

Parnas 列出了我们在尝试更改时经常会遇到的四类问题。在本讨论中,第二类是相关问题。

许多程序都是由一系列组件构成的,每个组件从上一个组件接收数据、处理数据(并更改格式),然后将数据发送给链中的下一个程序。如果不需要这个链条中的某个组件,通常就很难删除该代码,因为前一个组件的输出与后一个组件的输入要求不兼容。必须替换掉一个只改变格式的程序。

一个例子是假定输入未排序的工资单程序。系统的一个组件接受未分类的输入,并产生按某个键分类的输出。如果公司采用的办公程序能对输入内容进行分类,那么处理过程中的这一阶段就没有必要了。要取消该程序,可能需要添加一个程序,将数据从输入格式的文件传输到适合下一阶段的格式的文件。

如果我们回到 shell 单行本的例子中,稍微眯一下眼睛,那么 access.log 字符串就是一种输入格式(描述包含相关内容的文件),而访问日志的内容则是另一种输入格式。这是本质上相同的两种表述。

如果我们去掉无用的 cat,改写为

head -n 500 access.log | grep mail | perl -e …

我们发现,head履行着两项职责:

  • 将 access.log 字符串转换为文件内容;以及
  • 提取文件内容的前 500 条记录。

当我们对 Perl 脚本感到满意时,我们可能会想在整个访问日志中运行它,而不仅仅是前 500 条记录。如果我们只删除head处理步骤,就会缺少一个将 access.log 字符串转换为访问日志内容的步骤。我们可以将这一职责转移到 grep 调用中,但这意味着我们必须修改某些现有组件才能删除另一个组件–这可不行!

自然的解决方案是无用地使用 cat。有了将文件名转换为文件内容的单独处理步骤,我们就可以删除任何中间处理步骤,而剩下的仍然是一个正常运行的流水线。人们可以随意抱怨,但我会继续编写模块化代码,哪怕只是 shell 单行代码。

本文文字及图片出自 Useful Uses of cat

阅读余下内容
 

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注


京ICP备12002735号