TLCL之第三章(4)


第三章

7. 文本处理

cat - 连接文件并且打印到标准输出
sort - 给文本行排序
uniq - 报告或省略重复行
cut - 从每行中删除文本区域
paste - 合并文件文本行
join - 基于某个共享字段来联合两个文件的文本行
comm - 逐行比较两个有序的文件
diff - 逐行比较文件
patch - 给原始文件打补丁
tr - 翻译或删除字符
sed - 用于筛选和转换文本的流编辑器
aspell - 交互式拼写检查器

1. 文本应用程序

  1. 文档

许多人使用纯文本格式来编写文档。

  1. 网页

世界上最流行的电子文档类型可能就是网页了,网页是文本文档,它们使用HTML或者是XML作为标记语言来描述文档的可视格式。

  1. 电子邮件

从本质上来说,email是一个基于文本的媒介,为了传输,甚至非文本的附件也被转换成文本表示形式。

  1. 打印输出

在类Unix的系统中,输出会以纯文本格式发送到打印机,或者如果页面包含图形,其或别转换成一中文本格式的页面描述语言,以PostScript著称,然后再被发送给一款能产生图形点阵的程序,最后被打印出来。

  1. 程序源码

文本处理对于软件开发者而言至关重要是因为所有的软件都起始于文本格式。源代码,程序员实际上是程序员实际编写的一部分程序,是文本格式。

cat

这个cat程序具有许多有趣的选项,其中许多选项用来帮助更好地可视化文本内容。-A选项用来在文本中显示非打印字符。

[me@linuxbox ~]$ cat > foo.txt
    The quick brown fox jumped over the lazy dog.
[me@linuxbox ~]$ cat -A foo.txt
^IThe quick brown fox jumped over the lazy dog.       $
[me@linuxbox ~]$

在输出中,tab字符在我们的文本中有^I字符来表示(control-I),结果证明,它和tab字符是一样的。我们也看到一个$字符出现在文本行真正的结尾处,表明文本包含末尾的空格。


隐藏的回车符来自于哪里?
它们来自于DOS和Windows,Unix和DOS在文本文件中定义每行结束的方式不相同,Unix通过一个换行符(ASCII10)来结束一行,然而MS-DOS和它的衍生品使用回车(ASCII13)和换行符序列来终止每个文本行。
有几种方法能够把文件从DOS格式转变为Unix格式。在许多Linux系统中,有两个程序叫做dos2unix和unix2dos,它们能在两种格式之间转变文本文件,然而,如果你的系统中没有安装dos2unix程序,也不要担心。文件从dos格式转变为unix格式的过程非常简单,它只简单地涉及到删除违规的回车符,通过随后本章中讨论的一些程序,这个工作很容易完成。


cat程序也包含用来修改文本的选项,最著名的两个选项是-n,其给文本行添加行号和-s,禁止输出多个空白行。

sort

这个sort程序对标准输出的内容或命令行中指定的一个或多个文件进行排序,然后把排序结果发送到标准输出。

[me@linuxbox ~]$ sort > foo.txt
c
b
a
[me@linuxbox ~]$ cat foo.txt
a
b
c

常见的sort程序选项

选项长选项描述
-b–ignore-leading-blanks默认情况下,对整行进行排序从每行的第一字符开始。这个选项导致sort程序忽略每行开头的空格,从第一个非空白字符开始排序
-f–ignore-case让排序不区分大小写
-n–numeric-sort基于字符串的数值来排序。使用此选项允许根据数字值执行排序,而不是字母值
-r–reverse按相反的顺序排序,结果按照降序排列,而不是升序
-k–key=field1[,field2]对从field1到field2之间的字符排序,而不是整个文本行。
-m–merge把每个参数看作是一个预先排好序的文件。把多个文件合并成一个排好序的文件,而没有执行额外的排序。
-o–output=file把排好序的输出结果发送到文件,而不是标准输出
-t–field-separator=char定义域分隔字符。默认情况下,域由空格或制表符分隔

du命令可以确定最大的磁盘空间用户,这个du命令列出的输出结果按照路径名来排序:

[me@linuxbox ~]$ du -s /usr/share/* | sort -nr | head
509940         /usr/share/locale-langpack
242660         /usr/share/doc
197560         /usr/share/fonts
179144         /usr/share/gnome
146764         /usr/share/myspell
144304         /usr/share/gimp
135880         /usr/share/dict
76508          /usr/share/icons
68072          /usr/share/apps
62844          /usr/share/foomatic
[me@linuxbox ~]$ ls -l /usr/bin | sort -nr -k 5 | head
-rwxr-xr-x 1 root   root   8234216  2008-04-0717:42 inkscape
-rwxr-xr-x 1 root   root   8222692  2008-04-07 17:42 inkview
...

指定n和r选项来执行相反的数值排序,并且指定-k 5,让sort程序使用第五字段作为排序的关键值。

[me@linuxbox ~]$ sort --key=1,1 --key=2n distros.txt
Fedora         5     03/20/2006
Fedora         6     10/24/2006
Fedora         7     05/31/2007
...

–key=1,1 --key=2n与-k 1,1 -k 2n 格式是等价的,在第一个key选项的实例中,我们指定了一个字段区域。因为我们只想对第一个字段排序,我们指定了1,1,意味着“始于并且结束于第一个字段”,在第二个实例中,我们指定了2n,因为着第二个字段是排序的键值,并且按照数值排序。一个选项字母可能被包含在一个键值说明符的末尾,其用来指定排序的种类。这些选项字母和sort程序的全局选项一样:b(忽略开头的空格),n(数值排序),r(逆向排序),等等。
列表中第三个字段包含的日期格式不利于排序。在计算机中,日期通常设置为YYYY-MM-DD格式,这样使按时间顺序排序变得容易,但是上面的格式并不是这样的。我们可以通过使用key选项来指定偏移量:

[me@linuxbox ~]$ sort -k 3.7nbr -k 3.1nbr -k 3.4nbr distros.txt
Fedora         10    11/25/2008
Ubuntu         8.10  10/30/2008
SUSE           11.0  06/19/2008
...

通过指定-k 3.7,我们只是sort程序使用一个排序键值,其始于第三个字段中的第七个字符,对应于年的开头。

上面都是默认字段界定符为tabs或空格,但是有的时候不是默认的字段界定符,而sort程序提供了一个-t选项来定义分隔符。

[me@linuxbox ~]$ sort -t ':' -k 7 /etc/passwd | head
me:x:1001:1001:Myself,,,:/home/me:/bin/bash
root:x:0:0:root:/root:/bin/bash
dhcp:x:101:102::/nonexistent:/bin/false
gdm:x:106:114:Gnome Display Manager:/var/lib/gdm:/bin/false
hplip:x:104:7:HPLIP system user,,,:/var/run/hplip:/bin/false
klog:x:103:104::/home/klog:/bin/false
messagebus:x:108:119::/var/run/dbus:/bin/false
polkituser:x:110:122:PolicyKit,,,:/var/run/PolicyKit:/bin/false
pulse:x:107:116:PulseAudio daemon,,,:/var/run/pulse:/bin/false

uniq

与sort程序相比,这个uniq程序是个轻量级程序。当给定一个排好序的文件,uniq会删除任意重复行,并且把结果发送到标准输出。它常常和sort程序一块使用,来清理重复的输出。

常用的uniq选项

选项说明
-c输出所有的重复行,并且每行开头显示重复的次数
-d只输出重复行,而不是特有的文本行
-f n忽略每行开头的n个字段,字段之间由空格分隔,正如sort程序中的空格分隔符,然而,不同于sort程序,uniq没有选项来设置备用的字段分隔符。
-i在比较文本行的时候忽略大小写
-s n跳过(忽略)每行开头的n个字符
-u只输出独有的文本行,这是默认的

2. 切片和切块

cut

这个cut程序被用来从文本行中抽取文本,并把其输出到标准输出。它能够接受多个文件参数或者标准输出。

cut程序选择项

选项说明
-c char_list从文本行中抽取由char_list定义的文本。这个列表可能由一个或多个逗号分隔开的数值区间组成
-f field_list从文本行中抽取一个或多个由field_list定义的字段。这个列表可能包括一个或多个字段,或由逗号分隔开的字段区间
-d delim_char当指定-f选项之后,使用delim_char做为字段分隔符。默认情况下,字段之间必须由单个tab字符分隔开
–complement抽取整个文本行,除了那些-c和/或-f选项指定的文本

cut命令最好用来从其他程序产生的文件中抽取文本,而不是从人们直接输入的文本中抽取。

[me@linuxbox ~]$ cat -A distros.txt
SUSE^I10.2^I12/07/2006$
Fedora^I10^I11/25/2008$
SUSE^I11.0^I06/19/2008$
Ubuntu^I8.04^I04/24/2008$
Fedora^I8^I11/08/2007$
SUSE^I10.3^I10/04/2007$
Ubuntu^I6.10^I10/26/2006$
Fedora^I7^I05/31/2007$
Ubuntu^I7.10^I10/18/2007$
Ubuntu^I7.04^I04/19/2007$
SUSE^I10.1^I05/11/2006$
Fedora^I6^I10/24/2006$
Fedora^I9^I05/13/2008$
Ubuntu^I6.06^I06/01/2006$
Ubuntu^I8.10^I10/30/2008$
Fedora^I5^I03/20/2006$

字段之间仅仅是单个tab字符,没有嵌入空格,因为这个文件使用了tab而不是空格,我们将使用-f选项来抽取一个字段:

[me@linuxbox ~]$ cut -f 3 distros.txt
12/07/2006
11/25/2008
06/19/2008
04/24/2008
11/08/2007
10/04/2007
10/26/2006
05/31/2007
10/18/2007
04/19/2007
05/11/2006
10/24/2006
05/13/2008
06/01/2006
10/30/2008
03/20/2006

因为我们的 distros 文件是由 tab 分隔开的,最好用 cut 来抽取字段而不是字符。这是因为一个由 tab 分离的文件, 每行不太可能包含相同的字符数,这就使计算每行中字符的位置变得困难或者是不可能。在以上事例中,然而, 我们已经抽取了一个字段,幸运地是其包含地日期长度相同,所以通过从每行中抽取年份,我们能展示怎样 来抽取字符:

[me@linuxbox ~]$ cut -f 3 distros.txt
12/07/2006
11/25/2008
06/19/2008
04/24/2008
11/08/2007
10/04/2007
10/26/2006
05/31/2007
10/18/2007
04/19/2007
05/11/2006
10/24/2006
05/13/2008
06/01/2006
10/30/2008
03/20/2006
Because our distros file is tab-delimited, it is best to use cut to extract fields rather than characters. This is because when a file is tab-delimited, it is unlikely that each line will contain the same number of characters, which makes calculating character positions within the line difficult or impossible. In our example above, however, we now have extracted a field that luckily contains data of identical length, so we can show how character extraction works by extracting the year from each line:

因为我们的 distros 文件是由 tab 分隔开的,最好用 cut 来抽取字段而不是字符。这是因为一个由 tab 分离的文件, 每行不太可能包含相同的字符数,这就使计算每行中字符的位置变得困难或者是不可能。在以上事例中,然而, 我们已经抽取了一个字段,幸运地是其包含地日期长度相同,所以通过从每行中抽取年份,我们能展示怎样 来抽取字符:

[me@linuxbox ~]$ cut -f 3 distros.txt | cut -c 7-10
2006
2008
2008
2008
2007
2007
2006
2007
2007
2007
2006
2006
2008
2006
2008
2006

通过对我们列表再次运行cut命令,我们能够抽取从位置7到10的字符,其对应于日期字段的年份。


展开tabs

distros.txt 的文件格式很适合使用 cut 程序来抽取字段。但是如果我们想要 cut 程序 按照字符,而不是字段来操作一个文件,那又怎样呢?这要求我们用相应数目的空格来 代替 tab 字符。幸运地是,GNU 的 Coreutils 软件包有一个工具来解决这个问题。这个 程序名为 expand,它既可以接受一个或多个文件参数,也可以接受标准输入,并且把 修改过的文本送到标准输出。
如果我们通过 expand 来处理 distros.txt 文件,我们能够使用 cut -c 命令来从文件中抽取 任意区间内的字符。例如,我们能够使用以下命令来从列表中抽取发行年份,通过展开 此文件,再使用 cut 命令,来抽取从位置 23 开始到行尾的每一个字符:

[me@linuxbox ~]$ expand distros.txt | cut -c 23-

Coreutils软件包也提供了unexpand程序,用tab来代替空格。


当操作字段的时候,有可能指定不同的字段分隔符,而不是tab字符。

[me@linuxbox ~]$ cut -d ':' -f 1 /etc/passwd | head
root
daemon
bin
sys
sync
games
man
lp
mail
news

使用-d选项,我们能够指定冒号作为字段分隔符。

paste

与cut功能相反,它会添加一个或多个文本列到文件中,而不是从文件中抽取文本列。它通过读取多个文件,然后把每个文件中的字段整合成单个文本流,输入到标准输出。类似于cut命令,paste接受多个文件参数和/或标准输入。

join

在某些方面,join命令类似于paste,它会往文件中添加列,但是它使用了独特的方法来完成。一个join操作通常与关系型数据库有关联,在关系型数据库中来自于多个享有共同关键域的表格的数据结合起来,得到一个期望的结果。这个join程序执行相同的操作,它把来自于多个基于共享关键域的文件的数据结合起来。

3. 比较文本

通常比较文本文件的版本很有帮助,对于系统管理员和软件开发者来说,这个尤为重要。

comm

比较两个文本文件,并且会系那是每个文件特有的文本行和共有的文本行。

[me@linuxbox ~]$ cat > file1.txt
a
b
c
d
[me@linuxbox ~]$ cat > file2.txt
b
c
d
e

下一步,我们将使用comm命令来比较这两个文件:

[me@linuxbox ~]$ comm file1.txt file2.txt
a
        b
        c
        d
    e

正如我们所见到的,comm命令产生了三列输出。第一列包含第一个文件独有的文本行;第二列,文本行是第二列独有的;第三列包含两个文件共有的文本行。comm支持-n形式的选项,这里n代表1,2或3。这些选项使用的时候,指定了要隐藏的列。

diff

类似于comm程序,diff程序被用来检测文件之间的差异。然而,diff是一款更加复杂的工具,它支持许多输出格式,并且一次能处理许多文本文件。软件开发员经常使用diff程序来检查不同的程序源码版本之间的更改,diff能够递归地检查源码目录,经常称之为源码树。diff程序的一个常见用例是创建diff文件或补丁,它会被其它程序使用,例如patch程序,来把文件从一个版本转换为另一个版本。

[me@linuxbox ~]$ diff file1.txt file2.txt
1d0
< a
4a4
> e

diff更改命令

改变含义
r1ar2把第二个文件中位置r2处的文件行添加到第一个文件中的r1处
r1cr2用第二个文件中位置r2处的文本行更改(替代)位置r1处的文本行
r1dr2删除第一个文件中位置r1处的文本行,这些文本行将会出现在第二个文件中位置r2处

在这种格式中,一个范围就是由逗号分隔开的开头行和结束行的列表。虽然这种格式是默认情况,但是它并不像其他可选格式一样被广泛地使用,最流行的两种格式是上下文模式和统一模式。

当使用上下文模式(带上-c选项),我们将看到:

[me@linuxbox ~]$ diff -c file1.txt file2.txt
*** file1.txt    2008-12-23 06:40:13.000000000 -0500
--- file2.txt   2008-12-23 06:40:34.000000000 -0500
***************
*** 1,4 ****
- a
  b
  c
  d
--- 1,4 ----
  b
  c
  d
  + e

这个输出结果以两个文件名和它们的时间戳开头。第一个文件用星号做标记,第二个文件用短横线做标记。纵观列表的其他部分,这些标记象征它们各自代表的文件。

#其表示第一个文件中从第一行到第四行的文本行。
*** 1,4 ***

#这表示第二个文件中从第一行到第四行的文本行。
--- 1,4 ---

在更改组内,文本行以四个指示符之一开头:

diff上下文模式更改指示符

指示符含义
blank上下文显示行。它并不表示两个文件之间的差异
-删除行,这一行将会出现在第一个文件中,而不是第二个文件内
+添加行,这一行将会出现在第二个文件内,而不是第一个文件中
!更改行,将会显示某个文本行的两个版本,每个版本会出现在更改组的各自部分

这个统一模式相似于上下文模式,但是更加简洁,通过-u选项来指定它:

[me@linuxbox ~]$ diff -u file1.txt file2.txt
--- file1.txt 2008-12-23 06:40:13.000000000 -0500
+++ file2.txt 2008-12-23 06:40:34.000000000 -0500
@@ -1,4 +1,4 @@
-a
 b
 c
 d
+e

上下文模式和统一模式之间最显著的差异就是重复上下文的消除,这就使得统一模式的输出结果要比上下文 模式的输出结果简短。在我们上述实例中,我们看到类似于上下文模式中的文件时间戳,其紧紧跟随字符串 @@ -1,4 +1,4 @@。这行字符串表示了在更改组中描述的第一个文件中的文本行和第二个文件中的文本行。 这行字符串之后就是文本行本身,与三行默认的上下文。每行以可能的三个字符中的一个开头:

diff统一模式更改指示符

字符含义
blank两个文件都包含这一行
-在第一个文件中删除这一行
+添加这一行到第一个文件中

patch

这个patch程序被用来把更改应用到文本文件中,它接受从diff程序的输出,并且通常被用来把较老的文件版本转变为较新的文件版本。让我们考虑一个著名的例子。Linux 内核是由一个 大型的,组织松散的贡献者团队开发而成,这些贡献者会提交固定的少量更改到源码包中。 这个 Linux 内核由几百万行代码组成,虽然每个贡献者每次所做的修改相当少。对于一个贡献者 来说,每做一个修改就给每个开发者发送整个的内核源码树,这是没有任何意义的。相反, 提交一个 diff 文件。一个 diff 文件包含先前的内核版本与带有贡献者修改的新版本之间的差异。 然后一个接受者使用 patch 程序,把这些更改应用到他自己的源码树中。使用 diff/patch 组合提供了两个重大优点:

  1. 与整个源码树的大小相比,一个diff文件非常小。
  2. 一个diff文件简洁地显示了所做的修改,从而允许程序补丁的审阅者能快速地评估它。

当然,diff/patch能工作与任何文本文件,不仅仅是源码文件,它同样适用于配置文件或任意其他文本。

准备一个 diff 文件供 patch 程序使用,GNU 文档(查看下面的拓展阅读部分)建议这样使用 diff 命令:

diff -Naur old_file new_file > diff_file

old_file 和 new_file 部分不是单个文件就是包含文件的目录。这个 r 选项支持递归目录树。
一旦创建了 diff 文件,我们就能应用它,把旧文件修补成新文件。

patch < diff_file

我们将使用测试文件来说明:

[me@linuxbox ~]$ diff -Naur file1.txt file2.txt > patchfile.txt
[me@linuxbox ~]$ patch < patchfile.txt
patching file file1.txt
[me@linuxbox ~]$ cat file1.txt
b
c
d
e

4. 运行时编辑

我们对于文本编辑器的经验是它们主要是交互式的,我们手动移动光标,然后输入我们的修改。然而,也有没交互式的方法来编辑文本,例如,通过单个命令把一系列修改应用到多个文件中。

tr

这个tr程序被用来更改字符。我们可以把它看做是一种基于字符的查找和替换操作。换字是一种把字符从一个字母转换为另一个字母的过程。

[me@linuxbox ~]$ echo "lowercase letters" | tr a-z A-Z
LOWERCASE LETTERS

tr命令接受两个参数:要被转换的字符集以及相对应的转换后的字符集。字符集可以用三种方式来表示:

  1. POSIX字符类
  2. 一个枚举列表。ABCD…
  3. 一个字符域。例如A-Z
    大多数情况下,两个字符集应该长度相同,然而,有可能第一个集合大于第二个,尤其如果我们想要把多个字符转换为单个字符:
[me@linuxbox ~]$ echo "lowercase letters" | tr [:lower:] A
AAAAAAAAA AAAAAAA

除了换字之外,tr 命令能允许字符从输入流中简单地被删除。在之前的章节中,我们讨论了转换 MS-DOS 文本文件为 Unix 风格文本的问题。为了执行这个转换,每行末尾的回车符需要被删除。 这个可以通过 tr 命令来执行,如下所示:

tr -d '\r' < dos_file > unix_file

这里的 dos_file 是需要被转换的文件,unix_file 是转换后的结果。这种形式的命令使用转义序列 \r 来代表回车符。查看 tr 命令所支持地完整的转义序列和字符类别列表,试试下面的命令:

[me@linuxbox ~]$ tr --help

tr也可以完成另一个技巧,使用-s选项,tr命令能“挤压”(删除)重复的字符实例:

[me@linuxbox ~]$ echo "aaabbbccc" | tr -s ab
abccc

注意重复的字符必须是相邻的,如果不相邻则没有挤压效果:

[me@linuxbox ~]$ echo "abcabcabc" | tr -s ab
abcabcabc

sed

sed是stream editor(流编辑器)的简称。它对文本流,即一系列指定的文件或标准输入进行编辑。
sed的工作方式是要不给出单个编辑命令(在命令行)要不就是包含多个命令的脚本文件名,然后它就按行来执行这些命令:

[me@linuxbox ~]$ echo "front" | sed 's/front/back/'
back

在这个例子中,我们使用 echo 命令产生了一个单词的文本流,然后把它管道给 sed 命令。sed,依次, 对流文本执行指令 s/front/back/,随后输出“back”。我们也能够把这个命令认为是相似于 vi 中的“替换” (查找和替代)命令。

sed 中的命令开始于单个字符。在上面的例子中,这个替换命令由字母 s 来代表,其后跟着查找 和替代字符串,斜杠字符做为分隔符。分隔符的选择是随意的。按照惯例,经常使用斜杠字符, 但是 sed 将会接受紧随命令之后的任意字符做为分隔符。我们可以按照这种方式来执行相同的命令:

[me@linuxbox ~]$ echo "front" | sed 's_front_back_'
back

通过紧跟命令之后使用下划线符,则它变成界定符。sed有设置界定符的能力,使命令的可读性更强。
sed 中的大多数命令之前都会带有一个地址,其指定了输入流中要被编辑的文本行。如果省略了地址, 然后会对输入流的每一行执行编辑命令。最简单的地址形式是一个行号。我们能够添加一个地址 到我们例子中:

[me@linuxbox ~]$ echo "front" | sed '1s/front/back/'
back

给我们的命令添加地址1,就导致只对仅有一行文本的输入流的第一行执行替换操作。如果我们指定另一个数字:

[me@linuxbox ~]$ echo "front" | sed '2s/front/back/'
front

我们看到没有执行这个编辑命令,因为我们的输入流没有第二行。地址可以用许多方式来表达。这里是 最常用的:

sed地址表示法

地址含义
n行号,n是一个正整数
$最后一行
/regexp/所有匹配一个 POSIX 基本正则表达式的文本行。注意正则表达式通过 斜杠字符界定。选择性地,这个正则表达式可能由一个备用字符界定,通过\cregexpc 来 指定表达式,这里 c 就是一个备用的字符。
addr1,addr2从 addr1 到 addr2 范围内的文本行,包含地址 addr2 在内。地址可能是上述任意 单独的地址形式。
first~step匹配由数字 first 代表的文本行,然后随后的每个在 step 间隔处的文本行。例如 1~2 是指每个位于奇数行号的文本行,5~5 则指第五行和之后每五行位置的文本行。
addr1,+n匹配地址 addr1 和随后的 n 个文本行。
addr!匹配所有的文本行,除了 addr 之外,addr 可能是上述任意的地址形式。

通过使用这一章中早前的文件,我们将演示不同种类的地址表示法。首先,一系列行号:

[me@linuxbox ~]$ sed -n '1,5p' distros.txt
SUSE           10.2     12/07/2006
Fedora         10       11/25/2008
SUSE           11.0     06/19/2008
Ubuntu         8.04     04/24/2008
Fedora         8        11/08/2007

在每个例子中,我们打印出一系列的文本行,开始于第一行,直到第五行。为此,我们使用 p 命令, 其就是简单地把匹配的文本行打印出来。然而为了高效,我们必须包含选项 -n(不自动打印选项), 让 sed 不要默认地打印每一行。

下一步,我们使用正则表达式:

[me@linuxbox ~]$ sed -n '/SUSE/p' distros.txt
SUSE         10.2     12/07/2006
SUSE         11.0     06/19/2008
SUSE         10.3     10/04/2007
SUSE         10.1     05/11/2006

通过包含由斜杠界定的正则表达式/SUSE/,我们能够孤立出包含它的文本行,和 grep 程序的功能 是相同的。
最后,我们将使用否定上面的操作,通过给这个地址添加一个感叹号:

[me@linuxbox ~]$ sed -n '/SUSE/!p' distros.txt
Fedora         10       11/25/2008
Ubuntu         8.04     04/24/2008
Fedora         8        11/08/2007
Ubuntu         6.10     10/26/2006
Fedora         7        05/31/2007
Ubuntu         7.10     10/18/2007
Ubuntu         7.04     04/19/2007
Fedora         6        10/24/2006
Fedora         9        05/13/2008
Ubuntu         6.06     06/01/2006
Ubuntu         8.10     10/30/2008
Fedora         5        03/20/2006

这里我们看到期望的结果:输出了文件中所有的文本行,除了那些匹配这个正则表达式的文本行。

sed基本编辑命令

命令含义
=输出当前的行号
a在当前行之后追加文本
d删除当前行
i在当前行之前插入文本
p打印当前行。默认情况下,sed 程序打印每一行,并且只是编辑文件中匹配 指定地址的文本行。通过指定-n 选项,这个默认的行为能够被忽略
q退出 sed,不再处理更多的文本行。如果不指定-n 选项,输出当前行。
Q退出 sed,不再处理更多的文本行。
s/regexp/replacement/只要找到一个 regexp 匹配项,就替换为 replacement 的内容。 replacement 可能包括特殊字符 &,其等价于由 regexp 匹配的文本。另外, replacement 可能包含序列 \1到 \9,其是 regexp 中相对应的子表达式的内容。在 replacement 末尾的斜杠之后,可以指定一个 可选的标志,来修改 s 命令的行为。
y/set1/set2执行字符转写操作,通过把 set1 中的字符转变为相对应的 set2 中的字符。 注意不同于 tr 程序,sed 要求两个字符集合具有相同的长度。
sed 's/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/' distros.txt

sed默认情况下只接受基本的正则表达式,在表达式中的几个字符会 被当作文字字面值,而不是元字符。我们能够通过反斜杠的自由应用来转义。
s命令的另一个功能是使用可选标志,其跟随替代字符串。一个最重要的可选标志是g标志,其指示sed对某个文本行全范围地执行查找和替换操作,不仅仅是对第一个实例,这是默认行为。

[me@linuxbox ~]$ echo "aaabbbccc" | sed 's/b/B/'
aaaBbbccc

[me@linuxbox ~]$ echo "aaabbbccc" | sed 's/b/B/g'
aaaBBBccc

使用-f 选项,也有可能在一个脚本文件中构建更加复杂的命令。 为了演示,我们将使用 sed 和 distros.txt 文件来生成一个报告。我们的报告以开头标题,修改过的日期,以及 大写的发行版名称为特征。为此,我们需要编写一个脚本,所以我们将打开文本编辑器,然后输入以下文字:

# sed script to produce Linux distributions report

1 i\
\
Linux Distributions Report\

s/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/
y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/

我们把sed脚本保存为distros.sed文件,然后像这样运行它:

[me@linuxbox ~]$ sed -f distros.sed distros.txt
Linux Distributions Report
SUSE	10.2	2006-12-07
FEDORA	10	    2008-11-25
SUSE	11.0	2008-06-19
UBUNTU	8.04	2008-04-24
FEDORA	8	    2007-11-08
SUSE	10.3	2007-10-04
UBUNTU	6.10	2006-10-26
FEDORA	7	    2007-05-31
UBUNTU	7.10	2007-10-18
UBUNTU	7.04	2007-04-19
SUSE	10.1	2006-05-11
FEDORA	6	    2006-10-24
FEDORA	9	    2008-05-13

使用 cat 来给每行文本编号:

[me@linuxbox ~]$ cat -n distros.sed
1 # sed script to produce Linux distributions report
2
3 1 i\
4 \
5 Linux Distributions Report\
6
7 s/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/
8 y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/

我们脚本文件的第一行是一条注释。如同 Linux 系统中的许多配置文件和编程语言一样,注释以#字符开始, 然后是人类可读的文本。注释可以被放到脚本中的任意地方(虽然不在命令本身之中),且对任何 可能需要理解和/或维护脚本的人们都很有帮助。
第二行是一个空行。正如注释一样,添加空白行是为了提高程序的可读性。
许多 sed 命令支持行地址。这些行地址被用来指定对输入文本的哪一行执行操作。行地址可能被 表示为单独的行号,行号范围,以及特殊的行号“$”,它表示输入文本的最后一行。
从第三行到第六行所包含地文本要被插入到地址 1 处,也就是输入文本的第一行中。这个 i 命令 之后是反斜杠回车符,来产生一个转义的回车符,或者就是所谓的连行符。这个序列能够 被用在许多环境下,包括 shell 脚本,从而允许把回车符嵌入到文本流中,而没有通知 解释器(在这是指 sed 解释器)已经到达了文本行的末尾。这个 i 命令,同样地,命令 a(追加文本, 而不是插入文本)和 c(取代文本)命令都允许多个文本行,只要每个文本行,除了最后一行,以一个 连行符结束。实际上,脚本的第六行是插入文本的末尾,它以一个普通的回车符结尾,而不是一个 连行符,通知解释器 i 命令结束了。

注意:一个连行符由一个反斜杠字符其后紧跟一个回车符组成。它们之间不允许有空白字符。

第七行是我们的查找和替代命令。因为命令之前没有添加地址,所以输入流中的每一行文本 都得服从它的操作。
第八行执行小写字母到大写字母的字符替换操作。注意不同于 tr 命令,这个 sed 中的 y 命令不 支持字符区域(例如,[a-z]),也不支持 POSIX 字符集。再说一次,因为 y 命令之前不带地址, 所以它会操作输入流的每一行。


sed 是一款非常强大的程序,它能够针对文本流完成相当复杂的编辑任务。它最常 用于简单的行任务,而不是长长的脚本。许多用户喜欢使用其它工具,来执行较大的工作。 在这些工具中最著名的是 awk 和 perl。它们不仅仅是工具,像这里介绍的程序,且延伸到 完整的编程语言领域。特别是 perl,经常被用来代替 shell 脚本,来完成许多系统管理任务, 同时它也是一款非常流行网络开发语言。awk 更专用一些。其具体优点是其操作表格数据的能力。 awk 程序通常逐行处理文本文件,这点类似于 sed,awk 使用了一种方案,其与 sed 中地址 之后跟随编辑命令的概念相似。虽然关于 awk 和 perl 的内容都超出了本书所讨论的范围, 但是对于 Linux 命令行用户来说,它们都是非常好的技能。


aspell

交互式的拼写检查器,虽然 aspell 程序大多被其它需要拼写检查能力的 程序使用,但它也可以作为一个独立的命令行工具使用。它能够智能地检查各种类型的文本文件, 包括 HTML 文件,C/C++ 程序,电子邮件和其它种类的专业文本。
aspell check textfile
这里的 textfile 是要检查的文件名。

[me@linuxbox ~]$ cat > foo.txt
The quick brown fox jimped over the laxy dog.
[me@linuxbox ~]$ aspell check foo.txt
The quick brown fox jimped over the laxy dog.
1)jumped                        6)wimped
2)gimped                        7)camped
3)comped                        8)humped
4)limped                        9)impede
5)pimped                        0)umped
i)Ignore                        I)Ignore all
r)Replace                       R)Replace all
a)Add                           l)Add Lower
b)Abort                         x)Exit
?

在显示屏的顶部,我们看到我们的文本中有一个拼写可疑且高亮显示的单词。在中间部分,我们看到 十个拼写建议,序号从 0 到 9,然后是一系列其它可能的操作。最后,在最底部,我们看到一个提示符, 准备接受我们的选择。
如果我们按下 1 按键,aspell 会用单词 “jumped” 代替错误单词,然后移动到下一个拼写错的单词,就是 “laxy”。如果我们选择替代物 “lazy”,aspell 会替换 “laxy” 并且终止。一旦 aspell 结束操作,我们 可以检查我们的文件,会看到拼写错误的单词已经更正了。
除非由命令行选项 --dont-backup 告诉 aspell,否则通过追加扩展名.bak 到文件名中, aspell 会创建一个包含原始文本的备份文件。
我们将还原拼写错误,从而能够重用我们的文件:

[me@linuxbox ~]$ sed -i 's/lazy/laxy/; s/jumped/jimped/' foo.txt

这个 sed 选项-i,告诉 sed 在适当位置编辑文件,意思是不要把编辑结果发送到标准输出中。sed 会把更改应用到文件中, 以此重新编写文件。我们也看到可以把多个 sed 编辑命令放在同一行,编辑命令之间由分号分隔开来。
选项-H(HTML)检查模式
HTML 标志被忽略了,并且只会检查文件中非标志部分的内容。在这种模式下,HTML 标志的 内容被忽略了,不会进行拼写检查。然而,ALT 标志的内容,会被检查。
注意:默认情况下,aspell 会忽略文本中的 URL 和电子邮件地址。通过命令行选项,可以重写此行为。 也有可能指定哪些标志进行检查及跳过。详细内容查看 aspell 命令手册。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值