分享

文本处理利器sed与awk使用总结

 justinnn 2017-02-23

来自:桃子的博客铭

链接:https:///201612/cmd-tools-sed-awk.html(点击尾部阅读原文前往)


做的这个笔记,是在放暑假前从图书馆借的那本蓝皮《Unix Shells by Example》,暑假过程中自己折腾的产物,其实当时根本不知道学习这个有什么用。sed、awk当然不会用来单独开发程序,但是在命令行处中理文本,字段切割,分析日志,写一些shell脚本(比如数据库patch)的时候,还有就是在自动化做软件、服务配置文件更新的时候,还是非常好用的。


一、sed - stream editor for filtering and transforming text


sed的操作格式


~ sed -opts ‘command’ filename(s)

~ … | sed -opts ‘command’ # 管道输入


sed的opts选项


-n 取消默认打印。默认情况下会全文打印,因此会重复打印匹配的记录,而这个选项用来只打印匹配记录


-e 多个command可以用这个连接,比如 sed -n -e ‘cmd1’ -e ‘cmd2’ files

-i 使sed的编辑保存。默认sed操作没有破坏性(不会对原文件修改),但是确实要编辑原文件时候可以加上该参数


这里实验使用《UNIX shell by example》里面的datafile文件作为数据集。



1.1 删除 d


注意,和通常的法则不一样,下面的行号都是从1开始计数的。


(a) ? ~ sed ‘3d’ datafile


删除第3行,其余行打印到屏幕上。再次强调sed默认没有破坏性,原文件datafile没有改变。


(b) ? ~ sed ‘3,$d’ datafile


删除第3行到文件的末尾记录,只打印剩余的第1,2两行。$代表文件或者记录的最后一行


(c) ? ~ sed ‘$d’ datafile 


删除最后一行。


(d) ? ~ sed ‘/north/d’ datafile


删除匹配north的行,其余记录都被打印。


1.2 替换 s


标志g表示是对行内所有匹配记录都进行替换,否则只对行内首次出现的记录进行替换


(a) ? ~ sed -n ‘s/^west/north/p’ datafile


替换所有west打头记录,这里用了-n选项和p命令,只打印替换的记录


(b) ? ~ sed ‘s/[0-9][0-9]$/&.5/‘ datafile


符号$表示行末尾,符号&在替换中用于表示前面匹配的内容,上面表示行结尾为2位数的数字,添加.5成为xx.5。如果要用到&的字面意思,需要使用\&转意方可。


~ sed -n ‘s/[0-9][0-9]$/&.5\&/p’ datafile


(c) 标记符号(),可以复用前面的内容

 ~ sed -n 's/\(Mar\)go\(t\)/\1ianne\2/p' datafile   north           NO      Mariannet Weber            4.5     .89     5        9    ~

这里\1复用了前面标记的Mar,而\2复用了t。


(d) 默认的替换分隔符是/,其实sed可以使用任何分隔符,就是紧跟着s的那个符号。使用自定义的符号对于操作日期、路径等特殊记录很有效



1.3 行范围 ,


行范围的表示是双闭合的,就是包含匹配开始的行、匹配结束的行,以及两者之间的所有行: 5,10 /Dick/,/Joe/ /north/,$



这里将从south匹配行,到第一个theast匹配行结束,将每条匹配记录的$结尾替换(实际追加)成TTT。如果出现south,但是没有出现theast,就默认匹配到文件结尾的地方


1.4 多次编辑 -e


~ sed -e ‘1,3d’ -e ‘s/north/NORTH/‘ datafile


多个-e其操作是依次进行的,所以顺序还是有讲究的,不同的顺序对最终的结果可能会有影响



1.5 文件操作


(a) 读文件 r


将一个文件的内容加到当前的位置上实际就是在所有匹配行的下面插入文件内容

 ~ echo '---newfile info---' > newfile   ?  ~ sed '/west/r newfile' datafile       northwest       NW      Charles Main  3.0     .98     3       34   ---newfile info---   western         WE      Sharon Gray   5.3     .97     5       23   ---newfile info---   southwest       SW      Lewis Dalsass 2.7     .8      2       18   ---newfile info---   southern        SO      Suan Chin     5.1     .95     4       15   ...

