The Linux Command Line-WILLIAM-文本处理

199 阅读10分钟

所有的类 Unix 操作系统严重依赖文本形式的数据存储。因此有很多工具来操纵文本是有意义的

  • cat —Concatenate files and print on the standard output.
  • sort —Sort lines of text files.
  • uniq —Report or omit repeated lines.
  • cut —Remove sections from each line of files.
  • paste —Merge lines of files.
  • join —Join lines of two files on a common field.
  • comm —Compare two sorted files line by line.
  • diff —Compare files line by line.
  • patch —Apply a diff file to an original.
  • tr —Translate or delete characters.
  • sed —Stream editor for filtering and transforming text.
  • aspell —Interactive spell checker.

Applications of Text

文本有很多用处,它可以用作:

  • Documents:可以使用文本格式编写大型文档,然后使用标记语言描述完成的文档的格式
  • Web Pages:使用 HTML (Hypertext Markup Language) 或者 XML (Extensible Markup Language) 作为标记语言来展示文档格式
  • Email:电子邮件是本质上基于文本的媒体, 甚至非文本附件也将转换为文本表示形式以进行传输。
  • Printer Output:发往打印机的为纯字符,若包含图片将会转换为文本格式的页面描述语言,称为 PostScript
  • Program Source Code:文本处理之所以对开发者重要是因为所有的软件源代码均以文本形式存在

Revisiting Some Old Friends

cat—Concatenate Files and Print on Standard Output

cat 命令有一些有趣的选项。大多数用于更好的可视化文本内容。例如 -A ,用来显示文本中的非打印字符。

首先创建文件,其中在行首输入制表符以及结尾添加若干空格:

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

使用 -A 选项:

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

可以看到在文本中制表符在文本中表现形式为 ^I,意味着 CTRL -I$ 符号说明文本结尾包含空格

cat 还提供两个便利的为 -n,-s 作用分别为显示行号以及消除多余的空行:

[me@linuxbox ~]$ cat > foo.txt
The quick brown fox


jumped over the lazy dog.
[me@linuxbox ~]$ cat -ns foo.txt
	1 The quick brown fox
	2
	3 jumped over the lazy dog.
[me@linuxbox ~]$

上面例子中新创建了foo.txt文件,包含四行,中间两行为空行。使用 -ns 后,多余的空行被移除然后显示行号。

MS-DOS TEXT VS. UNIX TEXT

隐藏的回车从何而来?DOS 和 Windows。Unix 与 DOS 分别定义了文本文件中行结束符号。Unix 结束行 使用 linefeed character(ASCII 10),而 MS-DOS 使用 carriage return (ASCII 13)和 linefeed 序列来结束。

有许多方法将 dos 文件与 Unix 格式文件互转。例如 大多数 Linux 系统中 dos2unix 和 unix2dos 程序 等等。

Software applications and operating system representation of a newline with one or two control characters

|Operating system |Character encoding |Abbreviation| hex| value |dec value |Escape sequence |-|-|-|-|-|-| |Unix and Unix-like systems (Linux, macOS, FreeBSD, AIX, Xenix, etc.), Multics, BeOS, Amiga, RISC OS, and others| ASCII| LF| 0A| 10| \n |Microsoft Windows, DOS (MS-DOS, PC DOS, etc.), Atari TOS, DEC TOPS-10, RT-11, CP/M, MP/M, OS/2, Symbian OS, Palm OS, Amstrad CPC, and most other early non-Unix and non-IBM operating systems| ASCII |CR LF |0D 0A| 13 10| \r\n

sort—Sort Lines of Text Files

sort 程序对 标准输入(standard input)内容进行排序,在命令行可以指定一个或者多个文件,然后向标准输出发送结果:

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

在输入命令后,输入 c , b 和 a ,接着按下 CTRL -D 结束。

因为 sort 可以接受多个文件为参数,可以 合并(merge)多个文件排序汇总到一个文件中:

sort file1.txt file2.txt file3.txt > final_sorted_list.txt

Table 20-1: Common sort Options

