Shell文本操作(grep、sed,awk)

作者:Sophisticated

shell脚本语言包含了众多用于解决Unix/Linux系统问题的工具,其中有不少和文本处理相关, 包括sedawkgrepcut,这些工具可以相互结合以满足文本处理需求。

正则表达式

正则表达式是基于模式匹配的文本处理技术的关键所在。想要有效地运用正则表达式,就必 须对其有一个基本的理解

位置标记

位置标记锚点(position marker anchor)是标识字符串位置的正则表达式

正则表达式 描 述 示 例
^ 指定了匹配正则表达式的文本必须起始于字符串的首部 ^tux 能够匹配以tux起始的行
$ 指定了匹配正则表达式的文本必须结束于目标字符串的尾部 tux$能够匹配以tux结尾的行
标识符

标识符是正则表达式的基础组成部分。它定义了那些为了匹配正则表达式,必须存在(或不 存在)的字符

正则表达式 描 述 示 例
A字符 正则表达式必须匹配该字符 A 能够匹配字符A
. 匹配任意一个字符 Hack.能够匹配Hackl和Hacki,但是不能匹配Hackl2或 Hackil,它只能匹配单个字符
[ ] 匹配中括号内的任意一个字符。中括号内 可以是一个字符组或字符范围 coo[kl]能够匹配cook或cool,[0-9]匹配任意单个数字
[^] 匹配不在中括号内的任意一个字符。中括 号内可以是一个字符组或字符范围 9[^01]能够匹配92和93,但是不匹配91和90;A[^0-9] 匹配A以及随后除数字外的任意单个字符
数量修饰符

一个标识符可以出现一次、多次或是不出现。数量修饰符定义了模式可以出现的次数

正则表达式 描 述 示 例
? 匹配之前的项1次或0次 colou?r能够匹配color或colour,但是不能匹配colouur
+ 匹配之前的项1次或多次 Rollno-9+能够匹配Rollno-99和Rollno-9,但是不能匹配Rollno-
* 匹配之前的项0次或多次 co*l能够匹配cl、col和coool
{n} 匹配之前的项n次 [0-9]{3}能够匹配任意的三位数,[0-9]{3}可以扩展为[0-9][0-9] [0-9]
{n,} 之前的项至少需要匹配n次 [0-9]{2,} 能够匹配任意一个两位或更多位的数字
{n,m} 之前的项所必须匹配的小次数和大次数 [0-9]{2,5}能够匹配两位数到五位数之间的任意一个数字
常用元字符
代码 说明
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束
其他

还有其他一些特殊字符可以调整正则表达式的匹配方式

正则表达式 描 述 示 例
() 将括号中的内容视为一个整体 ma(tri)?x 能够匹配max或matrix
| 指定了一种选择结构,可以匹配 | 两边的 任意一项 Oct (1st | 2nd)能够匹配Oct 1st或Oct 2nd
\ 转义字符可以转义之前介绍的特殊字符 a\.b能够匹配a.b,但不能匹配ajb。因为\忽略了.的特 殊意义
示例

匹配任意单词的正则表达式: ( +[a-zA-Z]+ +)
开头的+表示需要匹配一个或多个空格。字符组[a-zA-Z]用于匹配所有的大小写字母。随后的+ 表示至少要匹配一个字母,多者不限。后的+表示需要匹配一个或多个空格来终结单词

这个正则表达式无法匹配句子末尾的单词。要想匹配句尾或是逗号前的单 词,需要将正则表达式改写为: ( +[a-zA-Z]+[?,.]? +)

匹配IP地址,IP地址是由点号分隔的4组三位数字:

[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} 

【注】:对于字符串this is a test和正则表达式s.*s,匹配的内容是s is a tes,而 非s is,即默认为贪婪模式。若将正则表达式改为s.*s+,即非贪婪模式,则会匹配s is

用grep 在文件中搜索文本

在stdin中搜索匹配特定模式的文本行:

$ echo -e "this is a word\nnext line" | grep word 
this is a word 

在文件中搜索匹配特定模式的文本行:

$ grep pattern filename
this is the line containing pattern 

grep命令默认使用基础正则表达式。这是先前描述的正则表达式的一个子集。选项-E可以 使grep使用扩展正则表达式。也可以使用默认启用扩展正则表达式的egrep命令:

$ grep -E "[a-z]+" filename 
或者 
$ egrep "[a-z]+" filename 

选项-o可以只输出匹配到的文本:

$ echo this is a line. | egrep -o "[a-z]+\." 
line 

选项-v可以打印出不匹配match_pattern的所有行:

$ grep -v match_pattern file 
#选项-v能够反转(invert)匹配结果。 

选项-c能够统计出匹配模式的文本行数:

$ grep -c "text" filename 
10 

需要注意的是-c只是统计匹配行的数量,并不是匹配的次数:

$ echo -e "1 2 3 4\nhello\n5 6" | egrep  -c "[0-9]" 
2

选项-n可以打印出匹配字符串所在行的行号:

$ cat sample1.txt 
gnu is not unix 
linux is fun 
bash is art 
$ cat sample2.txt 
planetlinux 
$ grep linux -n sample1.txt 
2:linux is fun 
补充内容

递归搜索多个文件

$ grep "text" . -R -n 

命令中的.指定了当前目录。例如:

