使用正则表达式最大的问题在于有不止一种类型的正则表达式。Linux中的不同应用程序可能会用不同类型的正则表达式。
正则表达式是通过正则表达式引擎实现的。正则表达式引擎是一套底层软件,负责解释正则表达式模式并使用这些模式进行文本匹配。
在Linux中,有两种流行的正则表达式引擎:
1、POSIX基础正则表达式引擎(basic regular expression,BRE)
2、POSIX扩展正则表达式引擎(extended regular expression,ERE)
大多数Linux工具都至少符合POSIX BRE引擎规范,能够识别该规范定义的所有模式符号。如gawk程序用ERE引擎来处理它的正则表达式模式。不过,有些工具(比如sed编辑器)只符合了BRE引擎规范的子集。
最基本的 BRE 模式是匹配数据流中的文本字符。BRE 引擎通常出现在依赖正则表达式进行文本过滤的编程语言中。它为常见模式提供了高级模式符号和特殊符号,比如匹配数字、单词以及按字母排序的字符。
有些字符在正则表达式中有特别的含义,不能在文本模式中单独使用这些字符。
正则表达式识别的特殊字符包括:. * [ ] ^ $ { } \ + ? | ( )
如果要用某个特殊字符作为文本字符,就必须转义。需要在它前面加一个反斜线(\)来告诉正则表达式引擎应该将接下来的字符当作普通的文本字符。
# 匹配以Book开头
$ echo "Books are great" | sed -n '/^Book/p'
Books are great
# 如果将^放到模式开头之外的其他位置,那么它就跟普通字符一样,不再是特殊字符了
$ echo "This ^ is a test" | sed -n '/s ^/p'
This ^ is a test
# 匹配以book结尾
$ echo "This is a good book" | sed -n '/book$/p'
This is a good book
# 精确匹配
$ cat data4
this is a test of using both anchors
I said this is a test
this is a test
I'm sure this is a test.
$ sed -n '/^this is a test$/p' data4
this is a test
# 过滤出数据流中的空白行
$ cat data5
This is one test line.
This is another test line.
$ sed '/^$/d' data5
This is one test line.
This is another test line.
匹配任意一个字符。
字符组中任意一个匹配到即可。
$ sed -n '/[ch]at/p' data6
The cat is sleeping.
That is a very nice hat.
排除匹配,只要在字符组的开头加个脱(^)字符。
$ sed -n '/[^ch]at/p' data6
This test is at line four.
$ sed -n '/^[0-9][0-9][0-9][0-9][0-9]$/p' data8
60633
46201
45902
# 也适用于字母
$ sed -n '/[c-h]at/p' data6
The cat is sleeping.
That is a very nice hat.
# 可以在单个字符组指定多个不连续的区间
# 该字符组允许区间a~c、h~m中的字母出现在at文本前,但不允许出现d~g的字母
$ sed -n '/[a-ch-m]at/p' data6
The cat is sleeping.
That is a very nice hat.
除了定义自己的字符组外,BRE还包含了一些特殊的字符组,可用来匹配特定类型的字符。
组 | 描述 |
---|---|
[[:alpha:]] | 匹配任意字母字符,不管是大写还是小写 |
[[:alnum:]] | 匹配任意字母数字字符09、AZ或a~z |
[[:blank:]] | 匹配空格或制表符 |
[[:digit:]] | 匹配0~9之间的数字 |
[[:lower:]] | 匹配小写字母字符a~z |
[[:print:]] | 匹配任意可打印字符 |
[[:punct:]] | 匹配标点符号 |
[[:space:]] | 匹配任意空白字符:空格、制表符、NL、FF、VT和CR |
[[:upper:]] | 匹配任意大写字母字符A~Z |
$ echo "abc" | sed -n '/[[:digit:]]/p'
$
$ echo "abc" | sed -n '/[[:alpha:]]/p'
abc
$ echo "abc123" | sed -n '/[[:digit:]]/p'
abc123
$ echo "This is, a test" | sed -n '/[[:punct:]]/p'
This is, a test
$ echo "This is a test" | sed -n '/[[:punct:]]/p'
ERE 模式包括了一些可供Linux应用和工具使用的额外符号。gawk程序能够识别ERE模式,但sed编辑器不能。
前面的字符出现0次或1次
# 将问号和字符组一起使用
# 字符组中的字符出现了0次或1次,模式匹配就成立。但如果两个字符都出现了,或者其中一个字符出现了2次,模式匹配就不成立
$ echo "bt" | gawk '/b[ae]?t/{print $0}'
bt
$ echo "bat" | gawk '/b[ae]?t/{print $0}'
bat
$ echo "bot" | gawk '/b[ae]?t/{print $0}'
$
$ echo "bet" | gawk '/b[ae]?t/{print $0}'
bet
$ echo "baet" | gawk '/b[ae]?t/{print $0}'
$
$ echo "beat" | gawk '/b[ae]?t/{print $0}'
$
$ echo "beet" | gawk '/b[ae]?t/{print $0}'
前面的字符可以出现1 次或多次,但必须至少出现1次。如果该字符没有出现,那么模式就不会匹配。
# 使用于字符组
$ echo "bt" | gawk '/b[ae]+t/{print $0}'
$
$ echo "bat" | gawk '/b[ae]+t/{print $0}'
bat
$ echo "bet" | gawk '/b[ae]+t/{print $0}'
bet
$ echo "beat" | gawk '/b[ae]+t/{print $0}'
beat
$ echo "beet" | gawk '/b[ae]+t/{print $0}'
beet
$ echo "beeat" | gawk '/b[ae]+t/{print $0}'
beeat
ERE中的花括号允许你为可重复的正则表达式指定一个上限。这通常称为间隔(interval)。 可以用两种格式来指定区间。
m:正则表达式准确出现m次。
m, n:正则表达式至少出现m次,至多n次。
默认情况下,gawk程序不会识别正则表达式间隔。必须指定gawk程序的 --re-interval 命令行选项才能识别正则表达式间隔。
$ echo "beet" | gawk --re-interval '/be{1,2}t/{print $0}'
beet
$ echo "bt" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
$
$ echo "bat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
bat
$ echo "bet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
bet
$ echo "beat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
beat
$ echo "beet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
beet
# b-t之间超过2(n)位也不匹配
$ echo "beeat" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
$
$ echo "baeet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
$
$ echo "baeaet" | gawk --re-interval '/b[ae]{1,2}t/{print $0}'
$
用逻辑OR方式指定正则表达式引擎要用的两个或多个模 式。如果任何一个模式匹配了数据流文本,文本就通过测试。如果没有模式匹配,则数据流文本匹配失败。
expr1|expr2|...
正则表达式模式也可以用圆括号进行分组。当正则表达式模式分组时,该组会被视为一个标准字符。可以像对普通字符一样给该组使用特殊字符。
$ echo "cat" | gawk '/(c|b)a(b|t)/{print $0}'
cat
$ echo "cab" | gawk '/(c|b)a(b|t)/{print $0}'
cab
$ echo "bat" | gawk '/(c|b)a(b|t)/{print $0}'
bat
$ echo "bab" | gawk '/(c|b)a(b|t)/{print $0}'
bab
$ echo "tab" | gawk '/(c|b)a(b|t)/{print $0}'
$
$ echo "tac" | gawk '/(c|b)a(b|t)/{print $0}'
sed 编辑器被称作流编辑器。sed编辑器可以根据命令来处理数据流中的数据,这些命令要么从命令行中输入,要么存储 在一个命令文本文件中。sed编辑器会执行下列操作:
1、一次从输入中读取一行数据;
2、根据所提供的编辑器命令匹配数据;
3、 按照命令修改流中的数据;
4、将新的数据输出到STDOUT;
在流编辑器将所有命令与一行数据匹配完毕后,它会读取下一行数据并重复这个过程。在流编辑器处理完流中的所有数据行后,它就会终止。
由于命令是按顺序逐行给出的,sed编辑器只需对数据流进行一遍处理就可以完成编辑操作。这使得sed编辑器要比交互式编辑器快得多,你可以快速完成对数据的自动修改。
sed options script file
sed命令选项:
选项 | 描述 |
---|---|
-e script | 在处理输入时,将script中指定的命令添加到已有的命令中 |
-f file | 在处理输入时,将file中指定的命令添加到已有的命令中 |
-n | 不产生命令输出,使用print命令来完成输出 |
script参数指定了应用于流数据上的单个命令。如果需要用多个命令,要么使用-e选项在命令行中指定,要么使用-f选项在单独的文件中指定。有大量的命令可用来处理数据。
# 批量修改文件内容
$ sed 's/dog/cat/' test.txt
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
# 要在sed命令行上执行多个命令时,需要使用 -e 选项;命令之间必须用分号隔开
$ sed -e 's/brown/green/; s/dog/cat/' test.txt
# 如果不想用分号(注意单引号开始结束区
$ sed -e '
> s/brown/green/
> s/dog/cat/' test.txt
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.
# sed编辑器并不会修改文本文件的数据。它只会将修改后的数据发送到STDOUT
$ cat test.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
如果有大量要处理的sed命令,那么将它们放进一个单独的文件中通常会更方便一些。可以在sed命令中用 -f 选项来指定文件。
$ cat script.sed
s/brown/green/
s/fox/elephant/
s/dog/cat/
$ sed -f script.sed test.txt
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
替换命令 s 在替换多行中的文本时能正常工作,但默认情况下它只替换每行中出现的第一处。 要让替换命令能够替换一行中不同地方出现的文本必须使用替换标记。替换标记会在替换命令字符串之后设置:
s/pattern/replacement/flags
# 可以使用其他字符来作为替换命令中的字符串分隔符
s!pattern!replacement!flags
s#pattern#replacement#flags
有4种可用的替换标记:
替换标记 | 描述 |
---|---|
数字 | 替换第几处模式匹配的地方(按行替换,每行的第n处) |
g | 替换所有匹配的文本 |
p | 打印匹配修改后的行(和 -n 选项结合) |
w file | 将替换的结果写到文件 |
$ cat file.txt
This is a test of the test script.
This is the second test of the test script.
This is a different line.
$ sed 's/test/ABC/2' file.txt # 匹配每行的第2处
This is a test of the ABC script.
This is the second test of the ABC script.
This is a different line.
$ sed 's/test/ABC/g' file.txt # 匹配所有
This is a ABC of the ABC script.
This is the second ABC of the ABC script.
This is a different line.
$ sed -n 's/different/ABC/p' file.txt # 匹配修改后的行
This is a ABC line.
$ sed 's/different/ABC/w test.txt' file.txt # 将输出保存到文件中
-n 选项将禁止sed编辑器输出。但p替换标记会输出修改过的行。将二者配合使用的效果就是只输出被替换命令修改过的行。
默认情况下,在sed编辑器中使用的命令会作用于文本数据的所有行。如果只想将命令作用于特定行或某些行,则必须用行寻址(line addressing)
当使用数字方式的行寻址时,可以用行在文本流中的行位置来引用。sed编辑器会将文本流中的第一行编号为1,然后继续按顺序为接下来的行分配行号。
在命令中指定的地址可以是单个行号,或是用起始行号、逗号以及结尾行号指定的一定区间范围内的行。
# 指定第二行
$ sed '2s/dog/cat/' data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
# 指定2、3行
$ sed '2,3s/dog/cat/' data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy dog
# 指定第二行到最后一行的所有行,$:结尾行号
$ sed '2,$s/dog/cat/' data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy cat
sed编辑器允许指定文本模式来过滤出命令要作用的行。格式:/pattern/command
必须用正斜线将要指定的pattern封起来,sed编辑器会将该命令作用到包含指定文本模式的行上。pattern支持正则表达式。
$ grep Samantha /etc/passwd
Samantha:x:502:502::/home/Samantha:/bin/bash
# 只替换包含Samantha关键字的行
$ sed '/Samantha/s/bash/csh/' /etc/passwd
[...]
Samantha:x:502:502::/home/Samantha:/bin/csh
如果需要在单行上执行多条命令,可以用花括号将多条命令组合在一起。
# 替换第二行的fox、dog
$ sed '2{
> s/fox/elephant/
> s/dog/cat/
> }' data1.txt
The quick brown fox jumps over the lazy dog.
The quick brown elephant jumps over the lazy cat.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
删除命令 d,删除匹配指定寻址模式的所有行。没有加入寻址模式,即删除所有文本行。
sed编辑器不会修改原始文件。删除的行只是从sed编辑器的输出中消失了。
$ cat data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
# 删除第3行
$ sed '3d' data6.txt
This is line number 1.
This is line number 2.
This is line number 4.
# 特定行区间指定
$ sed '2,3d' data6.txt
This is line number 1.
This is line number 4.
sed编辑器的模式匹配特性也适用于删除命令。
# 删除含有1的行
$ sed '/1/d' data6.txt
This is line number 2.
This is line number 3.
This is line number 4.
也可以使用两个文本模式来删除某个区间内的行,sed编辑器会删除两个指定行之间的所有行(包括指定的行)。
$ sed '/1/,/3/d' data6.txt
This is line number 4.
注意,如果再次匹配到开始区间而没有匹配到结束区间,会删除开始区间之后所有内容。
$ cat data7.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
This is line number 1 again.
This is text you want to keep.
This is the last line in the file.
# 第二个出现数字“1”的行再次触发了删除命令,因为没有找到停止模式,所以就将数据流中的剩余行全部删除了
$ sed '/1/,/3/d' data7.txt
This is line number 4.
sed编辑器允许向数据流插入(i)和附加(a)文本行。
命令 | 描述 |
---|---|
i | 增加到指定行前 |
a | 增加到指定行后 |
# 插入命令,文本会出现在数据流文本的前面
$ echo "BBB" | sed 'i\AAA'
AAA
BBB
# 附加命令,文本会出现在数据流文本的后面
$ echo "BBB" | sed 'a\AAA'
BBB
AAA
要向数据流行内部插入或附加数据,可以匹配一个数字行号或文本模式,但不能用地址区间。
# 将一个新行插入到数据流第三行前
$ sed '3i\This is an inserted line.' data6.txt
This is line number 1.
This is line number 2.
This is an inserted line.
This is line number 3.
# 将一个新行附加到数据流中第三行后
$ sed '3a\This is an appended line.' data6.txt
# 新行附加到数据流的末尾
$ sed '$a\This is a new line of text.' data6.txt
# 要插入多行文本,就必须对新文本中的每一行使用反斜线,直到最后一行
$ sed '1i\
> This is one line of new text.\
> This is another line of new text.' data6.txt
This is one line of new text.
This is another line of new text.
This is line number 1.
This is line number 2.
This is line number 3.
修改命令(c)允许修改数据流中整行文本的内容。它跟插入和附加命令的工作机制一样,必须在sed命令中单独指定新行。
# 修改第三行内容
$ sed '3c\
> This is a changed line of text.' data6.txt
This is line number 1.
This is line number 2.
This is a changed line of text.
This is line number 4.
# 也可以用文本模式来寻址,包含“number 3”的行
$ sed '/number 3/c\
> This is a changed line of text.' data6.txt
This is line number 1.
This is line number 2.
This is a changed line of text.
This is line number 4.
# 修改命令中使用地址区间(会把整个区间当成一体修改)
$ sed '2,3c\
> This is a new line of text.' data6.txt
This is line number 1.
This is a new line of text.
This is line number 4.
转换命令(y)是唯一可以处理单个字符的sed编辑器命令。转换命令会对转换前后的值进行一对一的映射,它们的长度必须要相同。
# 一一对应:1-7 2-8 3-9
$ sed 'y/123/789/' data8.txt
This is line number 7.
This is line number 8.
This is line number 9.
This is line number 4.
This is line number 7 again.
This is yet another line.
This is the last line in the file.
转换命令是一个全局命令,它会文本行中找到的所有指定字符自动进行转换,无法限定只转换在特定地方出现的字符。
$ echo "This 1 is a test of 1 try." | sed 'y/123/456/'
This 4 is a test of 4 try.
p:打印文本行;
=:打印行号;
l:(小写L)列出行;
$ cat file.txt
This is line AAA.
This is line BBB.
This is line CCC.
This is line DDD.
# 打印包含‘CCC’的行
$ sed -n '/CCC/p' file.txt
This is line CCC.
$ sed -n '2,3p' file.txt
This is line BBB.
This is line CCC.
-n 选项,可以禁止输出其他行,只打印包含匹配文本模式的行。
在修改之前查看行
$ sed -n '/CCC/{
> p
> s/line/test/p
> }' file.txt
This is line CCC.
This is test CCC.
sed编辑器命令会查找包含“CCC”的行,然后执行两条命令。首先,脚本用p命令来打印出原始行;然后它用s命令替换文本,并用p标记打印出替换结果。输出同时显示了原来的行文本和新的行文本。
行号由数据流中的换行符决定。每次数据流中出现一个换行符,sed编辑器会认为一行文本结束了。
$ cat data1.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
$ sed '=' data1.txt
1
The quick brown fox jumps over the lazy dog.
2
The quick brown fox jumps over the lazy dog.
3
The quick brown fox jumps over the lazy dog.
4
The quick brown fox jumps over the lazy dog.
# 查找特定文本模式
$ sed -n '/CCC/{
> =
> p
> }' file.txt
3
This is line CCC.
列出命令(l)可以打印数据流中的文本和不可打印的ASCII字符。任何不可打印字符要么在其八进制值前加一个反斜线,要么使用标准C风格的命名法(用于常见的不可打印字符),比如\t,来代表制表符。
$ cat data9.txt
This line contains tabs.
# 制表符的位置使用\t来显示。行尾的美元符$表示换行符
$ sed -n 'l' data9.txt
This\tline\tcontains\ttabs.$
w 命令用来向文件写入行:[address]w filename
# 把data6的1,2行写入test
$ sed '1,2w test.txt' data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
$ cat test.txt
This is line number 1.
This is line number 2.
将包含文本模式的数据行写入目标文件。
$ cat data11.txt
Blum, R Browncoat
McGuiness, A Alliance
Bresnahan, C Browncoat
Harken, C Alliance
# 将包含 Browncoat 的行写入目标文件
$ sed -n '/Browncoat/w Browncoats.txt' data11.txt
$ cat Browncoats.txt
Blum, R Browncoat
Bresnahan, C Browncoat
r 可以将一个独立文件中的数据插入到数据流中:[address]r filename
filename参数指定了数据文件的绝对路径或相对路径。在读取命令中使用地址区间,只能指定单独一个行号或文本模式地址。sed编辑器会将文件中的文本插入到指定地址后。
$ cat data12.txt
This is an added line.
This is the second added line.
# 把data12的内容插入到data6的第三行后
$ sed '3r data12.txt' data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is an added line.
This is the second added line.
This is line number 4.
# 使用文本模式地址时
$ sed '/number 2/r data12.txt' data6.txt
This is line number 1.
This is line number 2.
This is an added line.
This is the second added line.
This is line number 3.
This is line number 4.
# 插入数据流末尾
$ sed '$r data12.txt' data6.txt
读取命令配合删除命令,利用另一个文件中的数据来替换文件中的占位文本。
$ cat notice.std
Would the following people:
LIST
please report to the ship's captain.
$ cat data11.txt
Blum, R Browncoat
McGuiness, A Alliance
$ sed '/LIST/{
> r data11.txt # 读取data11.txt内容
> d # 删除LIST
> }' notice.std
Would the following people:
Blum, R Browncoat
McGuiness, A Alliance
please report to the ship's captain.
在使用sed编辑器的基础命令时,存在一个局限。所有的sed编辑器命令都是针对单行数据执行操作的。在sed编辑器读取数据流时,它会基于换行符的位置将数据分成行。sed编辑器根据定义好的脚本命令一次处理一行数据,然后移到下一行重复这个过程。
有时需要对跨多行的数据执行特定操作。如果要查找或替换一个短语,每行各包含其中一部分短语。如果用普通的sed编辑器命令来处理文本,就不可能发现这种被分开的短语。
三个可用来处理多行文本的特殊命令:
参数 | 描述 |
---|---|
N | 将数据流中的下一行加进来创建一个多行组(multiline group)来处理 |
D | 删除多行组中的一行 |
P | 打印多行组中的一行 |
n 命令会告诉sed编辑器移动到数据流中的下一文本行,而不用重新回到命令的最开始再执行一遍。
$ cat data1.txt
This is the header line.
This is a data line.
This is the last line.
$ sed '/^$/d' data1.txt
This is the header line.
This is a data line.
This is the last line.
# 删除含有‘header’行的下一行
$ sed '/header/{n ; d}' data1.txt
This is the header line.
This is the last line.
N 命令会将下一文本行添加到模式空间中已有的文本后。这样的作用是将数据流中的两个文本行合并到同一个模式空间中。文本行仍然用换行符分隔,但sed编辑器现在会将两行文本当成一行来处理。
$ cat data2.txt
This is the header line.
This is the first data line.
This is the second data line.
This is the last line.
# 查找含有单词first的那行文本。用N命令将下一行合并到那行,然后用替换命令s将换行符替换成空格
$ sed '/first/{ N ; s/\n/ / }' data2.txt
This is the header line.
This is the first data line. This is the second data line.
This is the last line.
替换分散在两行的文本短语。
$ cat data3.txt
On Tuesday, the Linux System
Administrator's group meeting will be held.
All System Administrators should attend.
Thank you for your attendance.
$ sed 's/System.Administrator/Desktop User/' data3.txt
On Tuesday, the Linux System
Administrator's group meeting will be held.
All Desktop Users should attend.
Thank you for your attendance
# 用N命令将发现第一个单词的那行和下一行合并后,替换命令在System和Administrator之间用了通配符模式(.)来匹配任意一个字符
$ sed 'N ; s/System.Administrator/Desktop User/' data3.txt
On Tuesday, the Linux Desktop User's group meeting will be held.
All Desktop Users should attend.
Thank you for your attendance.
# 但当它把换行符也替换为了空格,导致两行合并成一行
在sed编辑器脚本中用两个替换命令:一个用来匹配短语出现在多行中的情况,一个用来匹配短语出现在单行中的情况。
$ sed 'N
> s/System\nAdministrator/Desktop\nUser/
> s/System Administrator/Desktop User/
> ' data3.txt
On Tuesday, the Linux Desktop
User's group meeting will be held.
All Desktop Users should attend.
Thank you for your attendance.
脚本总是在执行sed编辑器命令前将下一行文本读入到模式空间。当它到了最后一行文本时,因为没有下一行可读,所以N命令会叫sed编辑器停止。
如果要匹配的文本正好在数据流的最后一行上,命令就不会发现要匹配的数据。比如下面的例子:
$ cat data4.txt
On Tuesday, the Linux System
Administrator's group meeting will be held.
All System Administrators should attend.
$ sed 'N
> s/System\nAdministrator/Desktop\nUser/
> s/System Administrator/Desktop User/
> ' data4.txt
On Tuesday, the Linux Desktop
User's group meeting will be held.
All System Administrators should attend.
由于System Administrator文本出现在了数据流中的最后一行,N 命令会错过它,因为没有其他行可读入到模式空间跟这行合并。
解决这个问题:将单行命令放到N命令前面,并将多行命令放到N命令后面
$ sed '
> s/System Administrator/Desktop User/
> N
> s/System\nAdministrator/Desktop\nUser/
> ' data4.txt
On Tuesday, the Linux Desktop
User's group meeting will be held.
All Desktop Users should attend.
合并行案例:
$ cat file.txt
host1
192.168.0.1
host2
192.168.0.2
host3
192.168.0.3
# 换行符“\n” 替换为 制表符“\t”
$ sed '{N ; s/\n/\t/ }' file.txt
host1 192.168.0.1
host2 192.168.0.2
host3 192.168.0.3
sed编辑器提供了多行删除命令 D,该命令会删除到换行符(含换行符)为止的所有字符。也就是只会删除模式空间中的第一行。
$ cat data4.txt
On Tuesday, the Linux System
Administrator's group meeting will be held.
All System Administrators should attend.
# 如果用N搭配单行删除命令(d)来使用,会将匹配到的行都删掉;
$ sed 'N ; /System\nAdministrator/d' data4.txt
All System Administrators should attend.
# 如果N搭配多行删除命令(D)来使用,只删除了第一行
$ sed 'N ; /System\nAdministrator/D' data4.txt
Administrator's group meeting will be held.
All System Administrators should attend.
P 只打印多行模式空间中的第一行。这包括模式空间中直到换行符为止的所有字符。
$ sed -n 'N ; /System\nAdministrator/P' data3.txt
On Tuesday, the Linux System
当多行匹配出现时,P命令只会打印模式空间中的第一行。多行P命令的强大之处在和N命令及D命令组合使用时才能显现出来。
D命令的独特之处在于强制sed编辑器返回到脚本的起始处,对同一模式空间中的内容重新执行这些命令(它不会从数据流中读取新的文本行)。在命令脚本中加入N命令,就能单步扫过整个模式空间,将多行一起匹配。
接下来,使用P命令打印出第一行,然后用D命令删除第一行并绕回到脚本的起始处。一旦返回,N命令会读取下一行文本并重新开始这个过程。这个循环会一直继续下去,直到数据流结束
模式空间(pattern space)是一块活跃的缓冲区,在sed编辑器执行命令时它会保存待检查的文本。但它并不是sed编辑器保存文本的唯一空间。
sed编辑器有另一块称作保持空间的缓冲区域。在处理模式空间中的某些行时,可以用保持空间来临时保存一些行。
sed编辑器的保持空间命令:
命令 | 描述 |
---|---|
h | 将模式空间复制到保持空间 |
H | 将模式空间附加到保持空间 |
g | 将保持空间复制到模式空间 |
G | 将保持空间附加到模式空间 |
x | 交换模式空间和保持空间的内容 |
这些命令用来将文本从模式空间复制到保持空间。这可以清空模式空间来加载其他要处理的字符串。通常,在使用h或H命令将字符串移动到保持空间后,最终还要用g、G或x命令将保存的字符串移回模式空间。
这里有个简短的例 子演示了如何用h和g命令来将数据在sed编辑器缓冲空间之间移动。
$ cat data2.txt
AAA
BBB
CCC
DDD
$ sed -n '/BBB/ {h ; p ; n ; p ; g ; p }' data2.txt
BBB
CCC
BBB
上面这个例子分析
$ cat data2.txt
AAA
BBB
CCC
DDD
$ sed -n '{1!G ; h ; $p }' data2.txt
DDD
CCC
BBB
AAA
# tac 命令有同样效果
$ tac data2.txt
DDD
CCC
BBB
AAA
tac 命令会倒序显示一个文本文件。名字是 cat 反写,它执行的正好是与cat命令相反的功能。
排除命令 ! 可以排除作用在某行上的命令,也就是让原本会起作用的命令不起作用。
$ cat file.txt
AAA
BBB
CCC
$ sed -n '/BBB/!p' file.txt
AAA
CCC
一般情况下,sed 编辑器会从脚本的顶部开始,一直执行到脚本的结尾。但是像d/n/D/N命令都能够在一定程度上改变默认的执行流程,甚至利用N/D/P三个命令可以形成一个强大的循环处理流程。
除此之外,其实sed还提供了分支(b)和测试(t)两个命令来控制流程,这两个命令可以跳转到指定的标签(label)位置继续执行命令。其结果与结构化编程类似。
! 命令可以排除作用在某行上的命令。sed编辑器提供另一种方法分支命令 b,可以基于地址、地址模式或地址区间排除一整块命令。这允许你只对特定行执行一组命令。
分支(branch)命令 b 的格式如下:
[address]b [label]
address :地址、地址模式或者地址区间(排除哪些数据)。
label :定义要跳转到的位置,如果没有加 label 参数,跳转命令会跳转到脚本的结尾。
$ cat data2.txt
This is the header line.
This is the first data line.
This is the second data line.
This is the last line.
# 排除2,3行,对其他行执行替换
$ sed '{2,3b ; s/This is/Is this/ ; s/line./test?/}' data2.txt
Is this the header test?
This is the first data line.
This is the second data line.
Is this the last test?
如果不想直接跳到脚本的结尾,可以为分支命令自定义一个要跳转到的标签。
# 标签以冒号开始,最多可以是7个字符长度
:label
# 类似if语句的效果
# 当执行到/pattern/b end时,如果匹配pattern,则跳转到:end标签所在的位置,跳过command2而不执行
command1
/pattern/b end
command2
:end
command3
要指定标签,将它加到 b 命令后即可。使用标签使shell跳过地址匹配处的命令,但仍然执行脚本中的其他命令。
$ sed '{s/This/ABC/
> /first/b jump
> s/line/No jump/
> :jump
> s/line/Yes jump/}' file.txt
ABC is the header No jump.
ABC is the first data Yes jump.
ABC is the second data No jump.
ABC is the last No jump.
如果分支命令的模式没有匹配,sed编辑器会继续执行脚本中的命令,包括分支标签后的命令。如果某行匹配了分支模式,sed编辑器就会跳转到带有分支标签(:jump)的那行(跳过地址匹配处的命令),执行之后的命令。
也可以跳转到脚本中靠前面的标签上,这样就达到了循环的效果。
$ echo "This, is, a, test, to, remove, commas." | sed -n '{
> :start
> s/,//1p
> b start
> }'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
^C
脚本的每次迭代都会删除文本中的第一个逗号,并打印字符串。这个脚本有个问题:它永远不会结束。这就形成了一个无穷循环,不停地查找逗号,直到使用Ctrl+C组合键发送一个信号,手动停止这个脚本。
要防止这个问题,可以为分支命令指定一个地址模式来查找。如果没有匹配,跳转就会结束。
$ echo "This, is, a, test, to, remove, commas." | sed -n '{
> :start
> s/,//1p # 1 第一个逗号
> /,/b start # 匹配到逗号,才会触发标签跳转
> }'
类似于分支命令,测试命令 t 也可以用来改变sed编辑器脚本的执行流程。测试命令会根据替换命令的结果跳转到某个标签,而不是根据地址进行跳转。
如果替换命令成功匹配并替换了一个模式,测试命令就会跳转到指定的标签。如果替换命令未能匹配指定的模式,测试命令就不会跳转(类似case结构)。
[address]t [label]
跟分支命令一样,在没有指定标签的情况下,如果测试成功,sed会跳转到脚本的结尾。
举个例子,如果第一个替换命令成功执行了,那么就不会执行第二条替换命令 。
# 第一个替换命令会查找模式文本first。如果匹配了行中的模式,它就会替换文本,而且测试命令会跳过后面的替换命令。如果第一个替换命令未能匹配模式,第二个替换命令就会被执行
$ sed '{
> s/first/matched/
> t
> s/This is the/No match on/
> }' data2.txt
No match on header line
This is the matched data line
No match on second data line
No match on last line
第一个替换命令会查找模式文本first。如果匹配了行中的模式,它就会替换文本,而且测试命令会跳过后面的替换命令。如果第一个替换命令未能匹配模式,第二个替换命令就会被执行。
$ echo "This, is, a, test, to, remove, commas. " | sed -n '{
> :start
> s/,//1p
> t start
> }'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
已经知道了如何在sed命令中使用模式来替代数据流中的文本。然而在使用通配符时,很难知道到底哪些文本会匹配模式。
举个例子,假如要在行中匹配的单词两边上放上引号。如果只是要匹配模式中的一个单词,那就非常简单:
$ echo "The cat sleeps in his hat." | sed 's/cat/"cat"/'
The "cat" sleeps in his hat.
但如果在模式中用通配符(.)来匹配多个单词,就存在问题
$ echo "The cat sleeps in his hat." | sed 's/.at/".at"/g'
The ".at" sleeps in his ".at".
模式字符串用点号通配符来匹配at前面的一个字母。但是,用于替代的字符串无法匹配已匹配单词中的通配符字符。
& 符号可以用来代表替换命令中匹配的数据。不管模式匹配的是什么样的文本,都可以在替代模式中使用&符号来使用这段文本。& 符号会提取匹配替换命令中指定模式的整个字符串。
$ echo "The cat sleeps in his hat." | sed 's/.at/"&"/g'
The "cat" sleeps in his "hat".
当模式匹配了单词cat,"cat"就会出现在了替换后的单词里。当它匹配了单词hat,"hat"就出现在了替换后的单词中。
sed编辑器用圆括号( )来定义替换模式中的子模式。可以在替代模式中使用特殊字符来引用每个子模式。
替代字符由反斜线和数字组成,数字表明子模式的位置,sed编辑器会给第一个子模式分配字符\1,给第二个子模式分配字符\2,依此类推。
当在替换命令中使用圆括号时,必须用转义字符将它们标示为分组字符而不是普通的圆括号。这跟转义其他特殊字符正好相反。
$ echo "The System Administrator manual" | sed '
> s/\(System\) Administrator/\1 User/'
The System User manual
这个替换命令用一对圆括号将单词System括起来,将其标示为一个子模式。然后它在替代模式中使用\1来提取第一个匹配的子模式。这没什么特别的,但在处理通配符模式时却特别有用。
$ echo "That furry cat is pretty" | sed 's/furry \(.at\)/\1/'
That cat is pretty
$ echo "That furry hat is pretty" | sed 's/furry \(.at\)/\1/'
That hat is pretty
当需要在两个或多个子模式间插入文本时,这个特性尤其有用。这里有个脚本,它使用子模式在大数字中插入逗号。
$ echo "1234567" | sed '{
> :start
> s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/
> t start
> }'
1,234,567
$
# 这个脚本将匹配模式分成了两部分。
.*[0-9] # 以数字结尾的任意长度的字符
[0-9]{3} # 三位数字
替代文本会在两个子模式之间加一个逗号,每个子模式都会通过其位置来标示。这个脚本使用测试命令来遍历这个数字,直到放置好所有的逗号。
可以将sed编辑器命令放到shell包装脚本中,不用每次使用时都重新键入整个脚本。包装脚本充当着sed编辑器脚本和命令行之间的中间人角色。
$ cat reverse.sh
sed -n '{ 1!G ; h ; $p }' $1
$ ./reverse.sh data2.txt
This is the last line.
This is the second data line.
This is the first data line.
This is the header line.
名为reverse的shell脚本用sed编辑器脚本来反转数据流中的文本行。它使用shell参数$1从命令行中提取第一个参数,这正是需要进行反转的文件名
默认情况下,sed编辑器会将脚本的结果输出到STDOUT上。可以在shell脚本中使用各种标准方法对sed编辑器的输出进行重定向。
可以在脚本中用 $() 将sed编辑器命令的输出重定向到一个变量中。
$ cat fact.sh
#!/bin/bash
# 对阶乘答案中的数字添加逗号
factorial=1
counter=1
number=$1
#
while [ $counter -le $number ]
do
factorial=$[ $factorial * $counter ]
counter=$[ $counter + 1 ]
done
# sed 输出保存到变量
result=$(echo $factorial | sed '{
:start
s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/
t start
}')
#
echo "The result is $result"
$ ./fact.sh 20
The result is 2,432,902,008,176,640,000
G 命令会简单地将保持空间内容附加到模式空间内容后。当启动sed编辑器时,保持空间只有一个空行(默认值)。将它附加到已有行后面,就在已有行后面创建了一个空白行。
# 向文本文件的行间插入空白行
$ sed 'G' data2.txt
This is the header line.
This is the first data line.
This is the second data line.
This is the last line.
$
不过最后一行后面也加了一个空白行,可以用排除符号(!)和尾行符号($)来确保脚本不会将空白行加到数据流的最后一行后面。当sed编辑器到了最后一行时,它会跳过G命令。
$ sed '$!G' data2.txt
对可能含有空白行的文件加倍行间距。先删除数据流中的所有空白行:/^$/d ,然后用G命令在所有行后插入新的空白行。
$ cat data6.txt
This is line one.
This is line two.
This is line three.
This is line four.
$ sed '/^$/d ; $!G' data6.txt
# “=”显示数据流中行的行号
$ sed '=' data2.txt
1
This is the header line.
2
This is the first data line.
3
This is the second data line.
4
This is the last line.
# 配合“N"合并行后,把换行符替换为空格
$ sed '=' data2.txt | sed 'N; s/\n/ /'
1 This is the header line.
2 This is the first data line.
3 This is the second data line.
4 This is the last line.
有些bash shell命令也可以添加行号,但它们会另外加入一些东西(有可能是不需要的间隔)。
$ nl data2.txt
1 This is the header line.
2 This is the first data line.
3 This is the second data line.
4 This is the last line.
$ cat -n data2.txt
1 This is the header line.
2 This is the first data line.
3 This is the second data line.
4 This is the last line.
显示最后一行。
$ sed -n '$p' data.txt
N命令将下一行文本附加到模式空间中已有文本行后面。一旦在模式空间有了一个10行的文本块,可以用美元符来检查你是否已经处于数据流的尾部。如果不在,就继续向模式空间增加行,同时删除原来的行(记住,D命令会删除模式空间的第一行)。
通过循环N命令和D命令,在向模式空间的文本行块增加新行的同时也删除了旧行。分支命令非常适合这个循环。要结束循环,只要识别出最后一行并用q命令退出就可以了。
# 显示文件最后10行
$ sed '{
> :start
> $q ; N ; 11,$D
> b start
> }' data.txt
# 删除连续的空白行
$ sed '/./,/^$/!d' xxx
区间是/./到/^$/。区间的开始地址会匹配任何含有至少一个字符的行。区间的结束地址会匹配一个空行。在这个区间内的行不会被删除。
# 删除开头的空白行
$ sed '/./,$!d' xxx
这个脚本用地址区间来决定哪些行要删掉。这个区间从含有字符的行开始,一直到数据流结束。在这个区间内的任何行都不会从输出中删除。这意味着含有字符的第一行之前的任何行都会删除。
# 删除结尾的空白行
$ sed '{
> :start
> /^\n*$/{$d ; N ; b start }
> }' xxx
注意,在正常脚本的花括号里还有花括号。这允许你在整个命令脚本中将一些命令分组。该命令组会被应用在指定的地址模式上。地址模式能够匹配只含有一个换行符的行。如果找到了这样的行,而且还是最后一行,删除命令会删掉它。如果不是最后一行,N命令会将下一行附加到它后面,分支命令会跳到循环起始位置重新开始。
$ cat data11.txt
<html>
<head>
<title>This is the page titletitle>
head>
<body>
<p>
This is the <b>firstb> line in the Web page.
This should provide some <i>usefuli>
information to use in our sed script.
body>
html>
^> 忽略掉嵌入到原始标签中的大于号;然后再删除空白行 /^$/d
$ sed 's/<[^>]*>//g ; /^$/d' data11.txt
This is the page title
This is the first line in the Web page.
This should provide some useful
information to use in our sed script.
gawk能提供一个类编程环境来修改和重新组织文件中的数据。gawk程序是Unix中的原始awk程序的GNU版本。gawk程序让流编辑迈上了一个新的台阶,它提供了一种编程语言而不只是编辑器命令。
gawk编程语言可以做下面的事情:
1、定义变量来保存数据;
2、使用算术和字符串操作符来处理数据;
3、使用结构化编程概念(比如if-then语句和循环)来为数据处理增加处理逻辑;
4、通过提取数据文件中的数据元素,将其重新排列或格式化,生成格式化报告;
gawk options program file
gawk 选项:
选项 | 描述 |
---|---|
-F fs | 指定行中划分数据字段的字段分隔符 |
-f file | 从指定的文件中读取程序 |
-v var=value | 定义gawk程序中的一个变量及其默认值 |
-mf N | 指定要处理的数据文件中的最大字段数 |
-mr N | 指定数据文件中的最大数据行数 |
-W keyword | 指定gawk的兼容模式或警告等级 |
gawk程序脚本用一对花括号来定义。你必须将脚本命令放到花括号 {} 中。由于gawk命令行假定脚本是单个文本字符串,还必须将脚本放到单引号中。
$ gawk '{print "Hello World!"}'
这个程序脚本定义了一个print命令:将文本打印到STDOUT。如果运行这个命令,什么都不会发生。原因在于没有在命令行上指定文件名,所以gawk程序会从STDIN接收数据。它会一直等待从STDIN输入的文本。
跟sed编辑器一样,gawk程序会针对数据流中的每行文本执行程序脚本。由于程序脚本被设为显示一行固定的文本字符串,因此不管在数据流中输入什么文本,都会得到同样的文本输出。
gawk的主要特性之一是其处理文本文件中数据的能力。它会自动给一行中的每个数据元素分配一个变量。默认情况下,gawk会将如下变量分配给它在文本行中发现的数据字段:
变量 | 描述 |
---|---|
$0 | 整个文本行 |
$1 | 文本行中的第1个数据字段 |
$2 | 文本行中的第2个数据字段 |
$n | 文本行中的第n个数据字段 |
在文本行中,每个数据字段都是通过字段分隔符划分的。gawk在读取一行文本时,会用预定义的字段分隔符划分每个数据字段。gawk中默认的字段分隔符是任意的空白字符(例如空格或制表符)。
$ cat data2.txt
One line of test text.
Two lines of test text.
Three lines of test text.
# 读取每行文本的第1个数据字段(第一列)
$ gawk '{print $1}' data2.txt
One
Two
Three
# -F 指定分隔符
$ gawk -F: '{print $1}' /etc/passwd
root
bin
daemon
要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号即可。
# 第一条命令会给字段变量$4赋值。第二条命令会打印整个数据字段
$ echo "My name is Rich" | gawk '{$4="Christine"; print $0}'
My name is Christine
# 多行输入命令
$ gawk '{
> $4="Christine"
> print $0}'
My name is Rich
My name is Christine
因为没有在命令行中指定文件名,gawk程序会 从STDIN中获得数据。当运行这个程序的时候,它会等着读取来自STDIN的文本。
$ cat script2.gawk
{print $1 "'s home directory is " $6}
# 或
$ cat script2.gawk
{
text = "'s home directory is "
print $1 text $6
}
$ gawk -F: -f script2.gawk /etc/passwd
root's home directory is /root
bin's home directory is /bin
daemon's home directory is /sbin
adm's home directory is /var/adm
gawk还允许指定程序脚本何时运行:BEGIN会强制gawk在处理数据前执行BEGIN关键字后指定的程序脚本。
$ cat data3.txt
Line 1
Line 2
Line 3
$ gawk 'BEGIN {print "The data3 File Contents:"}
> {print $0}' data3.txt
The data3 File Contents:
Line 1
Line 2
Line 3
在gawk执行了BEGIN脚本后,它会用第二段脚本来处理文件数据。注意,两段脚本仍然被认为是gawk命令行中的一个文本字符串,需要相应地加上单引号。
与BEGIN关键字类似,END关键字允许你指定一个程序脚本,gawk会在读完数据后执行它。
$ gawk 'BEGIN {print "The data3 File Contents:"}
> {print $0}
> END {print "End of File"}' data3.txt
The data3 File Contents:
Line 1
Line 2
Line 3
End of File
# 格式化输出 /etc/passwd
$ cat script4.gawk
BEGIN {
print "The latest list of users and shells"
print " UserID \t Shell"
print "-------- \t -------"
FS=":"
}
{
print $1 " \t " $7
}
END {
print "This concludes the listing"
}
$ gawk -f script4.gawk /etc/passwd
The latest list of users and shells
UserID Shell
-------- -------
root /bin/bash
bin /sbin/nologin
gawk是一门功能丰富的编程语言,可以通过它所提供的各种特性来编写高级程序处理数据。
gawk编程语言支持两种不同类型的变量:
1、内建变量
2、自定义变量
gawk中的一种内建变量类型:数据字段变量,使用美元符号和字段在该记录中的位置值来引用记录对应的字段。要引用记录中的第一个数据字段,就用变量 $1;要引用第二个字段,就用$2,依次类推。
数据字段是由字段分隔符来划定的。默认情况下,字段分隔符是一个空白字符,也就是空格符或者制表符。
gawk数据字段和记录变量:
内置变量 | 描述 |
---|---|
FIELDWIDTHS | 由空格分隔的一列数据,定义了每个数据字段确切宽度 |
FS | 输入字段分隔符;(字段分隔符,默认:空格) |
OFS | 输出字段分隔符;(字段分隔符,默认:空格) |
RS | 输入行分隔符;(记录分隔符,默认:换行符\n) |
ORS | 输出行分隔符;(记录分隔符,默认:换行符\n) |
什么是field(字段),什么是record(记录行)
- i am a student.
- i like to swim
- hello moto
1代表第一个记录行,2代表第二个记录行。通过观察我们可以知道总共有3个记录行(record)。
看看第一行:“i am a student”,这一行的每个单词都是一个字段(field)。“i”是一个字段,“am”是一个字段… 该行总共有4个字段。
FS 是用来定义输入字段分隔符。OFS 用来定义输出字段分隔符。
$ cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
$ gawk 'BEGIN{FS=","; OFS="-"} {print $1,$2,$3}' data1
data11-data12-data13
data21-data22-data23
data31-data32-data33
print命令会自动将OFS变量的值放置在输出中的每个字段间。通过设置OFS变量,可以在输出中使用任意字符串来分隔字段。
FIELDWIDTHS 变量允许不依靠字段分隔符来读取记录。在一些应用程序中,数据并没有使用字段分隔符,而是放置在了记录中的特定列。这种情况下,必须设定FIELDWIDTHS变量来匹配数据在记录中的位置。
一旦设置了FIELDWIDTH变量,gawk就会忽略FS变量,并根据提供的字段宽度来计算字段。
$ cat data1b
1005.3247596.37
115-2.349194.00
05810.1298100.1
# 采用字段宽度而非字段分隔符
$ gawk 'BEGIN{FIELDWIDTHS="3 5 2 5"}{print $1,$2,$3,$4}' data1b
100 5.324 75 96.37
115 -2.34 91 94.00
058 10.12 98 100.1
FIELDWIDTHS变量定义了四个字段,gawk依此来解析数据记录。每个记录中的数字串会根据已定义好的字段长度来分割。
一旦设定了FIELDWIDTHS变量的值,就不能再改变了。这种方法并不适用于变长的字段。这种方法并不适用于变长的字段。
默认情况下,gawk将RS和ORS设为换行符。默认的RS值表明,输入数据流中的每行新文本就是一条新纪录。
$ cat test.txt
a|b|c
# 输入数据行分隔符换为“|”
$ gawk 'BEGIN{ RS="|"; } { print $0 }' test.txt
a
b
c
# 输出数据行分隔符换为“-”
$ cat test2.txt
a
b
c
$ gawk 'BEGIN{ ORS="-" }{ print $0 }' test2.txt
a-b-c
除了字段和记录分隔符变量外,gawk还提供了其他一些内建变量来帮助了解数据发生了什么变化,并提取shell环境的信息。
gawk内建变量:
变量 | 描述 |
---|---|
ARGC | 当前命令行参数个数 |
ARGIND | 当前文件在ARGV中的位置 |
ARGV | 包含命令行参数的数组 |
CONVFMT | 数字的转换格式(参见printf语句),默认值为%.6 g |
ENVIRON | 当前shell环境变量及其值组成的关联数组 |
ERRNO | 当读取或关闭输入文件发生错误时的系统错误号 |
FILENAME | 当读取或关闭输入文件发生错误时的系统错误号 |
FNR | 当前数据文件中的数据行数 |
IGNORECASE | 设成非零值时,忽略gawk命令中出现的字符串的字符大小写 |
NF | 数据文件中的字段总数 |
NR | 已处理的输入记录数 |
OFMT | 数字的输出格式,默认值为%.6 g |
RLENGTH | 由match函数所匹配的子字符串的长度 |
RSTART | 由match函数所匹配的子字符串的起始位置 |
ARGC 和 ARGV 变量允许从shell中 获得命令行参数的总数以及它们的值。但gawk并不会将程序脚本当成命令 行参数的一部分。
$ gawk 'BEGIN{print ARGC,ARGV[1]}' data1
2 data1
ARGC 变量表明命令行上有2个参数:”gawk“和”data1“。
跟shell变量不同,在脚本中引用gawk变量时,变量名前不加美元符。
ENVIRON 使用关联数组来提取shell环境变量。关联数组用文本 作为数组的索引值,而不是数值。数组索引中的文本是shell环境变量名,而数组的值则是shell环境变量的值。
# 获取全局变量HOME、PATH的值
$ gawk 'BEGIN{ print ENVIRON["HOME"]; print ENVIRON["PATH"] }'
/home/xx
/usr/local/sbin:/usr/local/bin: ...
NF 变量可以在不知道具体位置的情况下指定记录中的最后一个数据字段。NF变量含有数据文件中最后一个数据字段的数字值。可以在它前面加个美元符将其用作字段变量。
$ gawk 'BEGIN{FS=":"; OFS=":"} {print $1,$NF}' /etc/passwd
rich:/bin/bash
testy:/bin/csh
mark:/bin/bash
dan:/bin/bash
mike:/bin/bash
test:/bin/bash
FNR 变量含有当前数据文件中已处理过的记录数。
$ cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
$ gawk 'BEGIN{FS=","}{print $1,"FNR="FNR}' data1 data1
data11 FNR=1
data21 FNR=2
data31 FNR=3
data11 FNR=1
data21 FNR=2
data31 FNR=3
在这个例子中,gawk程序的命令行定义了两个输入文件(两次指定的是同样的输入文件)。这个脚本会打印第一个数据字段的值和FNR变量的当前值。注意,当gawk程序处理第二个数据文件时,FNR值被设回了1。
NR 变量则含有已处理过的记录总数。
$ gawk '
> BEGIN {FS=","}
> {print $1,"FNR="FNR,"NR="NR}
> END{print "There were",NR,"records processed"}' data1 data1
data11 FNR=1 NR=1
data21 FNR=2 NR=2
data31 FNR=3 NR=3
data11 FNR=1 NR=4
data21 FNR=2 NR=5
data31 FNR=3 NR=6
There were 6 records processed
FNR变量的值在gawk处理第二个数据文件时被重置了,而NR变量则在处理第二个数据文件时继续计数。
结果就是:
1、如果只使用一个数据文件作为输入,FNR和NR的值是相同的;
2、如果使用多个数据文件作为输入,FNR的值会在处理每个数据文件时被重置,而NR的值则会继续计数直到处理完所有的数据文件。
在shell脚本中使用gawk时,应该将不同的gawk命令放到不同的行,这样会比较容易阅读和理解,不要在shell脚本中将所有的命令都塞到同一行。还有,如果在不同的shell脚本中用到了同样的gawk脚本,最好将这段gawk脚本放到一个单独的文件中,并 用-f参数来在shell脚本中引用它。
$ gawk '
> BEGIN{
> testing="This is a test"
> print testing
> testing=45
> print testing
> }'
This is a test
45
# 赋值语句还可以包含数学算式来处理数字值
$ gawk 'BEGIN{x=4; x= x * 2 + 3; print x}'
11
$ cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
$ cat script1
BEGIN{FS=","}
{print $n}
$ gawk -f script1 n=2 data1
data12
data22
data32
$ gawk -f script1 n=3 data1
data13
data23
data33
使用命令行参数来定义变量值会有一个问题。设置了变量后,这个值在代码的 BEGIN 部不可用。
可以用 -v 命令行参数来解决这个问题。它允许你在BEGIN代码之前设定变量。在命令行上,-v命令行参数必须放在脚本代码之前。
$ cat script2
BEGIN{print "The starting value is",n; FS=","}
{print $n}
$ gawk -f script2 n=3 data1
The starting value is
data13
data23
data33
$ gawk -v n=3 -f script2 data1
The starting value is 3
data13
data23
data33
为了在单个变量中存储多个值,许多编程语言都提供数组。gawk 编程语言使用关联数组提供数组功能。
关联数组跟数字数组不同之处在于它的索引值可以是任意文本字符串。这跟散列表和字典是同一个概念。
var[index] = element
# var:变量名
# index:关联数组的索引值
# element:数据元素值
capital["Illinois"] = "Springfield"
# 在引用数组变量时,必须包含索引值来提取相应的数据元素值
$ gawk 'BEGIN{
> capital["Illinois"] = "Springfield"
> print capital["Illinois"]
> }'
Springfield
# 在引用数组变量时,会得到数据元素的值。数据元素值是数字值时也一样
$ gawk 'BEGIN{
> var[1] = 34
> var[2] = 3
> total = var[1] + var[2]
> print total
> }'
37
如果要在gawk中遍历一个关联数组,可以用for语句的一种特殊形式:
for (var in array)
{
statements
}
这个for语句会在每次循环时将关联数组array的下一个索引值赋给变量var,然后执行一遍statements。重要的是记住这个变量中存储的是索引值而不是数组元素值。可以将这个变量用作数组的索引,轻松地取出数据元素值。
$ gawk 'BEGIN{
> var["a"] = 1
> var["g"] = 2
> var["m"] = 3
> var["u"] = 4
> for (test in var)
> {
> print "Index:",test," - Value:",var[test]
> }
> }'
Index: u - Value: 4
Index: m - Value: 3
Index: a - Value: 1
Index: g - Value: 2
注意,索引值不会按任何特定顺序返回,但它们都能够指向对应的数据元素值。
格式:
delete array[index]
删除命令会从数组中删除关联索引值和相关的数据元素值。
$ gawk 'BEGIN{
> var["a"] = 1
> var["g"] = 2
> for (test in var)
> {
> print "Index:",test," - Value:",var[test]
> }
> delete var["g"]
> print "---"
> for (test in var)
> print "Index:",test," - Value:",var[test]
> }'
Index: a - Value: 1
Index: g - Value: 2
---
Index: a - Value: 1
gawk 程序支持多种类型的匹配模式来过滤数据记录,这一点跟sed编辑器大同小异。BEGIN和END关键字是用来在读取数据流之前或之后执行命令的特殊模式。类似地,也可以创建其他模式在数据流中出现匹配数据时执行一些命令。
正则表达式必须出现在它要控制的程序脚本的左花括号前。
$ cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
$ gawk 'BEGIN{FS=","} /11/{print $1}' data1
data11
正则表达式/11/匹配了数据字段中含有字符串11的记录。gawk程序会用正则表达式对记录中所有的数据字段进行匹配,包括字段分隔符。
$ gawk 'BEGIN{FS=","} /,d/{print $1}' data1
data11
data21
data31
匹配操作符允许将正则表达式限定在记录中的特定数据字段。匹配操作符是波浪线(~)。可以指定匹配操作符、数据字段变量以及要匹配的正则表达式。
$1 ~ /^data/
$1变量代表记录中的第一个数据字段。这个表达式会过滤出第一个字段以文本data开头的所有记录。
在数据文件中搜索特定的数据元素
# 第一个数据字段中查找文本rich ; NF字段总数,所以$NF是最后一个字段
$ gawk -F: '$1 ~ /rich/{print $1,$NF}' /etc/passwd
rich /bin/bash
这个例子会在第一个数据字段中查找文本rich。如果在记录中找到了这个模式,它会打印该记录的第一个和最后一个数据字段值(NF:数据文件中的字段总数)。
也可以用 ! 符号来排除正则表达式的匹配。
$ gawk –F: '$1 !~ /rich/{print $1,$NF}' /etc/passwd
root /bin/bash
daemon /bin/sh
在这个例子中,gawk程序脚本会打印 /etc/passwd 文件中与用户ID rich 不匹配的用户ID和登录shell。
除了正则表达式,也可以在匹配模式中用数学表达式。这个功能在匹配数据字段中的数字值时非常方便。
举个例子,如果想显示所有属于root用户组(组ID为0)的系统用户,可以用这个脚本。
# 查看第四个数据字段含有值0的记录
$ gawk -F: '$4 == 0{print $1}' /etc/passwd
root
sync
shutdown
可以使用任何常见的数学比较表达式:
# x == y
# x <= y
# x < y
# x >= y
# x > y
也可以对文本数据使用表达式,但跟正则表达式不同,表达式必须完全匹配。数据必须跟模式严格匹配。
$ gawk -F, '$1 == "data"{print $1}' data1
$
$ gawk -F, '$1 == "data11"{print $1}' data1
data11
第一个测试没有匹配任何记录,因为第一个数据字段的值不在任何记录中。第二个测试用值 data11 匹配了一条记录。
gawk编程语言支持常见的结构化编程命令。
gawk编程语言支持标准的 if-then-else 格式的if语句。必须为 if 语句定义一个求值的条件,并将其用圆括号括起来。如果条件求值为TRUE,紧跟在 if 语句后的语句会执行。如果条件求值为FALSE,这条语句就会被跳过。
if (condition)
statement1
# 也可以将它放在一行上
if (condition) statement1
$ cat data4
10
5
50
$ gawk '{if ($1 > 20) print $1}' data4
50
# 如果需要在if语句中执行多条语句,就必须用花括号将它们括起来。
$ gawk '{
> if ($1 > 20)
> {
> x = $1 * 2
> print x
> }
> }' data4
100
# 也支持else子句,允许在if语句条件不成立的情况下执行一条或多条语句
$ gawk '{
> if ($1 > 20)
> {
> x = $1 * 2
> print x
> } else
> {
> x = $1 / 2
> print x
> }}' data4
5
2.5
100
可以在单行使用else子句,但必须在if语句部分之后使用分号。
if (condition) statement1; else statement2
$ gawk '{if ($1 > 20) print $1 * 2; else print $1 / 2}' data4
5
2.5
6.5
100
68
while (condition)
{
statements
}
while循环允许遍历一组数据,并检查迭代的结束条件。如果在计算中必须使用每条记录中(每行)的多个数据值,这个功能能帮得上忙。
# 每行数据的平均值
$ cat data5
130 120 135
160 113 140
145 170 215
$ gawk '{
> total = 0
> i = 1
> while (i < 4)
> {
> total += $i
> i++
> }
> avg = total / 3
> print "Average:",avg
> }' data5
Average: 128.333
Average: 137.667
Average: 176.667
while语句会遍历记录中的数据字段,将每个值都加到total变量上,并将计数器变量 i 增值。当计数器值等于4时,while的条件变成了FALSE,循环结束,然后执行脚本中的下一条语句。这条语句会计算并打印出平均值。这个过程会在数据文件中的每条记录上(每行)不断重复。
gawk编程语言支持在while循环中使用break语句和continue语句,从循环中跳出。
$ gawk '{
> total = 0
> i = 1
> while (i < 4)
> {
> total += $i
> if (i == 2)
> break
> i++
> }
> avg = total / 2
> print "The average of the first two data elements is:",avg
> }' data5
The average of the first two data elements is: 125
The average of the first two data elements is: 136.5
The average of the first two data elements is: 157.5
do-while 语句类似于 while 语句,但会在检查条件语句之前执行命令。
do
{
statements
} while (condition)
这种格式保证了语句会在条件被求值之前至少执行一次。当需要在求值条件前执行语句时,这个特性非常方便。
$ cat data5
130 120 135
160 113 140
145 170 215
$ gawk '{
> total = 0
> i = 1
> do
> {
> total += $i
> i++
> } while (total < 150)
> print total }' data5
250
160
315
这个脚本会读取每条记录(每行)的数据字段并将它们加在一起,直到累加结果达到150。如果第一个数据字段大于150(就像在第二条记录中看到的那样),则脚本会保证在条件被求值前至少读取第一个数据字段的内容。
gawk编程语言支持C风格的for循环。for循环中定义了迭代计数器,不用担心要像使用while语句一样自己负责给计数器增值了。
$ gawk '{
> total = 0
> for (i = 1; i < 4; i++)
> {
> total += $i
> }
> avg = total / 3
> print "Average:",avg
> }' data5
Average: 128.333
Average: 137.667
Average: 176.667
解决办法是使用格式化打印命令:printf
占位符 | 描述 |
---|---|
c | 将一个数作为ASCII字符显示 |
d | 整数 |
i | 整数(跟d一样) |
e | 用科学计数法显示一个数 |
f | 浮点值 |
g | 用科学计数法或浮点数显示(选择较短的格式) |
o | 显示一个八进制值 |
s | 显示一个文本字符串 |
x | 显示一个十六进制值 |
X | 显示一个十六进制值,但用大写字母A~F |
$ gawk 'BEGIN{
> a=10
> b=20
> c=15.234
> d="haha"
printf "%d, %d, %f, %s",a,b,c,d
}'
10, 20, 15.234000, haha
除了控制字母外,还有3种修饰符可以用来进一步控制输出:
$ cat data2
Riley Mullen
123 Main Street
Chicago, IL 60601
(312)555-1234
Frank Williams
456 Oak Street
Indianapolis, IN 46201
(317)555-9876
Haley Snell
4231 Elm Street
Detroit, MI 48201
(313)555-4938
$ gawk 'BEGIN{FS="\n"; RS=""} {print $1,$4}' data2
Riley Mullen (312)555-1234
Frank Williams (317)555-9876
Haley Snell (313)555-4938
可以用printf命令来帮助格式化输出,使得输出信息看起来更美观。将print命令转换成printf命令。
控制第一个字符串的输出宽度为16个字符;不足的空格补齐,并且使用减号使其左对齐。
# 显示字段最小长度20,
$ gawk 'BEGIN{FS="\n"; RS=""} {printf "%-16s %s\n", $1, $4}' data2
Riley Mullen (312)555-1234
Frank Williams (317)555-9876
Haley Snell (313)555-4938
注意,需要在printf命令的末尾手动添加换行符来生成新行。没添加的话,printf命令会继续在同一行打印后续输出。
# 用几个单独的printf命令在同一行上打印多个输出
$ gawk 'BEGIN{FS=","} {printf "%s ", $1} END{printf "\n"}' data1
data11 data21 data31
# 每个printf的输出都会出现在同一行上。为了终止该行,END部分打印了一个换行符
处理浮点值时也非常方便。
gawk 'BEGIN{
> n=123.4567
> printf "%f, %6.2f, %10.3f",n,n,n
> }'
123.456700, 123.46, 123.457
# %6.2f: 宽度为6个字符,保留2个小数
# %10.3f: 宽度为10个字符,保留3个小数
gawk编程语言提供了不少内置函数,可进行一些常见的数学、字符串以及时间函数运算。
函数 | 描述 |
---|---|
atan2(x, y) | x/y的反正切,x和y以弧度为单位 |
cos(x) | x的余弦,x以弧度为单位 |
exp(x) | x的指数函数 |
int(x) | x的整数部分(不会四舍五入) |
log(x) | x的自然对数 |
rand( ) | 比0大比1小的随机浮点值(不包括0或1) |
sin(x) | x的正弦,x以弧度为单位 |
sqrt(x) | x的平方根 |
srand(x) | 为计算随机数指定一个种子值 |
除了标准数学函数外,gawk还支持一些按位操作数据的函数。位操作函数在处理数据中的二进制值时非常有用。
函数 | 描述 |
---|---|
and(v1, v2) | 执行值v1和v2的按位与运算 |
compl(val) | 执行val的补运算 |
lshift(val, count) | 将值val左移count位 |
or(v1, v2) | 执行值v1和v2的按位或运算 |
rshift(val, count) | 将值val右移count位 |
xor(v1, v2) | 执行值v1和v2的按位异或运算 |
函数 | 描述 |
---|---|
asort(s [,d]) | 将数组s按数据元素值排序。索引值会被替换成表示新的排序顺序的连续数字。另外, 如果指定了d,则排序后的数组会存储在数组d中 |
asorti(s [,d]) | 将数组s按索引值排序。生成的数组会将索引值作为数据元素值,用连续数字索引来表 明排序顺序。另外如果指定了d,排序后的数组会存储在数组d中 |
gensub(r, s, h [, t]) | 查找变量$0或目标字符串t(如果提供了的话)来匹配正则表达式r。如果h是一个以g 或G开头的字符串,就用s替换掉匹配的文本。如果h是一个数字,它表示要替换掉第h 处r匹配的地方 |
gsub(r, s [,t]) | 查找变量$0或目标字符串t(如果提供了的话)来匹配正则表达式r。如果找到了,就 全部替换成字符串s |
index(s, t) | 返回字符串t在字符串s中的索引值,如果没找到的话返回0 |
length([s]) | 返回字符串s的长度;如果没有指定的话,返回$0的长度 |
match(s, r [,a]) | 返回字符串s中正则表达式r出现位置的索引。如果指定了数组a,它会存储s中匹配正 则表达式的那部分 |
split(s, a [,r]) | 将s用FS字符或正则表达式r(如果指定了的话)分开放到数组a中。返回字段的总数 |
sprintf(format, variables) | 用提供的format和variables返回一个类似于printf输出的字符串 |
sub(r, s [,t]) | 在变量$0或目标字符串t中查找正则表达式r的匹配。如果找到了,就用字符串s替换 掉第一处匹配 |
substr(s, i [,n]) | 返回s中从索引值i开始的n个字符组成的子字符串。如果未提供n,则返回s剩下的部 分 |
tolower(s) | 将s中的所有字符转换成小写 |
toupper(s) | 将s中的所有字符转换成大写 |
toupper 和 length 函数。
$ gawk 'BEGIN{x = "testing"; print toupper(x); print length(x) }'
TESTING
7
asort 和 asorti 函数基于数据元素值(asort)或索引值(asorti)对数组变量进行排序。
$ gawk 'BEGIN{
> var["a"] = 1
> var["c"] = 2
> var["b"] = 3
> var["d"] = 4
> asorti(var, test)
> for (i in test)
> print "Index:",i," - value:",test[i]
> }'
Index: 4 - value: d
Index: 1 - value: a
Index: 2 - value: b
Index: 3 - value: c
split 函数可以将数据字段放到数组中,新数组使用连续数字作为数组索引,从含有第一个数据字段的索引值1开始。
$ gawk 'BEGIN{ FS=","}{
> split($0, var)
> print var[1], var[5]
> }' data1
data11 data15
data21 data25
data31 data35
函数 | 描述 |
---|---|
mktime(datespec) | 将一个按YYYY MM DD HH MM SS [DST]格式指定的日期转换成时间戳值 |
strftime(format [,timestamp]) | 将当前时间的时间戳或timestamp(如果提供了的话)转化格式化日期(采用shell 函数date()的格式) |
systime( ) | 返回当前时间的时间戳 |
$ gawk 'BEGIN{
> day = strftime("%A, %B %d, %Y", date)
> print day
> }'
Friday, December 26, 2014
要定义自己的函数,必须用function关键字:
function name([variables])
{
statements
}
# 打印记录中的第三个数据字段
function printthird()
{
print $3
}
函数还能用 return 返回值,值可以是变量,或者是最终能计算出值的算式:
function myrand(limit)
{
return int(limit * rand())
}
# 可以将函数的返回值赋给gawk程序中的一个变量
x = myrand(100)
在定义函数时,它必须出现在所有代码块之前(包括BEGIN代码块)。乍一看可能有点怪异, 但它有助于将函数代码与gawk程序的其他部分分开。
$ gawk '
> function myprint()
> {
> printf "%-16s - %s\n", $1, $4
> }
> BEGIN{FS="\n"; RS=""}
> {
> myprint()
> }' data2
Riley Mullen - (312)555-1234
Frank Williams - (317)555-9876
Haley Snell - (313)555-4938
每次使用函数都要重写一遍太麻烦。gawk提供了一种途径来将多个函数放到一个库文件中,这样就能在所有的gawk程序中使用了。
$ cat funclib
function myprint()
{
printf "%-16s - %s\n", $1, $4
}
function myrand(limit)
{
return int(limit * rand())
}
function printthird()
{
print $3
}
funclib文件含有三个函数定义。需要使用-f命令行参数来使用它们。很遗憾,不能将-f命令行参数和内联gawk脚本放到一起使用,不过可以在同一个命令行中使用多个-f参数。
因此,要使用库,只要创建一个含有gawk程序的文件,然后在命令行上同时指定库文件和程序文件就行了。
$ cat script4
BEGIN{ FS="\n"; RS=""}
{
myprint()
}
$ gawk -f funclib -f script4 data2
Riley Mullen - (312)555-1234
Frank Williams - (317)555-9876
Haley Snell - (313)555-4938
# 1、反引号字符(`)
# 2、$()格式
t1=`date`
t2=$(date)
# 赋值等号和命令替换字符之没有空格2、命令分组
$ a=123456
$ echo $abc
$ echo ${a}bc
123456bc
常用日期格式:
%Y YYYY格式的年份(Year)
%m mm格式的月份(),01-12
%d dd格式的日期(day of month),01-31
%H HH格式的小时数(),00-23
%M MM格式的分钟数(),00-59
%S SS格式的秒数(),00-59
%F YYYY-mm-dd格式的完整日期(Full date),同%Y-%m-%d
%T HH-MM-SS格式的时间(Time),同%H:%M:%S
%s 从 1970 年 1 月 1 日 00:00:00 到目前为止的秒数
%w 星期几,0-6,0表示星期天
%u 星期几,1-7,7表示星期天
获取今天时期:`date +%Y%m%d` 或 `date +%F` 或 $(date +%y%m%d)
获取昨天时期:`date -d yesterday +%Y%m%d`
获取前天日期:`date -d -2day +%Y%m%d`
依次类推比如获取10天前的日期:`date -d -10day +%Y%m%d`
或n天前的 `date -d "n days ago" +%y%m%d`
明天:`date -d tomorrow +%y%m%d`
date +%F 时间为 2020-05-07
date '+%Y-%m-%d %H:%M:%S' 时间为 2020-05-07 15:57:24
date ‘+%Y%m%d%H%M%S’ 时间为 20210113101510
date -d "-1 week" +%Y%m%d 获取上周今日的日期 20200430
date -d "1 week" +%Y%m%d 获取下周本日的日期 20200514
date -d "+3 day" +%Y%m%d 获取3天后的日期 20200510
date -d "yesterday" +%Y%m%d 获取昨天的日期 20200506
date -d "-1 day" +%Y%m%d 获取前1天的日期 2020-05-06
date -d "10 days ago" +%Y%m%d 获取当天日期的前10天 20200427
date -d tomorrow +"%Y%m%d" 获取明天的日期 20200508
date +"%Y%m%d" 获取本月的日期 20200507
date +%y%m%d 获取本月的日期 200507
date -d "-1 month" +"%Y%m%d" 获取上月的日期 20200407
date -d "+1 month" +%Y%m%d 获取1个月后的日期 20200607
date -d '2 months ago' +%Y-%m-%d 获取2个月前的日期 2020-03-07
date -d '2 years ago' +%Y-%m-%d 获取2年前的日期 2018-05-07
date -d '2 years' +%Y-%m-%d 获取2天后的日期 2022-05-07
sleep 1 睡眠1秒
sleep 1s 睡眠1秒
sleep 1m 睡眠1分
sleep 1h 睡眠1小时
# 输出重定向
$ command > outputfile
> # 覆盖
>> # 追加
# 输入重定向
$ command < inputfile
# 内联输入重定向
$ command << EOF
> test string 1
> test string 2
> test string 3
> EOF