如何使用 GNU 文本实用程序
2009-06-30 04:17:00 来源:WEB开发网核心提示:本教程展示如何使用 GNU 文本实用程序集合来处理日志文件、文档、结构化文本数据库,以及其他文本性的数据或内容源,如何使用 GNU 文本实用程序,本集合中的实用程序经过 UNIX/Linux 开发人员几十年的改进,已证明是有用的,一旦理解了这些实用程序,将它们组合到已保存的 shell 脚本中是相当简单的,并且应该是您
本教程展示如何使用 GNU 文本实用程序集合来处理日志文件、文档、结构化文本数据库,以及其他文本性的数据或内容源。本集合中的实用程序经过 UNIX/Linux 开发人员几十年的改进,已证明是有用的,并且应该是您用于一般文本处理任务的第一选择。
本教程是为 Linux/UNIX 程序员和系统管理员编写的,属于初级至中级水平。
学习本教程的前提条件
对于本教程,您应该一般地熟悉一些类 UNIX 环境,特别是命令行 shell。您本身 不需要是一个程序员:事实上,本教程所讲述的技术将对系统管理员和需要处理特殊报告、日志文件、项目文档以及类似内容的用户最有用(因此对正式的编程代码处理不是那么有用)。在学习本教程的整个过程中,最好随时打开一个 shell,并试验本教程所展示的例子以及它们的一些变化形式。
基本概念将在简介:UNIX 哲学中回顾,您可以在其中复习管道、流、grep 和脚本编程的基础知识。
David Mertz 对于处理文本具有持久的爱好。 他甚至专门为此编写了一本书 Text Processing in Python ,并且经常在他为 IBM developerWorks 撰写的文章和专栏中谈及相关的主题。
关于本教程内容的技术问题和意见,请联系 David Mertz 或者单击任一屏顶部的"反馈"。 David 的 Web 站点 也是相关信息的一个很好来源。
简介:UNIX哲学
组合使用小型实用程序来完成大型任务
在诸如 Linux、FreeBSD、Mac OS X、Solaris、AIX 等受 UNIX 启发的操作系统中,开发环境甚至 shell 和工作环境的背后都存在一种共同的哲学。 这种哲学的要旨就是使用小型实用程序来 完满地(没有其他负面影响)完成每个小型任务,然后组合使用这些实用程序来执行复合任务。GNU 项目所产生的大多数产品都支持这种哲学――实际上特定的 GNU 实现已经移植到许多平台上,有些平台甚至传统上未被看作是 UNIX 类的。然而,Linux 内核必定是更有点单一性的软件――虽然如此,但是其内核模块、文件系统、视频驱动程序等都是相当组件化的。
对于本教程,您应该一般地熟悉一些类 UNIX 环境,特别是命令行 shell。 您本身不需要是一个程序员:事实上,本教程所讲述的技术将对系统管理员和需要处理特殊报告、日志文件、项目文档以及类似内容的用户最有用(因此对正式的编程代码处理不是那么有用)。在学习本教程的整个过程中,最好随时打开一个 shell,并试验本教程所展示的例子以及它们的一些变化形式。。
文件和流
如果这种 UNIX 哲学 具有倡导最低限度的模块化组件和协作的道义论的一面的话,它还具有本体论的一面:"一切皆文件"。抽象地说,文件 只是支持一些操作的对象:首先是读取和写入字节,但是也有诸如指出其当前位置和弄清何时到达文件结尾这样的操作。UNIX 权限模型也是围绕文件的概念来建立的。
具体地说,文件可以是可记录介质上的一个具体区域(并具有由文件系统提供的关于其名称、大小、在磁盘上的位置等的标记)。但是一个文件也可以是 /dev/ 层次结构中的一个虚拟设备,或者通过 TCP/IP 或通过诸如 NFS 这样的高级协议传来远程流。重要的是,特殊文件 STDIN、STDOUT 和 STDERR 可用于读取或写到用户控制台,以及用于在实用程序之间传递数据。这些特殊文件可通过虚拟文件名称来表示,并具有特殊的语法:
STDIN 是/dev/stdin 和/或 /dev/fd/0
STDOUT 是 /dev/stdout 和/或 /dev/fd/1
STDERR 是 /dev/stderr 和/或 /dev/fd/2
UNIX 的文件本体论的优点在于,这里讨论的大多数实用程序都将统一而中立地处理各种数据源,而不管实际位于字节传输之下的存储或传输机制是什么。
重定向和管道
UNIX/Linux 实用程序的通常组合方式是使用管道和重定向。许多实用程序或者自动地或者可选地从 STDIN 接受输入,并将它们的输出发送到 STDOUT(特殊的消息则发送到 STDERR)。管道将一个实用程序的 STDOUT 发送到另一个实用程序的 STDIN(或者发送到对同一个实用程序的新的调用)。重定向或者将一个文件的内容作为 STDIN 来读入,或者将 STDOUT 和/或 STDERR 输出发送到一个指定的文件。重定向通常用于保存数据以供以后处理或重复处理(对于后者,实用程序将使用 STDIN 重定向)。
在几乎所有的 shell 中,管道都使用竖线 | 符号来执行,而重定向都使用大于号和小于号来执行:> 和 <。为了重定向 STDERR,可使用 2>,或使用 &> 来同时将 STDOUT 和 STDERR 重定向到同一个地方。您还可以使用双大于号(>>)来将输出附加到一个现有文件的末尾。例如:
源码:----------------------------------------------------$ foo fname | bar - > myout 2> myerr
--------------------------------------------------
这里,实用程序 foo 可能处理名为 fname 的文件,并输出到 STDOUT。实用程序 bar 使用了一种普遍用法:当输出取自 STDIN 而不是取自指定的文件时指定一个短划线(其他某些实用程序仅接受 STDIN)。来自 bar 的 STDOUT 保存在 myout 中,它的 STDERR 则保存在 myerr 中。
文本实用程序是什么?
GNU 文本实用程序是一些用于处理和操作文本文件和流的工具集合,它们随着类 UNIX 操作系统的演进而被提炼,结果证明它们是最有用的。其中的大多数都是早期 UNIX 实现的组成部分,虽然许多已随着时间的推移而增添了附加的选项。
档案文件 textutils-2.1 中收集的实用程序包括 27 个工具;然而,GNU 项目维护者最近已决定将这些工具打包为更大的集合 coreutils-5.0 的一部分(预计可能会在后面的版本中这样做)。在从 BSD 而不是从 GNU 工具演变而来的系统上,相同的实用程序的打包方式可能稍有不同,但是仍然提供了大多数相同的实用程序。
本教程重点介绍传统上包括在 textutils 中的 27 个实用程序,偶尔会提及和使用一般在类 UNIX 系统上可用的相关工具。然而,我将跳过对实用程序 ptx(permuted index,置换索引)的介绍,因为它的用途太窄,并且包括在这里也太难于理解。
grep(通用正则表达式处理器)
这个工具本身 并不是 textutils 的组成部分,但是仍然值得特别提及。实用程序 grep 是使用最广泛的 UNIX 实用程序之一,经常用于文本实用程序的管道输入或输出。
grep 所做的工作从一种意义上说非常简单,从另一种意义上说又相当复杂,难于理解。基本上,grep 识别文件中与某个正则表达式相匹配的行。它有一些开关允许您以各种方式修改输出, 比如打印周围的上下文行,给匹配行编号,或者仅识别其中出现匹配项的文件而不是识别单独的行。本质上,grep 只是用于文件中的行的过滤器(但是功能非常强大)。
grep 的复杂部分是正则表达式,您可以指定它来描述需要的匹配条件。不过这将在另一个教程(请参阅 参考资料 中的 developerWorks 教程"Using regular expressions")中讲述。还有其他许多实用程序支持正则表达式,但是 grep 是其中最通用的一个工具,因而比起使用其他工具所提供的较弱的过滤器,将 grep 放进管道中通常更容易。一个简单的 grep 例子如下:
源码:-------------------------------------------------- $ grep -c [Ss]ystem$ * 2> /dev/null | grep :[^0]$
INSTALL:1
aclocal.m4:2
config.log:1
configure:1------------------------------------
这个例子列出包含以"system"结尾的行(或许首字母是大写的)的文件;同时显示这些实例出现的次数(也就是如果不为 0 的话)。(实际上,这个例子不会处理大于 9 的计数)。
shell 脚本
虽然文本实用程序旨在以各种有用的格式产生输出(通常通过命令行开关来修改),但是有些时候能够显式地跳转和循环是很有用的。诸如 bash 这样的 shell 允许您通过控制流来组合实用程序,从而执行更复杂的任务。shell 脚本对于封装需要多次执行的复合任务特别有用,特别是那些涉及任务参数化的任务。
解释 bash 脚本编程显然超出了本教程的范围。请参阅 参考资料 以了解 developerWorks 的"Bash by example"系列中对 bash 的介绍。一旦理解了这些实用程序,将它们组合到已保存的 shell 脚本中是相当简单的。只是出于演示目的,下面提供一个使用 bash 进行流控制的简单例子:
源码:----------------------------------------------------
[~/bacchus/articles/scratch/tu]$ cat flow
#!/bin/bash
for fname in `ls $1`; do
if (grep $2 $fname > /dev/null); then
echo "Creating: $fname.new" ;
tr "abc" "ABC" < $fname > $fname.new
fi
done
[~/bacchus/articles/scratch/tu]$ ./flow '*' bash
Creating: flow.new
Creating: test1.new
[~/bacchus/articles/scratch/tu]$ cat flow.new
#!/Bin/BAsh
for fnAme in `ls $1`; do
if (grep $2 $fnAme > /dev/null); then
eCho "CreAting: $fnAme.new" ;
tr "ABC" "ABC" < $fnAme > $fnAme.new
fi
done
面向流的过滤
cat 和 tac
最简单的文本实用程序只是
[]
更多精彩
赞助商链接