$ cd src_dir 
$ grep "test_function()" . -R -n 
./miscutils/test.c:16:test_function(); 

test_function()位于miscutils/test.c的第16行。如果你要在网站或源代码树中展开 搜索,选项-R尤其有用。它等价于下列命令: $ find . -type f | xargs grep "test_function()"

选项-i可以在匹配模式时不考虑字符的大小写:

$ echo hello world | grep -i "HELLO" hello

使用cut 按列切分文件

cut命令可以按列,而不是按行来切分文件。该命令可用于处理使用固定宽度字段的文件、 CSV文件或是由空格分隔的文件(例如标准日志文件)。

选项-f可以指定要提取的字段:

cut -f FIELD_LIST filename 

FIELD_LIST是需要显示的列。它由列号组成,彼此之间用逗号分隔。例如:

$ cut -f 2,3 filename 
#该命令将显示第2列和第3列。 

选项-d能够设置分隔符:

$ cat delimited_data.txt 
No;Name;Mark;Percent 
1;Sarath;45;90 
2;Alex;49;98 
3;Anu;45;90 
 
$ cut -f2 -d";" delimited_data.txt 
Name 
Sarath 
Alex 
Anu 

使用sed 替换文本

sed是stream editor(流编辑器)的缩写。它常见的用法是进行文本替换

sed可以使用另一个字符串来替换匹配模式。模式可以是简单的字符串或正则表达式:

$ sed 's/pattern/replace_string/' file 

选项-i会使得sed用修改后的数据替换原始文件:

$ sed -i 's/text/replace/' file 

g标记可以使sed执行全局替换:

$ sed 's/pattern/replace_string/g' file 

移除空行,空行可以用正则表达式 ^$ 进行匹配,后的/d告诉sed不执行替换操作,而是直接删除匹配到的空行:

$ sed '/^$/d' file 

直接在文件中替换,使用选项-i

$ cat sed_data.txt 
11 abc 111 this 9 file contains 111 11 88 numbers 0000 
 
$ sed -i 's/\b[0-9]\{3\}\b/NUMBER/g' sed_data.txt 
$ cat sed_data.txt 
11 abc NUMBER this 9 file contains NUMBER 11 88 numbers 0000 

上面的单行命令只替换了所有的3位数字。正则表达式\b[0-9]\{3\}\b用于匹配3位数字。[0-9] 表示数字取值范围是从0到9。{3}表示匹配之前的数字3次。\{3\}中的\用于转义{和}。\b表示 单词边界

用awk 进行高级文本处理

awk命令可以处理数据流。它支持关联数组、递归函数、条件语句等功能

awk脚本的结构如下:

awk 'BEGIN{  print "start" } pattern { commands } END{ print "end" }' file 
工作原理

(1) 首先执行BEGIN { commands } 语句块中的语句。
(2) 接着从文件或stdin中读取一行,如果能够匹配pattern,则执行随后的commands语句 块。重复这个过程,直到文件全部被读取完毕。
(3) 当读至输入流末尾时,执行END { commands } 语句块。

BEGIN语句块在awk开始从输入流中读取行之前被执行。这是一个可选的语句块,诸如变量 初始化、打印输出表格的表头等语句通常都可以放在BEGIN语句块中。

END语句块和BEGIN语句块类似。它在awk读取完输入流中所有的行之后被执行。像打印所有 行的分析结果这种常见任务都是在END语句块中实现的。

重要的部分就是和pattern关联的语句块。这个语句块同样是可选的。如果不提供,则默 认执行{ print },即打印所读取到的每一行。awk对于读取到的每一行都会执行该语句块。这就像一个用来读取行的while循环,在循环体中提供了相应的语句。

每读取一行,awk就会检查该行是否匹配指定的模式。模式本身可以是正则表达式、条件语 句以及行范围等。如果当前行匹配该模式,则执行{ }中的语句

模式是可选的。如果没有提供模式,那么awk就认为所有的行都是匹配的:

$ echo -e "line1\nline2" | awk 'BEGIN { print "Start" } { print } END { print "End" } ' 
Start 
line1 
line2 
End

当使用不带参数的print时,它会打印出当前行

特殊变量

NR:表示记录编号,当awk将行作为记录时,该变量相当于当前行号。
NF:表示字段数量,在处理当前记录时,相当于字段数量。默认的字段分隔符是空格。
$0:该变量包含当前记录的文本内容。
$1:该变量包含第一个字段的文本内容。
$2:该变量包含第二个字段的文本内容。

$ echo -e "line1 f2 f3\nline2 f4 f5\nline3 f6 f7" | \ 
 awk '{      print "Line no:"NR",No of fields:"NF, "$0="$0,      "$1="$1,"$2="$2,"$3="$3  }'  
Line no:1,No of fields:3 $0=line1 f2 f3 $1=line1 $2=f2 $3=f3  
Line no:2,No of fields:3 $0=line2 f4 f5 $1=line2 $2=f4 $3=f5  
Line no:3,No of fields:3 $0=line3 f6 f7 $1=line3 $2=f6 $3=f7 

以将每一行中第一个字段的值按照下面的方法累加:

$ seq 5 | awk 'BEGIN { sum=0; print "Summation:" }  { print $1"+"; sum+=$1 } END { print "=="; print sum }'
Summation:  
1+  
2+  
3+  
4+  
5+ 
== 
15 

你可能感兴趣的:(Shell)