正则表达式(regular expression)
在正式介绍三个文本处理方法之前,先对正则表达式做一下简单介绍。
正则表达式是你所定义的模式模板(pattern template),Linux工具可以用它来过滤文本。Linux工具(比如sed编辑器或gawk程序)能够在处理数据时使用正则表达式对数据进行模式匹配。如果数据匹配模式,它就会被接受并进一步处理;如果数据不匹配模式,它就会被滤掉。
在Linux中,有两种流行的正则表达式引擎:
POSIX基础正则表达式(basic regular expression,BRE)引擎
POSIX扩展正则表达式(extended regular expression,ERE)引擎
以下是正则表达式的元字符及含义说明:
元字符 | 功能 |
^ | 匹配行首 |
$ | 匹配行尾 |
^$ | 匹配空行 |
. | 匹配任意除换行符外的单个字符,包括0次 |
* | 在字符后面放置星号表示该字符必须在匹配模式的文本种出现0次或多次 |
[ ] | 匹配指定范围内的任意单个字符 |
[^] | 匹配不在指定范围内的任意字符 |
.* | 匹配任意长度任意字符,不包括0次 |
.. | 匹配子串 |
& | 保存搜索字符用来替换其他字符 用&来代替你匹配到的文本 |
\< | 匹配单词的开始,其后任意字符必须作为单词的首部出现 |
\> | 匹配单词的结束,其前任意字符必须作为单词的尾部出现 |
x{m} | 匹配字符x出现的次数,m次 |
x{m,} | 匹配字符x出现的次数,至少m次 |
x{m,n} | 匹配字符x出现的次数,至少m次,不多于n次 |
? | 匹配前面的字符1次或0次 |
+ | 匹配前面的字符可以出现1次或多次,必须至少要出现1次 |
正则表达式识别的特殊字符包括:
. * [ ] ^ $ { } \ + ? | ( )
记住不能在文本模式中单独使用这些字符。
如果要用某个特殊字符作为文本字符,就必须使用反斜线(\)转义。
grep(global regular expression print,全局正则表达式输出)
grep [options] pattern [files]
或
grep [-abcEFGhHilLnqrsvVwxy][-A<显示行数>][-B<显示列数>][-C<显示列数>][-d<进行动作>][-e<范本样式>][-f<范本文件>][--help][范本样式][文件或目录...]
pattern – 表示要查找的字符串或正则表达式。
files – 表示要查找的文件名,可以同时查找多个文件,如果省略 files 参数,则默认从标准输入中读取数据。
options 中常用选项:
-i
:忽略大小写进行匹配。-v
:反向查找,只打印不匹配的行。-n
:显示匹配行的行号。-l
:只打印匹配的文件名。-c
:只打印匹配的行数。- -A<显示行数> 或 –after-context=<显示行数> : 除了显示符合范本样式的那一列之外,并显示该行之后的内容。<>中指定多少行。
- -B<显示行数> 或 –before-context=<显示行数> : 除了显示符合范本样式的那一行之外,并显示该行之前的内容。<>中指定多少行。
- -e<范本样式> 或 –regexp=<范本样式> : 指定字符串做为查找文件内容的样式。
- -E 或 –extended-regexp : 将样式为延伸的正则表达式来使用。
sed(stream editor,流编辑器)
和普通的交互式文本编辑器恰好相反。在交互式文本编辑器中(比如vim),可以用键盘命令来交互式地插入、删除或替换数据中的文本。流编辑器则会在编辑器处理数据之前基于预先提供的一组规则来编辑数据流。(sed会一次从输入中读取一行数据,根据所提供的命令匹配数据,按照命令修改流中的数据,将新的数据输出到STDOUT)
注意,sed
默认不会直接修改源文件数据,而是会将数据复制到缓冲区中,修改也仅在缓冲区中进行。
sed [-hnV][-e<script>][-f<script文件>][文本文件]
参数说明:
- -e<script>或–expression=<script> 以选项中指定的script来处理输入的文本文件。(-e 参数可以省略,其后的命令语句要用单引号括起来)
- -f<script文件>或–file=<script文件> 以选项中指定的script文件来处理输入的文本文件。
- -n 不产生输出,使用print命令来完成输出
-e 参数一般在使用多个编辑器命令时不可省略,两个命令都做用到文件中的每行数据上。命令之间必须用分号隔开,并且在命令末尾和分号之间不能有空格。
sed -e 's/green/brown/;s/dog/cat/' data1.txt
s命令(substitute)
- s :替换,可以直接进行取代的工作。通常这个 s 的动作可以搭配正则表达式!
sed 's/原字符串/替换字符串/' data1.txt
替换命令默认情况下只替换每行出现的第一处。所有必须使用替换标记(substitution flag)。
有4种替换标记:
- 数字:表明新文本将替换第几处模式匹配文本
- g:表明新文本全部替换
- p:表明把修改过的行打印出来 (p常与-n参数连用)-n选项将禁止sed编辑器输出,但p替换标记会输出修改过的行。二者连用效果:只输出被替换命令修改过的行。
- w file:替换的结果写到file文件中 sed编辑器正常的输出是STDOUT,而只有那些包含匹配模式的行才会保存到指定文件中。
sed -n 's/test/trial/p' data2.txt
sed 's/test/trial/w test.txt' data2.txt
替换字符:s///可以等效替换成s!!! s??? 这种模式(自定义分隔符 为避免要替换的文本种包含‘/’符号,会让命令难以阅读)
- 使用地址:将命令作用在特定的某一行或某些行
用行号指定目标行:
sed '2s/dog/cat/' data3.txt #只对第二行修改
sed '2,3s/dog/cat/' data3.txt #对第2行到第3行修改
sed '2,$s/dog/cat/' data3.txt #对第2行到文本的最后一行修改,$表示最后一行
使用文本过滤器找到目标行:
sed '/abc/s/dog/cat/' data4.txt #指定到包含字符'abc'的行,对定位的行进行修改。
命令组合:
如果需要单行(多行:地址区间)上执行多条命令,可以用花括号将命令组合在一起。
sed '2,${
s/fox/elephant/
s/dog/cat/
}' data5.txt
d命令(delete)
d:删除,会删除所有匹配到的行。
sed '3d' data6.txt #删除第3行
sed '2,3d' data6.txt #删除2到3行
sed '3,$d' data6.txt #删除3到最后一行
sed '/munber 1/d' data6.txt #删除匹配到'munber 1'的行
也可以使用两个文本模式来删除某个区域内的行,但这么做一定要小心。你指定的第一个模式会“打开”行删除功能,第二个模式会“关闭”行删除功能。sed编译器会删除两个指定行之间的所有行(包括指定的行)。
sed '/1/,/3/d' data6.txt #匹配到“1”时打开删除功能,匹配到“3”时关闭删除功能,在这之间的行全部都删除。(只要匹配到就会打开,无论多少次)
i命令(插入:insert) & a命令(附加:append)
- 插入(insert) (i)会在指定行前面增加一个新行
- 附加(append)(a)会在指定行后面增加一个新行
sed '3i\This is a insert Line.' data7.txt #在第3行前插入“This is a insert Line.”
sed '3a\This is a append Line.' data7.txt #在第3行后附加“This is a append Line.”
c命令(修改行:change)
- 修改(change)(c)指定某行,修改这一行的内容
sed '3c\This is a changed Line.' data8.txt #第三行的内容修改成了“This is a changed Line.”
sed '/number 3/c\This is a changed Line.' data8.txt #匹配到'number 3'的行被修改成了“This is a changed Line.”
注意:修改命令中如果使用地址区间,会把整个区间里所有行都替换成目标内容
sed '2,3c\This is a new line.' data8.txt #会把第2行第3行,两行内容替换成“This is a new line.”这一行内容,而不是逐一修改
y命令(转换命令transform)
- 转换(transform)(y)是唯一可以处理单个字符的sed编辑器命令
sed 'y/abcde/12345/' data9.txt #会一一映射转换,把'abcde'分别转换成'12345'
转换命令是一个全局命令,也就是说,它会在文本中找到所有指定字符自动进行转换,而不去考虑他们出现的位置。
打印
- p命令用来打印文本
- 等号(=)用来打印行号(在这一行前打印)
- l(小写L)用来列出行(会用\t形式打印出不可显示的字符)
gawk(Unix中原始awk程序的GNU版本)
gawk提供了一种编译语言而不只是编译器命令,在gawk中可以做到:
- 定义变量来保存数据
- 使用算数和字符串操作来处理数据
- 使用结构化编程概念(如if-then语句和循环)来为数据处理增加处理逻辑
- 通过提取数据文件中的数据元素,将其重新排列或格式化,生成格式化报告
基本语法
gawk options program file
构成 gawk 脚本的语句须包含在一对大括号( {} )中,而作为命令选项的整个脚本需要包含在一对引号中:
gawk '{print $0}' test1.txt
gawk的重要特性之一就是处理文本文件中数据的能力。他会自动给每一行的每个数据分配一个变量。默认情况下会将如下变量分配给他在文本行中发现的数据字段:
- $0代表整个文本行
- $1代表文本行中第1个数据字段
- $2代表文本行中第2个数据字段
- $n代表文本行中第n个数据字段
每个数据字段都是通过字段分隔符来划分的。gawk默认的字段分隔符是空白字符(空格或制表符)。可以自己使用-F参数来指定字段分隔符
gawk读取数据和sed类似,都是逐行读取处理。
gawk和sed一样也可以使用-f参数来读取预存在文本里的命令。
BEGIN&END
gawk允许指定脚本何时运行。默认情况下,gawk 从输入中读取一行文本,再对该文本执行程序指令。而有时候需要在读取待处理数据之前先执行某些指令,此时就要用到BEGIN关键字。同样的,END 关键字允许你指定在数据处理完成后才执行的脚本。
gawk 'BEGIN{print"Before the program starts"}
{print $0}
END{print"After the program end"}' data2.txt
内建变量&自建变量
字段分隔符内建变量 | 描述 |
FIELDWIDTHS | 由空格分隔的一列数字,定义了每个数据字段确切的宽度 |
FS | 输入字段分隔符 |
RS | 输入记录分隔符 |
OFS | 输出字段分隔符 |
ORS | 输出记录分隔符 |
gawk 'BEGIN{FS=" "; OFS="--"} {print $1,$2,$3}' data3.txt
数据变量
变量 | 描述 |
ARGC | 当前命令行参数的数目 |
ARGV | 由命令行参数组成的数组 |
CONVFMT | 数字的转换格式,默认值为 %.6 g |
ENVIRON | 包含当前系统shell环境变量的关联数组(字典) |
FILENAME | gawk 处理的输入数据文件的文件名 |
FNR | 当前正在处理的数据文件的 Record 序号(当前处理的数据文件的这行数据的行号) |
IGNORECASE | 设置为非零值时忽略大小写 |
NF | 数据文件中的字段总数 |
NR | 已处理的 Record 总数 (当前处理的这行数据的行号) |
NF、NR、FNR是三个非常好用的变量:
NF变量含有数据文件最后一个字段的数字值。可以在它前面加个美元符号将其作为字段变量使用。
NR、FNR虽然类似,但略有相同。FNR:当前数据文件中已经处理过的记录数;NR:已经处理过的记录总数。
如果在脚本中同时给多个输入文件,FNR单独记录每个文件,NR会记录所有文件的累计。
自定义变量
gawk可以自定义变量在程序中使用,变量名可是数字、字母、下划线,但不能以数字开头,区分大小写。
在变量定义的时候不需要任何标识符、在变量引用的时候不需要任何标识符。
gawk还可以在命令行上给自定义的变量赋值:
cat script1
BEGIN{FS=","}
{print $n}
gawk -f scrpit1 n=2 data4.txt #在scrpit脚本中定义变量n;在命令行上给参数n赋值“2”
gawk -v n=2 -f scrpit1 data4.txt #增加 -v 参数
上面 在脚本中定义变量,在命令行赋值的方法,在BEGIN部分不能使用定义的参数“n”。在命令行上的脚本代码(script1)之前加一个 -v 即可解决
gawk的基础用法部分介绍到这里就结束了。gawk被定义为一种编译语言,他还有定义数组、定义关联数组、正则表达式、匹配操作符、结构化命令的if语句、while语句、do-while语句、for语句、内建函数、自定义函数等高级方法。这些用法在其它语言中一定都学习过,这里就不过多赘述了。虽然不介绍,但是gawk搭配结构化语法(例如数组、循环等)可以轻松快速的实现一些需求。在服务器的日常操作是非常便捷的。
tips:这里分享一个非常实用的gawk命令搭配数组、循环体,实现的文本转置功能。为大家打开思路
gawk 'BEGIN{FS="\t";OFS="\t"} {for(i=1;i<=NF;i++){if(NR==1){a[i]=$i;}else{a[i]=a[i]"\t"$i;}}} END{for(i=1;a[i]!="";i++){print a[i];}}' input.txt
#\t是 指定的分隔符
为方便阅读,把代码按结构换行:
gawk 'BEGIN{FS="\t";OFS="\t"}
{for(i=1;i<=NF;i++){
if(NR==1){a[i]=$i;}
else{a[i]=a[i]"\t"$i;}
}
}
END{for(i=1;a[i]!="";i++){print a[i];}}' input.txt
#\t是 指定的分隔符