OptionLong OptionDescription
-b--ignore-leading-blanksBy default, sorting is performed on theentire line, starting with the first char-acter in the line. This option causessort to ignore leading spaces in linesand calculates sorting based on the firstnon-whitespace character on the line.
-f--ignore-caseMakes sorting case insensitive.
-n--numeric-sortPerforms sorting based on the numericevaluation of a string. Using thisoption allows sorting to be performed onnumeric values rather than alphabeticvalues.
-r--reverseSort in reverse order. Results are indescending rather than ascendingorder.
-k--key=field1[,field2]Sort based on a key field locatedfrom field1 to field2 rather than theentire line.
-m--mergeTreat each argument as the name of apresorted file. Merge multiple files intoa single sorted result without perform-ing any additional sorting.
-o--output=fileSend sorted output to file rather than tostandard output.
-t--field-separator=charDefine the field-separator character. Bydefault, fields are separated by spacesor tabs.

其中 -n 用于数字排序。使用此命令来排序 由 du 命令查询结果来确认最大的用户硬盘空间。通常,du 命令列出的结果通过路径名来排序:

[me@linuxbox ~]$ du -s /usr/share/* | head
252 /usr/share/aclocal
96 /usr/share/acpi-support
8 /usr/share/adduser
196 /usr/share/alacarte
344 /usr/share/alsa
8 /usr/share/alsa-base
12488 /usr/share/anthy
8 /usr/share/apmd
21440 /usr/share/app-install
48 /usr/share/application-registry

上面命令将结果定向到 head 来限制结果为 前十个 结果:

[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

使用 -nr 可以产生 数字倒序 的排序结果。能产生此结果原因为 数值在每行开头出现。如果我们想要根据在行中出现的内容进行排序时候要怎么做?例如将下面的列表:

[me@linuxbox ~]$ ls -l /usr/bin | head
total 152948
-rwxr-xr-x 1 root root 34824 2012-04-04 02:42 [
-rwxr-xr-x 1 root root 101556 2011-11-27 06:08 a2p
-rwxr-xr-x 1 root root 13036 2012-02-27 08:22 aconnect
-rwxr-xr-x 1 root root 10552 2011-08-15 10:34 acpi
-rwxr-xr-x 1 root root 3800 2012-04-14 03:51 acpi_fakekey
-rwxr-xr-x 1 root root 7536 2012-04-19 00:19 acpi_listen
-rwxr-xr-x 1 root root 3576 2012-04-29 07:57 addpart
-rwxr-xr-x 1 root root 20808 2012-01-03 18:02 addr2line
-rwxr-xr-x 1 root root 489704 2012-10-09 17:02 adept_batch

可以使用 sort 来根据文件大小排序:

[me@linuxbox ~]$ ls -l /usr/bin | sort -nr -k 5 | head
-rwxr-xr-x 1 root root 8234216 2012-04-07 17:42 inkscape
-rwxr-xr-x 1 root root 8222692 2012-04-07 17:42 inkview
-rwxr-xr-x 1 root root 3746508 2012-03-07 23:45 gimp-2.4
-rwxr-xr-x 1 root root 3654020 2012-08-26 16:16 quanta
-rwxr-xr-x 1 root root 2928760 2012-09-10 14:31 gdbtui
-rwxr-xr-x 1 root root 2928756 2012-09-10 14:31 gdb
-rwxr-xr-x 1 root root 2602236 2012-10-10 12:56 net
-rwxr-xr-x 1 root root 2304684 2012-10-10 12:56 rpcclient
-rwxr-xr-x 1 root root 2241832 2012-04-04 05:56 aptitude
-rwxr-xr-x 1 root root 2202476 2012-10-10 12:56 smbcacls

上面命令我们制定 -k 5 来使用第五个域作为 key 来排序。

sort 是如何定义 域的呢?举例来说,加入有个简单的文本:

William Shotts

sort 默认将此行视为有两个域,第一个字段包含 William 以及 包含 Shotts 的第二个字段。这意味着空格字符(空格和制表符)用作字段之间的分隔符,并且在执行排序时,分隔符包含在字段中。

再次执行 ls 检查输出中的一行,可以看到有八个字段并且第五个字段为文件大小:

-rwxr-xr-x 1 root root 8234216 2012-04-07 17:42 inkscape

下一个例子,考虑下下面文件包含 三个 Linux 不同版本系统从2006到2008之间的发布。每行包含三个字段:版本名称,版本号以及发行日期,

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
SUSE 10.3 10/04/2007
Ubuntu 6.10 10/26/2006
Fedora 7 05/31/2007
Ubuntu 7.10 10/18/2007
Ubuntu 7.04 04/19/2007
SUSE 10.1 05/11/2006
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

将以上内容添加到 distros.txt 文件中,进行排序:

[me@linuxbox ~]$ sort distros.txt
Fedora 	10 		11/25/2008
Fedora 	5 		03/20/2006
Fedora 	6 		10/24/2006
Fedora 	7 		05/31/2007
Fedora 	8 		11/08/2007
Fedora 	9 		05/13/2008
SUSE 	10.1 	05/11/2006
SUSE 	10.2 	12/07/2006
SUSE 	10.3 	10/04/2007
SUSE 	11.0 	06/19/2008
Ubuntu 	6.06 	06/01/2006
Ubuntu 	6.10 	10/26/2006
Ubuntu 	7.04 	04/19/2007
Ubuntu 	7.10 	10/18/2007
Ubuntu 	8.04 	04/24/2008
Ubuntu 	8.10 	10/30/2008

有时候会用到多个字段排序,可以对上面例子中的第一个字段按照字母排序,第二字段按照数值排序:

[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
Fedora 8 11/08/2007
Fedora 9 05/13/2008
Fedora 10 11/25/2008
SUSE 10.1 05/11/2006
SUSE 10.2 12/07/2006
SUSE 10.3 10/04/2007
SUSE 11.0 06/19/2008
Ubuntu 6.06 06/01/2006
Ubuntu 6.10 10/26/2006
Ubuntu 7.04 04/19/2007
Ubuntu 7.10 10/18/2007
Ubuntu 8.04 04/24/2008
Ubuntu 8.10 10/30/2008

上面命令中 --key=1,1 --key=2n。 --key=1,1 意味着想要限制排序字段为第一个字段,1,1 意味着 “从第一个字段开始到第一个字段结束”。 --key=2n 代表着第二个字段是按照数值的排序字段。上面命令在指定字段数字 2 的后面加上了代表排序类型的字符(与 sort 的全局字符相同),b(ignore leading blanks), n (numeric sort), r (reverse sort) 等等

此列表中包含日期类型的字段,如何对它排序呢?sort 提供一种方式。字段可以指定字段内的偏移量(offsets):

[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
Fedora 	9 		05/13/2008
Ubuntu 	8.04 	04/24/2008
Fedora 	8 		11/08/2007
Ubuntu 	7.10 	10/18/2007
SUSE 	10.3 	10/04/2007
Fedora 	7 		05/31/2007
Ubuntu 	7.04 	04/19/2007
SUSE 	10.2 	12/07/2006
Ubuntu 	6.10 	10/26/2006
Fedora 	6 		10/24/2006
Ubuntu 	6.06 	06/01/2006
SUSE 	10.1 	05/11/2006
Fedora 	5 		03/20/2006

指定 -k 3.7nbr 意味着 按照第三个字段 第七个字符开始进行排序。 同样,指定-k 3.1和-k 3.4以隔离日期的月份和日期部分。

有一些文件不适用空格或者制表符来作为分割符,例如 /etc/passwd 文件:

[me@linuxbox ~]$ head /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh

此文件中每行以:作为分割符,如何排序呢?可以使用 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—Report or Omit Repeated Lines

uniq 十分轻量。用来去除文件中(包含标准输入)任何重复的行并发送到标准输出。

创建文件:

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

执行 uniq:

[me@linuxbox ~]$ uniq foo.txt
a
b
c
a
b
c

和原来结果是一样的,如果想要移除重复的行必须要先进行排序:

[me@linuxbox ~]$ sort foo.txt | uniq
a
b
c

这是因为 uniq 只在临近的行移除重复的行。

Table 20-2: Common uniq Options

OptionDescription
-cOutput a list of duplicate lines preceded by the number of times the line occurs.
-dOutput only repeated lines, rather than unique lines.
-f nIgnore n leading fields in each line. Fields are separated by whitespace as they are in sort ; however, unlike sort , uniq has no option for setting an alternative field separator.
-iIgnore case during the line comparisons.
-s nSkip (ignore) the leading n characters of each line.
-uOutput only unique lines. This is the default.

命令后面加上 -c 可以查看重复次数:

[me@linuxbox ~]$ sort foo.txt | uniq -c
	2 a
	2 b
	2 c

Slicing and Dicing

cut—Remove Sections from Each Line of Files

cut 用来从每行提取文本域并输出到标准输出。可以接受多个文件参数或者标准输入。

使用下表 选项来指定提取的文本:

Table 20-3: cut Selection Options

OptionDescription
-c char_listExtract the portion of the line defined by char_list .The list may consist of one or more comma-separated numerical ranges.
-f field_listExtract one or more fields from the line as defined by field_list . The list may contain one or more fields or field ranges separated by commas.
-d delim_charWhen -f is specified, use delim_char as the field delimiting character. By default, fields must be separated by a single tab character.
--complementExtract the entire line of text, except for those portions specified by -c and/or -f .

使用 cat 查看文件:

[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$

上面文件内容格式看上去很好,每个域中没有包含空格并以 制表符 作为分割符。因为使用制表符作为分割符,可以使用 -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

可以使用 -c 接着提取每行中年份:

[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

可以使用 -d 指定冒号为分割符:

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

paste—Merge Lines of Files

相比于 cut 提取域,paste 命令 用来 增加若干个列。与 cut, paste 类似都可以接受多个文件以及标准输入。

创建文件并提取域到 distros-versions.txt 和 distros-dates.txt 文件中:

[me@linuxbox ~]$ sort -k 3.7nbr -k 3.1nbr -k 3.4nbr distros.txt > distros-by-date.txt
[me@linuxbox ~]$ cut -f 1,2 distros-by-date.txt > distros-versions.txt
[me@linuxbox ~]$ head distros-versions.txt
Fedora 	10
Ubuntu 	8.10
SUSE 	11.0
Fedora 	9
Ubuntu 	8.04
Fedora 	8
Ubuntu 	7.10
SUSE 	10.3
Fedora 	7
Ubuntu 	7.04
[me@linuxbox ~]$ cut -f 3 distros-by-date.txt > distros-dates.txt
[me@linuxbox ~]$ head distros-dates.txt
11/25/2008
10/30/2008
06/19/2008
05/13/2008
04/24/2008
11/08/2007
10/18/2007
10/04/2007
05/31/2007
04/19/2007

使用 paste 来使得日期列 放在 版本和名称前面:

[me@linuxbox ~]$ paste distros-dates.txt distros-versions.txt
11/25/2008 Fedora 10
10/30/2008 Ubuntu 8.10
06/19/2008 SUSE 11.0
05/13/2008 Fedora 9
04/24/2008 Ubuntu 8.04
11/08/2007 Fedora 8
10/18/2007 Ubuntu 7.10
10/04/2007 SUSE 10.3
05/31/2007 Fedora 7
04/19/2007 Ubuntu 7.04
12/07/2006 SUSE 10.2
10/26/2006 Ubuntu 6.10
10/24/2006 Fedora 6
06/01/2006 Ubuntu 6.06
05/11/2006 SUSE 10.1
03/20/2006 Fedora 5

join—Join Lines of Two Files on a Common Field

某种程度上, join 类似于 paste 都是增加增加列到文件中,但是 join 以其独特的方式实现这一点。 在关系数据库中通常有 join 这个操作,来自多个表的具有共享键字段的记录被合并以形成期望的结果。此处 join 命令执行相同的操作。

创建一个包含时间与版本名称的文件:

[me@linuxbox ~]$ cut -f 1,1 distros-by-date.txt > distros-names.txt
[me@linuxbox ~]$ paste distros-dates.txt distros-names.txt > distros-key-names.txt
[me@linuxbox ~]$ head distros-key-names.txt
11/25/2008 Fedora
10/30/2008 Ubuntu
06/19/2008 SUSE
05/13/2008 Fedora
04/24/2008 Ubuntu
11/08/2007 Fedora
10/18/2007 Ubuntu
10/04/2007 SUSE
05/31/2007 Fedora
04/19/2007 Ubuntu

创建一个包含时间与版本号的文件:

[me@linuxbox ~]$ cut -f 2,2 distros-by-date.txt > distros-vernums.txt
[me@linuxbox ~]$ paste distros-dates.txt distros-vernums.txt > distros-key-vernums.txt
[me@linuxbox ~]$ head distros-key-vernums.txt
11/25/2008 10
10/30/2008 8.10
06/19/2008 11.0
05/13/2008 9
04/24/2008 8.04
11/08/2007 8
10/18/2007 7.10
10/04/2007 10.3
05/31/2007 7
04/19/2007 7.04

两个文件中都有共享字段 发行日期。执行 join 时候必须确保文件是按照共享字段排序的文件:

[me@linuxbox ~]$ join distros-key-names.txt distros-key-vernums.txt | head
11/25/2008 Fedora 10
10/30/2008 Ubuntu 8.10
06/19/2008 SUSE 11.0
05/13/2008 Fedora 9
04/24/2008 Ubuntu 8.04
11/08/2007 Fedora 8
10/18/2007 Ubuntu 7.10
10/04/2007 SUSE 10.3
05/31/2007 Fedora 7
04/19/2007 Ubuntu 7.04

默认情况下,join 使用空格作为分割符,并在输出时候使用单个空格分割域。此配置可以通过命令修改。

比较文本

comm—Compare Two Sorted Files Line by Line

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 命令的结果中包含三列。第一列为 第一个 文件(file1.txt)中独有的行;第二列为 第二个 文件(file2.txt)中独有的行;而第三行则是共有的行。comm 还支持 -n (n为1,2 或者 3)来指定结果中不显示哪列。例如,只显示 结果中 第三列:

[me@linuxbox ~]$ comm -12 file1.txt file2.txt
b
c
d

diff—Compare Files Line by Line

与 comm 类似, diff 用来检测文件间不同。但是 diff 更为复杂,支持很多输出格式 以及可以 一次处理大量的文本文件集合。因为diff具有递归检查源代码目录(通常称为源树)的能力,通常被软件开发人员用来检查不同版本程序源代码之间的更改。

执行 diff ,可以看到一个简洁的结果:

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

在默认格式下,每组更改之前都带有范围操作范围形式的更改命令(请参见表20-4),以描述将第一个文件转换为第二个文件所需的更改的位置和类型。

Table 20-4: diff Change Commands

|Change| |-|-| |r1ar2|Append the lines at the position r2 in the second file to the position r1 in the first file. |r1cr2|Change (replace) the lines at position r1 with the lines at the position r2 in the second file. |r1dr2|Delete the lines in the first file at position r1 , which would have appeared at range r2 in the second file

此格式是默认的(主要是为了POSIX合规性以及对diff的传统Unix版本的向后兼容性),还可以使用 context format (-c)和 unified format (-u)两种格式:

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

*** 1,4 **** 指 在首个文件中第一行到第四行; --- 1,4 ---- 指 在第二个文件中第一行到第四行,用来表示不同的符号含义如下:

Table 20-5: diff Context-Format Change Indicators

IndicatorMeaning
(none)A line shown for context. It does not indicate a difference between the two files.
-A line deleted. This line will appear in the first file but not in the second file.
+A line added. This line will appear in the second file but not in the first file.
!A line changed. The two versions of the line will be displayed,each in its respective section of the change group.

unified format 格式比 context format 更加简洁:

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

符号的含义如下:

Table 20-6: diff Unified-Format Change Indicators

CharacterMeaning
(none)This line is shared by both files.
-This line was removed from the first file.
+This line was added to the first file.

patch—Apply a diff to an Original

patch 程序用于将更改作用于文本文件。他接受 diff 的输出通常用于将老版本文件更新为最新版本。使用 diff / patch 有两个优势:

  • diff 文件相比于整个源文件很小

  • diff 文件清晰的显示所做的改变,可以使评审者更快的评估

要准备要与patch一起使用的diff文件,GNU文档建议如下使用diff:

diff -Naur old_file new_file > diff_file

上面的 old_file ,new_file 可以为单个文件或者包含文件的文件夹, -r 支持文件夹递归。

diff 文件创建后,可以执行 patch 将文件更新为新版本文件:

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

上面命令中并没有指定 目标文件,因为 diff 文件在头部已经包含了文件名。

Editing on the Fly

文本编辑器具有很强的交互性,意味着想要改变文本首先要手动移动光标并输入修改。然而,这里存在一种非交互式的方法可以使用一行命令来对多个文件执行一系列修改。

tr—Transliterate or Delete Characters

tr 用来直译(transliterate)字符。可以理解为 基于字符的搜索和替换操作。字译(Transliteration)为从字母到另一种形式改变字符的过程。例如,将小写字符转化成大写字符为字译。

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

上面命令中,tr 对标准输入操作并将其输出到标准输出。 tr 接受两个参数:想要转化的字符集合 以及 对应的转化字符。字符集可以表现为以下三种方式:

  • 一个枚举列表;例如,ABCDEFGHIJKLMNOPQRSTUVWXYZ

  • 字符范围;如,A-Z 。此方法可能与其他命令有相同的问题(因为 locale 对照的顺序)

  • POSIX 字符类;如,[:upper:]

大多数情况下,两个字符集合应该是相同长度的,但是,也有第一个字符集长于第二个字符集的情况发生:

[me@linuxbox ~]$ echo "lowercase letters" | tr [:lower:] A
AAAAAAAAA AAAAAAA

tr 也允许简单的从输入中删除字符。例如,上面的 MS-DOS 文件转化为 Unix 风格的文件,只需要将每行中的carriage return (ASCII 13) 删除:

tr -d '\r' <dos_file> unix_file

可以使用以下命令来查看支持的 字符类 和序列:

[me@linuxbox ~]$ trhelp

sed—Stream Editor for Filtering and Transforming Text

sed(stream editor)可以从指定文件集或者标准输入的文本流上编辑文本。

大体上,sed 接受 一行单独的命令或者一个包含多条命令的脚本 ,sed 执行在文本流中执行这些命令。例如一个简单的例子:

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

上面命令中 的 s/front/back/ 指令将 back 作为输出。可以意识到此指令类似于 vi 中的替换。

sed 的指令以一个字符作为开头。上面命令中为 s 接着跟着以斜杠分割的 搜索字符串和替换字符串。分割符的选择是任意的,只是斜杠更为常用,sed 将 接受在指令符后面的字符 作为分割符:

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

sed 中的大多数命令都可以用地址开头,地址可以指定编辑哪行,如果省略将会编辑输入流中的每一行。最简单的地址形式为行号:

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

上面是指定了行号 1,也可以指定其他行号:

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

可以看到指令并未在第一行生效。

Table 20-7: sed Address Notation

|Address|Description ||| |n|A line number where n is a positive integer |$ |The last line |/regexp/|Lines matching a POSIX basic regular expression. Note that theregular expression is delimited by slash characters. Optionally,the regular expression may be delimited by an alternate char-acter, by specifying the expression with \cregexpc , where c isthe alternate character. |addr1,addr2|A range of lines from addr1 to addr2 , inclusive. Addresses maybe any of the single address forms above. |firststep |Match the line represented by the number first and then eachsubsequent line at step intervals. For example, 12 refers toeach odd-numbered line, and 5~5 refers to the fifth line andevery fifth line thereafter. |addr1,+n|Match addr1 and the following n lines. |addr! |Match all lines except addr , which may be any of the forms above.

指定不同形式的地址:

[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

也可以使用正则表达式:

[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/ ,将匹配项筛选出来。

也可以通过 ! 来消除匹配项:

[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

Table 20-8: sed Basic Editing Commands

CommandDescription
=Output current line number.
aAppend text after the current line.
dDelete the current line.
iInsert text in front of the current line.
pPrint the current line. By default, sed prints every lineand edits only lines that match a specified addresswithin the file. The default behavior can be over-ridden by specifying the -n option.
qExit sed without processing any more lines. If the -noption is not specified, output the current line.
QExit sed without processing any more lines.
s/regexp/replacement/Substitute the contents of replacement whereverregexp is found. replacement may include the specialcharacter & , which is equivalent to the text matchedby regexp . In addition, replacement may include thesequences \1 through \9 , which are the contents ofthe corresponding subexpressions in regexp . Formore about this, see the following discussion onback references. After the trailing slash followingreplacement , an optional flag may be specified tomodify the s command’s behavior.
y/set1/set2Perform transliteration by converting characters fromset1 to the corresponding characters in set2 . Notethat unlike tr , sed requires that both sets be of thesame length.

sed 可以一个命令 把日期格式 从 MM/DD/YYYY 改为 YYYY-MM-DD:

[me@linuxbox ~]$ sed 's/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/' distros.txt
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
Ubuntu 	6.06 	2006-06-01
Ubuntu 	8.10 	2008-10-30
Fedora 	5 		2006-03-20

此命令为下面的结构:

sed 's/regexp/replacement/' distros.txt

接下来构建正则表达式来匹配原来日期格式:

[0-9]{2}/[0-9]{2}/[0-9]{4}$

然后执行替换,要用到在一些使用 BRE 应用中出现的正则表达式中的新特性。名为 back references,即如果 \n (n为1-9)出现在 替换中,该序列将引用前面的正则表达式中的相应子表达式。为了创建子表达式,只需要将其用括号包裹起来:

([0-9]{2})/([0-9]{2})/([0-9]{4})$

现在有三个子表达式,第一个包含月份,第二个包含日期,第三个包含年。现在可以构造替换(replacement)为:

\3-\1-\2

因此,完整的命令为:

sed 's/([0-9]{2})/([0-9]{2})/([0-9]{4})$/\3-\1-\2/' distros.txt

但是,上面命令还存在一些问题。第一个为表达式中的斜杠会使 sed 解析 s 指令时候困惑;第二个为因为 sed 只接受 基础正则,所以许多 元字符将会被当作普通字符。因此可以使用反斜杠来使得字符转义:

sed 's/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/' distros.txt

s 的指令还可以指定选项,例如:

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

可以通过加上 g 选项来改变所有的b:

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

现在我们只通过命令行来指定单条命令,也可以使用 -f 指定脚本文件:

首先,创建脚本:

# 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/

保存为 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
UBUNTU 6.06 2006-06-01
UBUNTU 8.10 2008-10-30
FEDORA 5 2006-03-20

查看脚本:

[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 系统上语言一样以 # 开头。

第二行为空行。

第三行到第六行都为插入文本到第一行。i 指令后跟反斜杠和carriage return 序列 来产生 转义的 carriage return(或者称为 line-continuation character)。此序列可以用于许多场景包括 shell 脚本,使得 carriage return 嵌入到文本流中,而无需向解释器(在本例中为sed)发信号,通知已经到达行尾。i命令以及命令a(附加文本)和c(替代文本)允许多行文本,条件是除最后一行外,每行均以 line-continuation 字符 结尾。脚本的第六行实际上是插入文本的结尾,并以carriage return符而不是line-continuation符结束,表示i命令的结尾。

第七行为搜索替换。

第八行执行 transliteration,将小写转为大写。

aspell—Interactive Spell Checker

aspell 为交互式拼写检查。该程序是名为 ispell 的早期程序的继承者。

可以使用下列命令来执行:

aspell check textfile

为了演示,首先创建文件:

[me@linuxbox ~]$ cat > foo.txt
The quick brown fox jimped over the laxy dog.

使用 aspell :

[me@linuxbox ~]$ aspell check foo.txt

因为 aspell 为交互式,将会看到类似于下面结果:

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 替换 jimped 单词为 jumped,然后移动到下一个目标(此例子中为 laxy),如果接着选择替换,aspell 将会执行替换并关闭:

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

除非在命令中加入 --dont-backup 指定不备份,aspell 将会创建 以 .bak 为扩展名的备份文件。

aspell 可以对 html 文件进行检查:

[me@linuxbox ~]$ aspell -H check foo.txt

可以看到:

<html>
<head>
<title>|Mispelled| HTML file</title>
</head>
<body>
<p>The quick brown fox jimped over the laxy dog.</p>
</body>
</html>
#####################################################
1) Mi spelled 		6) Misapplied
2) Mi-spelled 		7) Miscalled
3) Misspelled 		8) Respelled
4) Dispelled 		9) Misspell
5) Spelled 			0) Misled
i) Ignore 			I) Ignore all
r) Replace 			R) Replace all
a) Add 				l) Add Lower
b) Abort 			x) Exit

【注】默认情况下 ,aspell 忽视 URLs 和 email 地址,可以通过选项来取消。