(b) 写文件 w


把所有匹配到的记录都写入到指定的文件当中

~ sed ‘/south/w newfile1’ datafile


(c) 追加文本 a


该命令是在匹配的记录后面直接追加提供的文本内容,感觉追加的内容有strip的效果,开始的空字符会被删除


~ sed ‘/south/a This is the message’ datafile


(d) 插入文本 i


跟上面的a命令类似,只不过这个i是插在匹配记录的前面

 ~ sed '/western/i   - This is the message' datafile   northwest       NW      Charles Main  3.0     .98     3       34   - This is the message   western         WE      Sharon Gray   5.3     .97     5       23southwest       SW      Lewis Dalsass 2.7     .8      2       18   ...

(e) 替换文本 c


用该字符替换匹配到的整个记录行


~ sed ‘/north/c This is the message’ datafile 


(f) 下一行 n


对匹配到的记录,对其下一行做某些相应的操作

 ~ sed '/northwest/{n; s/^\<[a-z]* xttzzf/;=""> datafile    northwest       NW      Charles Main  3.0     .98     3       34  XXTTZZF         WE      Sharon Gray 5.3     .97     5       23  southwest       SW      Lewis Dalsas2.7     .8      2       18   ...

(g) 替换、转换操作 y


下面对第1,2两行记录中的字符做大写化转换

 ~ sed '1,2y/abcdefghigklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' datafile   NORTHWEST     NW      CHARLES MAIN    3.0     .98     3       34 WESTERN         WE      SHARON GRAY  5.3     .97     5       23   southwest     SW      Lewis Dalsass2.7     .8      2       18   ...

(h) 退出


直接退出,不做接下来记录的继续处理


~ sed ‘5q’ datafile 

~ sed ‘/north/{ s/north/NORTH/; q; }’ datafile


1.6 sed正则表达式的元字符


Linux平台下很多工具(sed、awk、bash、python…),其正则表达式虽然在大体上约定类似,但是很多细节方面的东西还是有所差异,使用不确定最好事先查询一下!

^         行首定位符   $         行尾定位符   .         匹配除换行之外的单个字符   *         匹配0个或者多个前导字符(这里是前导字符0或者多个,任意一个或多个字符,使用 .* )   []        指定字符中的任意一个字符,比如[Ll] [a-z]   [^]       上面一样,不匹配的字符   \(..\)    保存匹配的字符,后面使用\1 \2..来引用,最多使用9个标签   &         保存查找匹配到的串,可以用在后面的替换中 s/love/**&**/   \<  =""  =""  =""  词首定位符,单词的开头=""  ="" \="">        词尾定位符   x\{m\}    连续m个   x\{m,\}   至少m个   x\{m,n\}  m-n个,sed -n 's/[0-9]\{2\}$/&.5/p' datafile

二、awk - pattern scanning and text processing language


注意,下面的操作都是针对gawk的。刚开始发现下面有些例程走不通,原来是Ubuntu上面默认装的个mawk,换成gawk就正常了,话说这个awk就那三个人发明的么,居然还有版本差异?


awk比上面的sed要复杂的多,支持运算符、逻辑判断、循环等基本操作,难怪在解释上面已经定位为一个language了。

awk的操作格式


~ awk -opts ‘cmd’ filenames

~ … | awk -opts ‘cmd’ # 管道输入


下面的实验操作默认使用《UNIX shell by example》里面的employees文件,也有很多是沿用上面的datafile。

 ~ cat employees   Tom Jones       4424      5/12/66     543354  
Mary Adams      5346      11/4/63     28765  
Sally Chang     1654      7/22/54     650000  
Billy Black     1683      9/23/44     336500    ~

2.1 简介


awk将输入的每行作为一条记录,默认的行分隔符就是换行符。$0指代每行的整条记录,而NR将每条记录所对应的行号保存在其中。


~ awk ‘{print NR,$0}’ employees 


