sed与awk命令稍复杂,都用于文本处理,同时也是Linuxer必备技能,因此单独拎了出来。此外,提到这两个命令,不得不提正则表达式,因此也做了简单介绍。
正则表达式
Linux使用POSIX标准描述的正则表达式,关于POSIX标准可以自行百度。它跟其他语言的正则语法基本是想通的,因此如果你有其他语言的正则基础,简单看下Linux的基本正则和扩展正则的区别,还有一些常用字符集(字母、数字等)的表示形式就OK。
POSIX 把正则表达式的实现分成了两类: 基本正则表达式(BRE)和扩展的正则表达式(ERE)。
BRE 和 ERE 之间有什么区别呢?这是关于元字符的问题。BRE 可以辨别以下元字符:
1 | ^ $ . [ ] * |
其它的所有字符被认为是文本字符。ERE 添加了以下元字符(以及与其相关的功能):
1 | ( ) { } ? + | |
然而,在 BRE 中,字符“(”,“)”,“{”,和 “}”用反斜杠转义后,被看作是元字符; 相反在 ERE 中,在任意元字符之前加上反斜杠会导致其被看作是一个文本字符。
grep、sed命令默认用BRE,awk默认用ERE。grep用-E指定使用ERE,sed用-r指定用ERE。
元字符 | 说明 |
---|---|
. | 匹配任意字符 |
^ | 匹配行首 |
$ | 匹配行尾 |
* | 限定符,匹配之前元素零个或多个 |
? | 限定符,匹配之前元素零个或一个 |
+ | 限定符,匹配之前元素一个或多个 |
| | 或 |
[] | 匹配中括号内字符集中的一个,中括号中^表示否定,-表示范围(放在开头表示本身) |
{} | 限定符,匹配特定个数的元素,具体用法见下表 |
() | 子表达式,可对整个子表达式使用数量限定符,\n表示第n个子表达式匹配的内容 |
{}用法:
限定符 | 意思 |
---|---|
{n} | 匹配前面的元素,如果它确切地出现了 n 次。 |
{n,m} | 匹配前面的元素,如果它至少出现了 n 次,但是不多于 m 次。 |
{n,} | 匹配前面的元素,如果它出现了 n 次或多于 n 次。 |
{,m} | 匹配前面的元素,如果它出现的次数不多于 m 次。 |
POSIX字符集:
字符集 | 说明 |
---|---|
[:alnum:] | 字母数字字符。在 ASCII 中,等价于:[A-Za-z0-9] |
[:word:] | 与[:alnum:]相同, 但增加了下划线字符。 |
[:alpha:] | 字母字符。在 ASCII 中,等价于:[A-Za-z] |
[:blank:] | 包含空格和 tab 字符。 |
[:cntrl:] | ASCII 的控制码。包含了0到31,和127的 ASCII 字符。 |
[:digit:] | 数字0到9 |
[:graph:] |
可视字符。在 ASCII 中,它包含33到126的字符。 |
[:lower:] | 小写字母。 |
[:punct:] | 标点符号字符。在 ASCII 中,等价于: |
[:print:] | 可打印的字符。在[:graph:] 中的所有字符,再加上空格字符。 |
[:space:] | 空白字符,包括空格,tab,回车,换行,vertical tab, 和 form feed.在 ASCII 中, 等价于:[ \t\r\n\v\f] |
[:upper:] | 大写字母。 |
[:xdigit:] | 用来表示十六进制数字的字符。在 ASCII 中,等价于:[0-9A-Fa-f] |
sed命令
简介
名字 sed 是 stream editor(流编辑器)的简称。它对文本流进行编辑,可以是一系列指定的文件, 也可以是标准输入。sed 是一款强大的,并且有些复杂的程序(有整本内容都是关于 sed 程序的书籍), 所以在这里我们不会详尽的讨论它。
总之,sed 的工作方式可以给出单个编辑命令(在命令行中),也可以是包含多个命令的脚本文件名, 然后它就按行来执行这些命令。
示例
这里有一个非常简单的 sed 实例:
1 | [me@linuxbox ~]$ echo "front" | sed 's/front/back/' |
在这个例子中,我们使用 echo 命令产生了一个单词的文本流,然后把它管道给 sed 命令。sed,依次, 对流文本执行指令 s/front/back/,随后输出“back”。我们也能够把这个命令认为是相似于 vi 中的“替换” (查找和替代)命令。
sed 中的命令开始于单个字符。在上面的例子中,这个替换命令由字母 s 来代表,其后跟着查找 和替代字符串,斜杠字符做为分隔符。分隔符的选择是随意的。按照惯例,经常使用斜杠字符, 但是 sed 将会接受紧随命令之后的任意字符做为分隔符。我们可以按照这种方式来执行相同的命令:
1 | [me@linuxbox ~]$ echo "front" | sed 's_front_back_' |
通过紧跟命令之后使用下划线字符,则它变成界定符。sed 可以设置界定符的能力,使命令的可读性更强, 正如我们将看到的.
sed 中的大多数命令之前都会带有一个地址,其指定了输入流中要被编辑的文本行。如果省略了地址, 然后会对输入流的每一行执行编辑命令。最简单的地址形式是一个行号。我们能够添加一个地址 到我们例子中:
1 | [me@linuxbox ~]$ echo "front" | sed '1s/front/back/' |
给我们的命令添加地址 1,就导致只对仅有一行文本的输入流的第一行执行替换操作。如果我们指定另一 个数字:
1 | [me@linuxbox ~]$ echo "front" | sed '2s/front/back/' |
我们看到没有执行这个编辑命令,因为我们的输入流没有第二行。
地址表示法
地址可以用许多方式来表达。这里是 最常用的:
地址 | 说明 |
---|---|
n | 行号,n 是一个正整数。 |
$ | 最后一行。 |
/regexp/ | 所有匹配一个 POSIX 基本正则表达式的文本行。注意正则表达式通过 斜杠字符界定。选择性地,这个正则表达式可能由一个备用字符界定,通过\cregexpc 来 指定表达式,这里 c 就是一个备用的字符。 |
addr1,addr2 | 从 addr1 到 addr2 范围内的文本行,包含地址 addr2 在内。地址可能是上述任意 单独的地址形式。 |
first~step | 匹配由数字 first 代表的文本行,然后随后的每个在 step 间隔处的文本行。例如 1 |
addr1,+n | 匹配地址 addr1 和随后的 n 个文本行。 |
addr! | 匹配所有的文本行,除了 addr 之外,addr 可能是上述任意的地址形式。 |
编辑命令
下面是sed基本编辑命令列表:
命令 | 说明 |
---|---|
= | 输出当前的行号。 |
a | 在当前行之后追加文本。 |
d | 删除当前行。 |
i | 在当前行之前插入文本。 |
p | 打印当前行。默认情况下,sed 程序打印每一行,并且只是编辑文件中匹配 指定地址的文本行。通过指定-n 选项,这个默认的行为能够被忽略。 |
q | 退出 sed,不再处理更多的文本行。如果不指定-n 选项,输出当前行。 |
Q | 退出 sed,不再处理更多的文本行。 |
s/regexp/replacement/ | 只要找到一个 regexp 匹配项,就替换为 replacement 的内容。 replacement 可能包括特殊字符 &,其等价于由 regexp 匹配的文本。另外, replacement 可能包含序列 \1到 \9,其是 regexp 中相对应的子表达式的内容。另一个最重要的可选标志是 g 标志,在末尾分隔符后使用,其指示 sed 对某个文本行全范围地执行查找和替代操作,不仅仅是对第一个实例 |
y/set1/set2 | 执行字符转写操作,通过把 set1 中的字符转变为相对应的 set2 中的字符。 注意不同于 tr 程序,sed 要求两个字符集合具有相同的长度。 |
常用选项
-f file选项,可以在一个脚本文件中构建更加复杂的命令。
-i选项可以直接将编辑结果更新到文件。
-r选项指定使用扩展正则表达式。
引用shell变量
sed一般使用单引号,sed引用shell变量时使用双引号即可
1 | pattern1=XXX |
awk命令
简介
awk是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处,灵活性是awk最大的优势。
版本介绍
awk:最初在1 9 7 7年完成,1 9 8 5年发表了一个新版本的awk,它的功能比旧版本增强了不少,awk 能够用很短的程序对文档里的资料做修改、比较、提取、打印等处理,如果使用C 或P a s c a l 等语言编写程序完成上述的任务会十分不方便而且很花费时间,所写的程序也会很大;
nawk: 在 20 世纪 80 年代中期,对 awk语言进行了更新,并不同程度地使用一种称为 nawk(new awk) 的增强版本对其进行了替换。许多系统中仍然存在着旧的awk 解释器,但通常将其安装为 oawk (old awk) 命令,而 nawk 解释器则安装为主要的 awk 命令,也可以使用 nawk 命令。Dr. Kernighan 仍然在对 nawk 进行维护,与 gawk 一样,它也是开放源代码的,并且可以免费获得;
mawk:mawk 是 awk 编程语言的解释器。awk语言在多媒体数据文件以及文本的检索和处理,算法的原型设计和试验都有广泛的使用。mawk带给awk新的概念,它实现了在 《The AWK Programming Language》(Aho, Kernighan and Weinberger, The AWK Programming Language, Addison-Wesley Publishing, 1988.被认为是 AWK 手册。)中定义的 awk语言。mawk遵循 POSIX 1003.2 (草案 11.3)定义的 AWK 语言,包含了一些没有在AWK 手册中提到的特色,同时 mawk 提供一小部分扩展,另外据说mawk是实现最快的awk;
gawk: 是 GNU Project 的awk解释器的开放源代码实现。尽管早期的 GAWK 发行版是旧的 AWK 的替代程序,但不断地对其进行了更新,以包含 NAWK 的特性;
目前,大家都比较倾向于使用awk和gawk,本文中要介绍的awk是以GUN的gawk为例的。Ubuntu系统中的各种awk的选项设置,可以通过sudo update-alternatives –config awk来完成,实际上你通过手动修改软链接也能实现。Debian最小化安装的时候awk的链接是指向mawk的,Ubuntu的awk命令默认使用mawk,但是不支持[[:alpha:]]这种表示和{}数量限定符语法。
语法
1 | awk [opion] 'awk_script' input_file1 [input_file2 ...] |
awk的常用选项option有:
① -F fs : 使用fs作为输入记录的字段分隔符,多个分隔符设置使用’[]’包含进去,如,-f ‘[ ,]’ 即空格和逗号
② -f filename : 从文件filename中读取awk_script
③ -v var=value : 为awk_script设置变量
awk有三种运行方式:
第一种,把awk的脚本命令直接放在命令中。
第二种,把awk的所有的脚本命令放在一个脚本文件中,然后用-f选项来指定要运行的脚本命令文件。
第三种,将awk_script放入脚本文件并以 #!/bin/awk -f 作为首行,给予该脚本可执行权限,然后在shell下通过键入该脚本的脚本名调用之。
awk脚本:
awk_script可以由一条或多条awk_cmd组成,对于多个awk_cmd,一个awk_cmd完成后,应该另起一行,以便进行分隔。
awk_cmd由两部分组成: awk_pattern { actions },即模式和操作。
模式可以是以下任意一个:
/正则表达式/:使用通配符的扩展集。
关系表达式:使用运算符进行操作,可以是字符串或数字的比较测试。
模式匹配表达式:用运算符(匹配)和!(不匹配)。
操作由一个或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于大括号内,主要部分是:
变量或数组赋值
输出命令
内置函数
控制流语句
awk命令的一般形式:
1 | awk ' BEGIN { actions } |
其中 BEGIN { actions } 和 END { actions } 是可选的。
工作原理
awk脚本的运行过程:
① 如果BEGIN 区块存在,awk执行它指定的actions。
② awk从输入文件中读取一行,称为一条输入记录。(如果输入文件省略,将从标准输入读取)
③ awk将读入的记录分割成字段,将第1个字段放入变量$1中,第2个字段放入$2,以此类推。$0表示整条记录。字段分隔符使用shell环境变量IFS或由参数指定。
④ 把当前输入记录依次与每一个awk_cmd中awk_pattern比较,看是否匹配,如果相匹配,就执行对应的actions。如果不匹配,就跳过对应的actions,直到比较完所有的awk_cmd。
⑤ 当一条输入记录比较了所有的awk_cmd后,awk读取输入的下一行,继续重复步骤③和④,这个过程一直持续,直到awk读取到文件尾。
⑥ 当awk读完所有的输入行后,如果存在END,就执行相应的actions。
说明:
1)input_file可以是多于一个文件的文件列表,awk将按顺序处理列表中的每个文件。
2)一条awk_cmd的awk_pattern可以省略,省略时不对输入记录进行匹配比较就执行相应的actions。一条awk_cmd的actions 也可以省略,省略时默认的动作为打印当前输入记录,即{print $0} 。一条awk_cmd中的awk_pattern和actions不能同时省略。
3) BEGIN区块和END区块别位于awk_script的开头和结尾。awk_script中只有END区块或者只有BEGIN区块是被允许的。如果awk_script中只有BEGIN { actions } ,awk不会读取input_file。
4) awk把输入文件的数据读入内存,然后操作内存中的输入数据副本,awk不会修改输入文件的内容。
5) awk的总是输出到标准输出,如果想让awk输出到文件,可以使用重定向。
内置变量
变量 | 描述 |
---|---|
$n | 当前记录的第n个字段,字段间由FS分隔 |
$0 | 完整的输入记录 |
ARGC | 命令行参数的数目 |
ARGIND | 命令行中当前文件的位置(从0开始算) |
ARGV | 包含命令行参数的数组 |
CONVFMT | 数字转换格式(默认值为%.6g)ENVIRON环境变量关联数组 |
ERRNO | 最后一个系统错误的描述 |
FIELDWIDTHS | 字段宽度列表(用空格键分隔) |
FILENAME | 当前文件名 |
FNR | 各文件分别计数的行号 |
FS | 字段分隔符(默认是任何空格) |
IGNORECASE | 如果为真,则进行忽略大小写的匹配 |
NF | 当前记录中的字段个数,就是有多少列 |
NR | 已经读出的记录数,就是行号,从1开始 |
OFMT | 数字的输出格式(默认值是%.6g) |
OFS | 输出记录分隔符(输出换行符),输出时用指定的符号代替换行符 |
ORS | 输出记录分隔符(默认值是一个换行符) |
RLENGTH | 由match函数所匹配的字符串的长度 |
RS | 记录分隔符(默认是一个换行符) |
RSTART | 由match函数所匹配的字符串的第一个位置 |
SUBSEP | 数组下标分隔符(默认值是/034) |
运算符
运算符 | 描述 |
---|---|
= += -= = /= %= ^= *= | 赋值 |
?: | C条件表达式 |
|| | 逻辑或 |
&& | 逻辑与 |
~ ~! | 匹配正则表达式和不匹配正则表达式 |
< <= > >= != == | 关系运算符 |
空格 | 连接 |
+ - | 加,减 |
* / % | 乘,除与求余 |
+ - ! | 一元加,减和逻辑非 |
^ *** | 求幂 |
++ – | 增加或减少,作为前缀或后缀 |
$ | 字段引用 |
in | 数组成员 |
内建函数
算术函数:
函数名 | 说明 |
---|---|
atan2( y, x ) | 返回 y/x 的反正切。 |
cos( x ) | 返回 x 的余弦;x 是弧度。 |
sin( x ) | 返回 x 的正弦;x 是弧度。 |
exp( x ) | 返回 x 幂函数。 |
log( x ) | 返回 x 的自然对数。 |
sqrt( x ) | 返回 x 平方根。 |
int( x ) | 返回 x 的截断至整数的值。 |
rand( ) | 返回任意数字 n,其中 0 <= n < 1。 |
srand( [Expr] ) | 将 rand 函数的种子值设置为 Expr 参数的值,或如果省略 Expr 参数则使用某天的时间。返回先前的种子值。 |
字符串函数:
函数 | 说明 |
---|---|
gsub( Ere, Repl, [ In ] ) | 除了正则表达式所有具体值被替代这点,它和 sub 函数完全一样地执行,。 |
sub( Ere, Repl, [ In ] ) | 用 Repl 参数指定的字符串替换 In 参数指定的字符串中的由 Ere 参数指定的扩展正则表达式的第一个具体值。sub 函数返回替换的数量。出现在 Repl 参数指定的字符串中的 &(和符号)由 In 参数指定的与 Ere 参数的指定的扩展正则表达式匹配的字符串替换。如果未指定 In 参数,缺省值是整个记录($0 记录变量)。 |
index( String1, String2 ) | 在由 String1 参数指定的字符串(其中有出现 String2 指定的参数)中,返回位置,从 1 开始编号。如果 String2 参数不在 String1 参数中出现,则返回 0(零)。 |
length [(String)] | 返回 String 参数指定的字符串的长度(字符形式)。如果未给出 String 参数,则返回整个记录的长度($0 记录变量)。 |
blength [(String)] | 返回 String 参数指定的字符串的长度(以字节为单位)。如果未给出 String 参数,则返回整个记录的长度($0 记录变量)。 |
substr( String, M, [ N ] ) | 返回具有 N 参数指定的字符数量子串。子串从 String 参数指定的字符串取得,其字符以 M 参数指定的位置开始。M 参数指定为将 String 参数中的第一个字符作为编号 1。如果未指定 N 参数,则子串的长度将是 M 参数指定的位置到 String 参数的末尾 的长度。 |
match( String, Ere ) | 在 String 参数指定的字符串(Ere 参数指定的扩展正则表达式出现在其中)中返回位置(字符形式),从 1 开始编号,或如果 Ere 参数不出现,则返回 0(零)。RSTART 特殊变量设置为返回值。RLENGTH 特殊变量设置为匹配的字符串的长度,或如果未找到任何匹配,则设置为 -1(负一)。 |
split( String, A, [Ere] ) | 将 String 参数指定的参数分割为数组元素 A[1], A[2], . . ., A[n],并返回 n 变量的值。此分隔可以通过 Ere 参数指定的扩展正则表达式进行,或用当前字段分隔符(FS 特殊变量)来进行(如果没有给出 Ere 参数)。除非上下文指明特定的元素还应具有一个数字值,否则 A 数组中的元素用字符串值来创建。 |
tolower( String ) | 返回 String 参数指定的字符串,字符串中每个大写字符将更改为小写。大写和小写的映射由当前语言环境的 LC_CTYPE 范畴定义。 |
toupper( String ) | 返回 String 参数指定的字符串,字符串中每个小写字符将更改为大写。大写和小写的映射由当前语言环境的 LC_CTYPE 范畴定义。 |
sprintf(Format, Expr, Expr, . . . ) | 根据 Format 参数指定的printf子例程格式字符串来格式化 Expr 参数指定的表达式并返回最后生成的字符串。 |
一般函数:
函数 | 说明 |
---|---|
close( Expression ) | 用同一个带字符串值的 Expression 参数来关闭由 print 或 printf 语句打开的或调用 getline 函数打开的文件或管道。如果文件或管道成功关闭,则返回 0;其它情况下返回非零值。如果打算写一个文件,并稍后在同一个程序中读取文件,则 close 语句是必需的。 |
system(Command ) | 执行 Command 参数指定的命令,并返回退出状态。等同于system子例程。 |
Expression | getline [ Variable ] | 从来自 Expression 参数指定的命令的输出中通过管道传送的流中读取一个输入记录,并将该记录的值指定给 Variable 参数指定的变量。如果当前未打开将 Expression 参数的值作为其命令名称的流,则创建流。创建的流等同于调用popen子例程,此时 Command 参数取 Expression 参数的值且 Mode 参数设置为一个是 r 的值。只要流保留打开且 Expression 参数求得同一个字符串,则对 getline 函数的每次后续调用读取另一个记录。如果未指定 Variable 参数,则 $0 记录变量和 NF 特殊变量设置为从流读取的记录。 |
getline [ Variable ] < Expression | 从 Expression 参数指定的文件读取输入的下一个记录,并将 Variable 参数指定的变量设置为该记录的值。只要流保留打开且 Expression 参数对同一个字符串求值,则对 getline 函数的每次后续调用读取另一个记录。如果未指定 Variable 参数,则 $0 记录变量和 NF 特殊变量设置为从流读取的记录。 |
getline [ Variable ] | 将 Variable 参数指定的变量设置为从当前输入文件读取的下一个输入记录。如果未指定 Variable 参数,则 $0 记录变量设置为该记录的值,还将设置 NF、NR 和 FNR 特殊变量。 |
时间函数:
函数名 | 说明 |
---|---|
mktime( YYYY MM DD HH MM SS[ DST]) | 生成时间格式 |
strftime([format [, timestamp]]) | 格式化时间输出,将时间戳转为时间字符串 具体格式,见下表. |
systime() | 得到时间戳,返回从1970年1月1日开始到当前时间(不计闰年)的整秒数 |
控制语句
包括if、for、while、do while语句,语法跟C语言一致,不再贴出来了。
其他语句:
break 当 break 语句用于 while 或 for 语句时,导致退出程序循环。
continue 当 continue 语句用于 while 或 for 语句时,使程序循环移动到下一个迭代。
next 能能够导致读入下一个输入行,并返回到脚本的顶部。这可以避免对当前输入行执行其他的操作过程。
exit 语句使主输入循环退出并将控制转移到END,如果END存在的话。如果没有定义END规则,或在END中应用exit语句,则终止脚本的执行。
数组
数组是awk的灵魂,处理文本中最不能少的就是它的数组处理。因为数组索引(下标)可以是数字和字符串在awk中数组叫做关联数组(associative arrays)。awk 中的数组不必提前声明,也不必声明大小。数组元素用0或空字符串来初始化,这根据上下文而定。
数组定义
1 一维数组
a) 数字下标
array[1]=”it”
array[2]=”homer”
array[3]=”sunboy”
array[4]=2050
b) 字符下标
array[“first”]=”yang”
array[“second”]=”gang”
array[“third”]=”sunboy”
2 二维数组
awk 多维数组在本质上是一维数组,因awk在存储上并不支持多维数组,awk提供了逻辑上模拟二维数组的访问方式。例如,array[2,3] = 1这样的访问是允许的。
awk使用一个特殊的字符串SUBSEP (\034)作为分割字段,在上面的例子 array[2,3] = 1 中,关联数组array存储的键值实际上是2\0343,2和3分别为下标(2,3),\034为SUBSEP分隔符
类似一维数组的成员测试,多维数组可以使用 if ( (i,j) in array) 语法,但是下标必须放置在圆括号中。
类似一维数组的循环访问,多维数组使用 for ( item in array ) 语法遍历数组。与一维数组不同的是,多维数组必须使用split()函数来访问单独的下标分量,格式: split ( item, subscr, SUBSEP), 例如: split (item, array2, SUBSEP); 后,array2[1]为下标“2”, array2[2]为下标“3”
示例:
1 | 1. awk 'BEGIN{ |
注: 示例中 split(i, array2, SUBSEP); 即是把二维数组作为一维数组处理,同样数组元素顺序不确定,下面将介绍数组排序
数组函数
1) 数组长度(length)
length(array) 获取数组长度, split 分割数组也返回数组长度,示例:
2) 数组排序(asort)
asort对数组array按照首字母进行排序,返回数组长度;
如果要得到数组原本顺序,需要使用数组下标依次访问;
for…in 输出关联数组的顺序是无序的,所以通过for…in 得到是无序的数组。如果需要得到有序数组,需要通过下标获得
3) 键值操作
a 查找键值(in)
1 | awk 'BEGIN{array["a"]="aaa"; array["b"]="bbb"; if("c" in array){print "found";}else{print "not found"}; for(k in array){print k, array[k];}}' |
结果:
not found
a aaa
b bbb
注: 没有引用array下标“c”,因此没有添加到数组中
b 删除键值(delete)
1 | awk 'BEGIN{array["a"]="aaa"; array["b"]="bbb"; delete array["a"]; for(k in array){print k, array[k];}}' |
结果: b bbb
引用shell变量
第一种:双引号+单引号
1 | line=XXX |
第二种:使用-v var=value
1 | awk -v a=$second -v b=$count '$2==a{sum += $1};END {print sum/b}' filename |
练习
参考CSDN博客中的一个小练习:http://blog.csdn.net/monkey_d_meng/article/details/5924357
参考文献
[1]、《The Linux Command Line》
[2]、 http://man.linuxde.net/awk
[3]、《鸟哥的Linux私房菜》