作者:Sophisticated
shell脚本语言包含了众多用于解决Unix/Linux系统问题的工具,其中有不少和文本处理相关, 包括sed
、awk
、grep
和cut
,这些工具可以相互结合以满足文本处理需求。
正则表达式是基于模式匹配的文本处理技术的关键所在。想要有效地运用正则表达式,就必 须对其有一个基本的理解
位置标记锚点(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
。
在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
命令可以按列,而不是按行来切分文件。该命令可用于处理使用固定宽度字段的文件、 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
是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 '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