每条记录都是由多个字段组成的,这些字段的分隔符默认是空白字符(空格或者制表符,如果想要改变这个默认设置,可以使用-F这个参数),每条字段都是用1,1,2…开始标号表示,而每行的字段个数保存在NF这个特殊的字段当中。

 ~ awk -F: '{print NR, $1,$7}' /etc/passwd    1 root /bin/bash   2 daemon /usr/sbin/nologin   3 bin /usr/sbin/nologin   ...

2.2 模式和操作


awk的行为可以是花括号包起来的多个操作:


{action1; action2; … ;}


默认的模式匹配中就已经包含了if的意思了,表明当该模式满足时候就进行操作,如果没指定默认操作就是打印出这些满足要求的整行记录

 ~ awk '/Tom/' employees   Tom Jones       4424      5/12/66     543354  
 ~ awk '$3<> employees   Sally Chang     1654      7/22/54     650000  
Billy Black     1683      9/23/44     336500  
 ~ awk '$0 ~ /Tom/{print}' employees   Tom Jones       4424      5/12/66     543354  
 ~ awk '$0 ~ /Tom/{print 'NAMEINFO->' $1, '~'  ,$2}' employeesNAMEINFO->Tom ~ Jones   ?  ~

2.3 模式匹配


(a) 整行所有字段匹配

 ~ awk '/[TM]/' employees   Tom Jones       4424      5/12/66     543354  
Mary Adams      5346      11/4/63     28765  
 ~ awk '/To|Mar/' employees   Tom Jones       4424      5/12/66     543354  
Mary Adams      5346      11/4/63     28765  
 ~

(b) ~ 特定字段匹配操作符,而使用符号!可以表示取反的意思

 ~ awk '$2 ~ /Ch/ {print NR,$1,$2}' employees     3 Sally Chang   ?  ~ awk '$2 !~ /Ch/ {print NR,$1,$2}' employees     1 Tom Jones   2 Mary Adams   4 Billy Black   ?  ~ awk ' $4 ~ /^Gr/' datafile        western         WE      Sharon Gray            
5.3     .97     5       23  
 ~

(c) 其他的匹配例子

 ~ awk ' $5 ~ /\.[4-6]+/' datafile   eastern         EA      TB Savage     4.4     .84     5       20   north           NO      Margot Webe4.5     .89     5        9

这个起到匹配作用的是4.4和4.5代表的字段哦,英文名字中有空格,占用了两个字段位


2.4 关系运算符和条件表达式


(a) <><=> >= != == ~ !~ 前面是一般的比较关系运算符,后面是用于字符串或者正则表达式是否匹配

 ~ awk '$5 >= 5.3' datafile                          western         WE      Sharon Gray   5.3     .97     5       23   northeast       NE      AM Main Jr.5.1     .94     3       13   central         CT     Ann Stephens5.7     .94     5       13    ~

上面我们看到一条有问题的数据,就是第二条记录。其实看看前面的姓名有三个字段…


(b) 算书运算 + - * / % ^


前面是正常的四则运算,而后面分别是除法、取余和取冪运算符。awk支持浮点运算,而且会按照浮点方式执行运算(比如下面的9/2=4.5)

 ~ awk '/centr/ {print NR,$1,$2,$5+3,7+8}' datafile    9 central CT 8.7 15  
 ~ echo 9 | awk '{print $1%2, $1/2}'  
1 4.5  
 ~

(c) 逻辑运算 && || !

 ~ awk '$5 > 5.5 && $1 ~ /cen/' datafile   central         CT      Ann Stephens  5.7     .94     5       13    ~

(d) 条件表达式


类似于C/C++的?运算符,格式为:条件表达式1 ? 表达式2 : 表达式3

 ~ awk 'NR<3 {="" print="" (="" nf="=8" 'valid'="" :="" 'invalid'="" ),'nf=',NF}'> datafile   valid NF= 8  
valid NF= 8  
 ~

(e) 范围模式 ,


awk的范围模式也是封闭范围。在所有记录中他们会顺序进行多次匹配,第一次匹配完后还可以进行下面接下来的第二次、第三次可能的匹配范围。如果开头匹配到了,但是没有结尾的话,会把整个文件记录的末尾当作是这次匹配的结尾作为范围

 ~ awk '/north/,/south/ {print NR, $1, $2}' datafile   1 northwest NW   2 western WE   3 southwest SW   7 northeast NE   8 north NO   9 central CT   ?  ~

上面例子的第二次匹配没有匹配到结尾,就默认到文件的结尾

(f) 赋值运算 = += -= *= /= %=

 ~ awk '/north/ {$1='NORTH' ; print NR,$1,$2,$3}' datafile   1 NORTH NW Charles   7 NORTH NE AM   8 NORTH NO Margot   ?  ~

2.5 变量


(a) awk的内置变量


如上面例子中使用到的NR、NF,这些是awk内置的变量,可以使用$直接取值和设置

ARGC          命令行参数个数   ARGV          命令行参数构成胡数组   FILENAME  当前输入文件的文件名   FNR          当前文件的记录数   FS             输入分割符,默认是空格字符   NF               当前记录的字段数   NR               当前的记录编号   OFS             输出字段分割符   ORS            记录分割符   IGNORECASE  是否忽略大小写

上面的FS、OFS看似都是空字符。其他使用例子:

 ~ echo -ne '123\n346\n' | awk '{ print $0,ARGC,ARGV[0],FILENAME,FNR,FS,NF,NR,OFS }'  
123 1 awk - 1   1 1    
346 1 awk - 2   1 2    
 ~ awk ' {IGNORECASE=1}; $1=='North' {print NR,$1,$2,$3} ' datafile   8 north NO Margot   ?  ~

(b) 一般变量


变量类型: 数值类型(默认值0),字符串类型(默认值””)

强制转化: name+0 name+””

 ~ echo 123 | awk '{ x = $1; y = ++x; print 'x='x,'y='y}'      x=124 y=124      
 ~ echo 123 | awk '{ x = $1; y = x++; print 'x='x,'y='y}'      x=124 y=123      
 ~

2.6 BEGIN、END模式


BEGIN是在对输入文件进行任何处理之前进行的操作块,而实际上不需要任何输入文件,也能执行BEGIN测试,所以后面有很多同输入无关的测试,这样就可以把这些代码写道BEGIN的语句块里面。使用过程中,通常在BEGIN中设置OFS、RS、FS等参数值,以及用户定义输入格式、变量定义初始化等操作。

END模式也不匹配任何输入,awk是在处理完毕所有输入行之后才处理END模式

 ~ awk ' BEGIN{IGNORECASE=1; count=0}; $1 ~ /North/ {count++} ; END{ print 'Found',count}  ' datafile      Found 3        

2.7 重定向和管道


(a) 支持 > >> 重定向符号


使用的时候作为文件名参数需要使用””括起来,getline可以用于输入重定向来获得输入信息

 ~ awk 'BEGIN { 'date' | getline date; print 'The date is',date > 'date.file'}'  
 ~ cat date.file   The date is Wed Dec 28 18:43:39 HKT 2016  
 ~

(b) 管道 |

 ~ awk '/north/ { print $0 | 'grep Charles' }' datafile   northwest       NW      Charles Main  3.0     .98     3       34    ~

(c) system 函数。可以进行系统命令调用

 ~ awk 'BEGIN { system('whoami') }'  
v5kf   ?  ~

(d) printf 格式化输出信息,跟C语言的类似

 ~ awk 'BEGIN { printf 'Hello, %s, you are %d years old.\n','Nicol TAO','23'}'  
Hello, Nicol TAO, you are 23 years old.   ?  ~

2.8 条件语句和循环


(a) if


在条件模式中,if是隐含的模式了,而条件语句if也可以按照需要直接声明出来的


句式类似于if () {} else if () {} else {}

 ~ awk
'{ if( $1 ~ /north/) { print 'north related.'} else if ( $1 ~ /south/ ) { print 'south related.'} else { print 'wield...'} }' datafile   north related.   wield...   south related.   south related.   south related.   wield...   north related.   north related.   wield...   ?  ~

(b) while


句法 while () {}

 ~ awk
'BEGIN { i = 0; count = 0; } { while ( i < nr="" )="" {="" i="" ++;="" if="" (="" $1="" ~="" orth/="" )="" {="" count++;="" print="" nr,$1="" $2="" }="" }="" }="" end="" {="" print="" 'count:',count="">
datafile   1 northwestNW   7 northeastNE   8 northNO   Count: 3  
 ~

(c) for


普通for循环,句法for( ; ; ){}

 ~ awk 'BEGIN { i = 0;} { for(;i datafile   1 northwest NW ~~   7 northeast NE ~~   8 north NO ~~   ?  ~

(d) break continue


同C/C++语言一样,是作用于跳出循环体和跳出本次循环的关键字。


2.9 程序控制语句


(a) next


从文件中读取下一行输入,然后从awk脚本顶部开始重新执行。同continue效果也有点相似,只不过这里是作用于awk工具在对每行操作的自动“循环”中的


(b) exit


中断记录的处理,但是不能够跳过END语句块。exit可以带一个范围为0~255的退出参数,约定0表示成功,这个退出参数实际就传递给了$?表示执行的结果

 
~ awk '{  if ( $1 ~ /north/ ) { print NR,$1,$2,'skip'; next; } if ( $2 ~ /SO/ ) { print NR,$1,$2,'exit will'; exit 3; } print NR,$1,$2,'after if...'; } END { print 'Fininal should be called here...' }'
datafile   1 northwest NW skip   2 western WE after if...   3 southwest SW after if...   4 southern SO exit will   Fininal should be called here...   ?  ~ echo $?   3  
 ~

2.10 数组


(a) awk中的数组也可以称为键值对,因为数组的下标可以是字符串

 ~ awk 'BEGIN{x=0;i=0;} {name[x++]=$2;} END { for(;i employees   name[0] is Jones   name[1] is Adams   name[2] is Chang   name[3] is Black   ?  ~

(b) 上面是使用的普通for循环结构,而如果数组的下标不是数字类型时候,使用新的for遍历循环就很方便


for( item in array) { …array[item]…}


item会自动依次提取array中的索引值,实际数组元素可以通过array[item]来访问。awk中的数组是通过hash来存贮的,所以这里的便利理论上来说顺序是不确定的

 ~ awk '{name[$1]=$2;} END { for(item in name ) { print 'Family name for',item,'is',name[item];} }' employees   Family name for Tom is Jones   Family name for Sally is Chang   Family name for Mary is Adams   Family name for Billy is Black   ?  ~ awk '{ if ($1 ~ /north/){ count['north'] ++;} else if ( $1 ~ /east/) count['east']++; else count['other']++;} END{ for(item in count) { print item,'related count:',count[item]; } }' datafile   other related count: 4  
east related count: 2  
north related count: 3  
 ~

(c) 数组的其他部分


splite可以分割字符串,构造形成数组

 ~ awk 'BEGIN{ str='/etc/samba/smb.conf'; split(str,name,'/'); for (item in name) print name[item]':';}'   :   etc:   samba:   smb.conf:   ?  ~

这里的开头/被分割出来产生了一个空串哈…


2.11 内置函数

sub               (/reg/,替换串[,目标串])   gsub             (/reg/,替换串[,目标串])   index(str,sub_str) 返回sub_str第一次在str中出现的位置(偏移量从1开始)   length(str)     返回字符串的字符个数   substr(str,start_pos[,length]) 返回子串,如果没有length,就到串的末尾   match(str,/reg/)     返回正则匹配在字符串中的位置,同时设置RSTART和RLENGTH的值   split(str,arr_name[,split_sig])

上面两个内置函数sub和gsub的区别是,sub只进行第一次替换,而gsub会对所有串进行替换(相当于s添加了g参数吧)。下面是操作例子

 ~ awk '{sub(/[Ee]ast/,'EAST',$1); print $1,index($1,'EAST'),'length',length($1);}' datafile   northwest 0 length 9  
western 0 length 7  
southwest 0 length 9  
southern 0 length 8  
southEAST 6 length 9  
EASTern 1 length 7  
northEAST 6 length 9  
north 0 length 5  
central 0 length 7  
 ~ awk '{ sub(/lly/,'--&**',$1); print $1 }' employees   Tom   Mary   Sa--lly**   Bi--lly**   ?  ~ awk '{if ( match($1,/[Ee]ast/) != 0) { print RSTART,RLENGTH,$1; }}' datafile   6 4 southeast   1 4 eastern   6 4 northeast   ?  ~

上面可以看见&的用法哈


2.12 算数函数


atan2(x,y) cos(x) exp(x) log(x) sin(x) sqrt(x)

int(x)直接舍去小数,保留整数部分

rand() 产生随机数(0~1) srand(x) 初始化随机数种子

默认情况下每次调用rand(),结果都会产生相同的随机数,这时候需要调用srand()重新产生一个种子,后面的随机数才不同

 ~ awk 'BEGIN{ print rand(),rand(),srand(),rand(),rand();}'   0.237788 0.291066 1 0.215969 0.629868  
 ~ awk 'BEGIN{ print rand(),rand(),srand(),rand(),rand();}'   0.237788 0.291066 1 0.556348 0.749557  
 ~ awk 'BEGIN{ print rand(),rand(),srand(),rand(),rand();}'   0.237788 0.291066 1 0.0931369 0.835396  
 ~

2.13 awk正则表达式元字符

^      行首定位符   $      行尾定位符   .      匹配除换行之外的单个字符   *      匹配0个或者多个前导字符(这里是前导字符0或者多个,任意一个或多个字符,使用 .* )   +      匹配一个或者多个前导字符   ?      匹配0个或者1个前导字符   []     指定字符中的任意一个字符,比如[Ll] [a-z]   [^]    上面一样,不匹配的字符   AA|BB  匹配AA或者BB   (AB)+  匹配一个或者多个AB组合,比如AB,ABAB,ABABAB...   \*     匹配*本身   &      保存查找匹配到的串,可以用在后面的替换中 s/love/**&**/

同第一部分sed工具相比,awk不支持的正则模式有

\(..\)   \<  =""  =""  =""  \="">    x\{m\}  x\{m,\} x\{m,n\}

从上面看来awk比较的复杂,已经具备了算数运算、逻辑、循环等一个脚本语言需要的大多数基本元素(貌似还缺函数)了。其实觉得单独用awk写脚本的不是很多,大多都是和sed一块,夹杂到shell


script里面用的。比如当时我工作时候用的例子:

PRIMSAM=`cat /etc/hosts | /usr/xpg4/bin/grep 'OAM_PRIMARY_IF_A' | awk '{print $2}'`   SEDSAM=`cat /etc/hosts | /usr/xpg4/bin/grep 'OAM_SECONDARY_IF_A' | awk '{print $2}'`   count1=`ttsys -v 1 -e 'select count(*) from CONFIGPARAMS where NODENAME='CPSNodes' and SUBSYSTEMNAME='Call Processing' and MANAGERNAME='Call Manager' and PARAMNAME='LocNumMapFeature';quit'|sed -e 's/<> -e 's/ >//g'|awk '{print $1}'`

还比如:

SMBPRINC=`klist -k | grep '\<> | uniq | awk '{print $2;}'`   TMPDIR='/'`date +%N | md5sum | awk '{print $1}'`   TMPFILE=`date +%N | md5sum | awk '{print $1}'`   sed -e 's/^MOUNTD_NFS_V2=.*$//g' /etc/sysconfig/nfs -i 2>&1 >/dev/null   sed -e {s/REALM/$( echo `hostname -d` | tr [:lower:] [:upper:] )/} $TESTCONFIGFILE -i

看吧,当时做的笔记还是多认真的,那时的学习是多么单纯的一件事!



●本文编号195,以后想阅读这篇文章直接输入195即可。

●输入m可以获取到文章目录

相关推荐↓↓↓
 

黑客技术与网络安全

推荐15个技术类公众微信

涵盖:程序人生、算法与数据结构、黑客技术与网络安全、大数据技术、前端开发、Java、Python、Web开发、安卓开发、iOS开发、C/C++、.NET、Linux、数据库、运维等。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多