第14章 正则表达式 ==================== 学习Linux请加QQ群: 群1(163262181满) 群2(148412746满) 群3(246401509) 跟阿铭学Linux邀请函 (http://www.aminglinux.com),5期已开班,请加QQ81677956索要配套视频教程。 这部分内容可以说是学习shell脚本之前必学的内容。如果你这部分内容学的越好,那么你的shell脚本编写能力就会越强。所以不要嫌这部分内容啰嗦,也不要怕麻烦,要用心学习。一定要多加练习,练习多了就能熟练掌握了。 在计算机科学中,正则表达式是这样解释的:它是指一个用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串。在很多文本编辑器或其他工具里,正则表达式通常被用来检索和/或替换那些符合某个模式的文本内容。许多程序设计语言都支持利用正则表达式进行字符串操作。对于系统管理员来讲,正则表达式贯穿在我们的日常运维工作中,无论是查找某个文档,抑或查询某个日志文件分析其内容,都会用到正则表达式。 其实正则表达式,只是一种思想,一种表示方法。只要我们使用的工具支持表示这种思想那么这个工具就可以处理正则表达式的字符串。常用的工具有grep, sed, awk 等,下面阿铭就分别介绍一下这三种工具的使用方法。 grep / egrep --------------- 阿铭在前面的内容中多次提到并用到grep命令,可见它的重要性。所以好好学习一下这个重要的命令吧。你要知道的是grep连同下面讲的sed, awk都是针对文本的行才操作的。 语法: ``grep [-cinvABC] 'word' filename`` -c :打印符合要求的行数 -i :忽略大小写 -n :在输出符合要求的行的同时连同行号一起输出 -v :打印不符合要求的行 -A :后跟一个数字(有无空格都可以),例如 –A2则表示打印符合要求的行以及下面两行 -B :后跟一个数字,例如 –B2 则表示打印符合要求的行以及上面两行 -C :后跟一个数字,例如 –C2 则表示打印符合要求的行以及上下各两行 .. testcode:: [root@localhost ~]# grep -A2 'halt' /etc/passwd halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin 把包含 'halt' 的行以及这行下面的两行都打印出。 .. testcode:: [root@localhost ~]# grep -B2 'halt' /etc/passwd sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt 把包含 'halt' 的行以及这行上面的两行都打印出。 .. testcode:: [root@localhost ~]# grep -C2 'halt' /etc/passwd sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin 把包含 'halt' 的行以及这行上面和下面的各两行都打印出。 下面阿铭举几个典型实例帮你更深刻的理解grep. 1. **过滤出带有某个关键词的行并输出行号** .. testcode:: [root@localhost ~]# grep -n 'root' /etc/passwd 1:root:x:0:0:root:/root:/bin/bash 11:operator:x:11:0:operator:/root:/sbin/nologin 2. **过滤不带有某个关键词的行,并输出行号** .. testcode:: [root@localhost ~]# grep -nv 'nologin' /etc/passwd 1:root:x:0:0:root:/root:/bin/bash 6:sync:x:5:0:sync:/sbin:/bin/sync 7:shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown 8:halt:x:7:0:halt:/sbin:/sbin/halt 26:test:x:511:511::/home/test:/bin/bash 27:test1:x:512:511::/home/test1:/bin/bash 3. **过滤出所有包含数字的行** .. testcode:: [root@localhost ~]# grep '[0-9]' /etc/inittab # upstart works, see init(5), init(8), and initctl(8). # 0 - halt (Do NOT set initdefault to this) # 1 - Single user mode # 2 - Multiuser, without NFS (The same as 3, if you do not have networking) # 3 - Full multiuser mode # 4 - unused # 5 - X11 # 6 - reboot (Do NOT set initdefault to this) id:3:initdefault: 4. **过滤出所有不包含数字的行** .. testcode:: [root@localhost ~]# grep -v '[0-9]' /etc/inittab # inittab is only used by upstart for the default runlevel. # # ADDING OTHER CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM. # # System initialization is started by /etc/init/rcS.conf # # Individual runlevels are started by /etc/init/rc.conf # # Ctrl-Alt-Delete is handled by /etc/init/control-alt-delete.conf # # Terminal gettys are handled by /etc/init/tty.conf and /etc/init/serial.conf, # with configuration in /etc/sysconfig/init. # # For information on how to write upstart event handlers, or how # # Default runlevel. The runlevels used are: # 5. **把所有以 '#' 开头的行去除** .. testcode:: [root@localhost ~]# grep -v '^#' /etc/inittab id:3:initdefault: 6. **去除所有空行和以 '#' 开头的行** .. testcode:: [root@localhost ~]# grep -v '^#' /etc/crontab |grep -v '^$' SHELL=/bin/bash PATH=/sbin:/bin:/usr/sbin:/usr/bin MAILTO=root HOME=/ 在正则表达式中, "^" 表示行的开始, "$" 表示行的结尾,那么空行则可以用 "^$" 表示,如何打印出不以英文字母开头的行呢? .. testcode:: [root@localhost ~]# vim test.txt [root@localhost ~]# cat test.txt 123 abc 456 abc2323 #laksdjf Alllllllll 阿铭先在test.txt中写几行字符串,用来做实验。 .. testcode:: [root@localhost ~]# grep '^[^a-zA-Z]' test.txt 123 456 #laksdjf [root@localhost ~]# grep '[^a-zA-Z]' test.txt 123 456 abc2323 #laksdjf 在前面阿铭也提到过这个 '[ ]' 的应用,如果是数字的话就用[0-9]这样的形式,当然有时候也可以用这样的形式[15]即只含有1或者5,注意,它不会认为是15。如果要过滤出数字以及大小写字母则要这样写[0-9a-zA-Z]。另外[ ]还有一种形式,就是[^字符] 表示除[ ]内的字符之外的字符。 7. **过滤任意一个字符与重复字符** .. testcode:: [root@localhost ~]# grep 'r..o' /etc/passwd operator:x:11:0:operator:/root:/sbin/nologin gopher:x:13:30:gopher:/var/gopher:/sbin/nologin vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin ``.`` 表示任意一个字符,上例中,就是把符合r与o之间有两个任意字符的行过滤出来, ``*`` 表示零个或多个前面的字符。 .. testcode:: [root@localhost ~]# grep 'ooo*' /etc/passwd root:x:0:0:root:/root:/bin/bash lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin mail:x:8:12:mail:/var/spool/mail:/sbin/nologin uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin postfix:x:89:89::/var/spool/postfix:/sbin/nologin 'ooo*' 表示oo, ooo, oooo ... 或者更多的 'o' 现在你是否想到了 '.*' 这个组合表示什么意义? .. testcode:: [root@localhost ~]# grep '.*' /etc/passwd |wc -l 27 [root@localhost ~]# wc -l /etc/passwd 27 /etc/passwd '.*' 表示零个或多个任意字符,空行也包含在内。 8. **指定要过滤字符出现的次数** .. testcode:: [root@localhost ~]# grep 'o\{2\}' /etc/passwd root:x:0:0:root:/root:/bin/bash lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin mail:x:8:12:mail:/var/spool/mail:/sbin/nologin uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin postfix:x:89:89::/var/spool/postfix:/sbin/nologin 这里用到了{ },其内部为数字,表示前面的字符要重复的次数。上例中表示包含有两个o 即 'oo' 的行。注意,{ }左右都需要加上脱意字符 '\\', 另外,使用{ }我们还可以表示一个范围的,具体格式是 '\{n1,n2\}' 其中n1, ‘>=, ‘<, ‘<=, ‘!= 等等,值得注意的是,在和数字比较时,若把比较的数字用双引号引起来后,那么awk不会认为是数字,而认为是字符,不加双引号则认为是数字。 .. testcode:: [root@localhost ~]# awk -F ':' '$3>="500"' /etc/passwd shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin nobody:x:99:99:Nobody:/:/sbin/nologin dbus:x:81:81:System message bus:/:/sbin/nologin vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin haldaemon:x:68:68:HAL daemon:/:/sbin/nologin postfix:x:89:89::/var/spool/postfix:/sbin/nologin sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin tcpdump:x:72:72::/:/sbin/nologin user11:x:510:502:user11,user11's office,12345678,123456789:/home/user11:/sbin/nologin test:x:511:511::/home/test:/bin/bash test1:x:512:511::/home/test1:/bin/bash 在上面的例子中,阿铭本想把uid大于等于500的行打印出,但是结果并不是我们的预期,这是因为awk把所有的数字当作字符来对待了,就跟上一章中提到的 sort 排序原理一样。 .. testcode:: [root@localhost ~]# awk -F ':' '$7!="/sbin/nologin"' /etc/passwd root:x:0:0:root:/root:/bin/bash sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt test:x:511:511::/home/test:/bin/bash test1:x:512:511::/home/test1:/bin/bash != 为不匹配,除了针对某一个段的字符进行逻辑比较外,还可以两个段之间进行逻辑比较。 .. testcode:: [root@localhost ~]# awk -F ':' '$3<$4' /etc/passwd adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin mail:x:8:12:mail:/var/spool/mail:/sbin/nologin uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin games:x:12:100:games:/usr/games:/sbin/nologin gopher:x:13:30:gopher:/var/gopher:/sbin/nologin ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin 另外还可以使用 && 和 || 表示 "并且" 和 "或者" 的意思。 .. testcode:: [root@localhost ~]# awk -F ':' '$3>"5" && $3<"7"' /etc/passwd shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin haldaemon:x:68:68:HAL daemon:/:/sbin/nologin user11:x:510:502:user11,user11's office,12345678,123456789:/home/user11:/sbin/nologin test:x:511:511::/home/test:/bin/bash test1:x:512:511::/home/test1:/bin/bash 也可以是或者 .. testcode:: [root@localhost ~]# awk -F ':' '$3>"5" || $7=="/bin/bash"' /etc/passwd root:x:0:0:root:/root:/bin/bash shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin nobody:x:99:99:Nobody:/:/sbin/nologin dbus:x:81:81:System message bus:/:/sbin/nologin vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin haldaemon:x:68:68:HAL daemon:/:/sbin/nologin postfix:x:89:89::/var/spool/postfix:/sbin/nologin sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin tcpdump:x:72:72::/:/sbin/nologin user11:x:510:502:user11,user11's office,12345678,123456789:/home/user11:/sbin/nologin test:x:511:511::/home/test:/bin/bash test1:x:512:511::/home/test1:/bin/bash 4. **awk的内置变量** awk常用的变量有: NF :用分隔符分隔后一共有多少段 NR :行数 .. testcode:: [root@localhost ~]# head -n3 /etc/passwd | awk -F ':' '{print NF}' 7 7 7 [root@localhost ~]# head -n3 /etc/passwd | awk -F ':' '{print $NF}' /bin/bash /sbin/nologin /sbin/nologin NF 是多少段,而$NF是最后一段的值, 而NR则是行号。 .. testcode:: [root@localhost ~]# head -n3 /etc/passwd | awk -F ':' '{print NR}' 1 2 3 我们可以使用行号作为判断条件: .. testcode:: [root@localhost ~]# awk 'NR>20' /etc/passwd postfix:x:89:89::/var/spool/postfix:/sbin/nologin abrt:x:173:173::/etc/abrt:/sbin/nologin sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin tcpdump:x:72:72::/:/sbin/nologin user11:x:510:502:user11,user11's office,12345678,123456789:/home/user11:/sbin/nologin test:x:511:511::/home/test:/bin/bash test1:x:512:511::/home/test1:/bin/bash 也可以配合段匹配一起使用: .. testcode:: [root@localhost ~]# awk -F ':' 'NR>20 && $1 ~ /ssh/' /etc/passwd sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin 5. **awk中的数学运算** awk可以把段值更改: .. testcode:: [root@localhost ~]# head -n 3 /etc/passwd |awk -F ':' '$1="root"' root x 0 0 root /root /bin/bash root x 1 1 bin /bin /sbin/nologin root x 2 2 daemon /sbin /sbin/nologin awk还可以对各个段的值进行数学运算: .. testcode:: [root@localhost ~]# head -n2 /etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin [root@localhost ~]# head -n2 /etc/passwd |awk -F ':' '{$7=$3+$4}' [root@localhost ~]# head -n2 /etc/passwd |awk -F ':' '{$7=$3+$4; print $0}' root x 0 0 root /root 0 bin x 1 1 bin /bin 2 当然还可以计算某个段的总和 .. testcode:: [root@localhost ~]# awk -F ':' '{(tot=tot+$3)}; END {print tot}' /etc/passwd 2891 这里的END要注意一下,表示所有的行都已经执行,这是awk特有的语法,其实awk连同sed都可以写成一个脚本文件,而且有他们特有的语法,在awk中使用if判断、for循环都是可以的,只是阿铭认为日常管理工作中没有必要使用那么复杂的语句而已。 .. testcode:: [root@localhost ~]# awk -F ':' '{if ($1=="root") print $0}' /etc/passwd root:x:0:0:root:/root:/bin/bash 基本上,正则表达的内容就这些了。但是阿铭要提醒你一下,阿铭介绍的这些仅仅是最基本的东西,并没有提啊深入的去讲sed和awk,但是完全可以满足日常工作的需要,有时候也许你会碰到比较复杂的需求,如果真遇到了就去请教一下google吧。下面出几道关于awk的练习题,希望你要认真完成。 1. 用awk 打印整个test.txt (以下操作都是用awk工具实现,针对test.txt) 2. 查找所有包含 'bash' 的行 3. 用 ':' 作为分隔符,查找第三段等于0的行 4. 用 ':' 作为分隔符,查找第一段为 'root' 的行,并把该段的 'root' 换成 'toor' (可以连同sed一起使用) 5. 用 ':' 作为分隔符,打印最后一段 6. 打印行数大于20的所有行 7. 用 ':' 作为分隔符,打印所有第三段小于第四段的行 8. 用 ':' 作为分隔符,打印第一段以及最后一段,并且中间用 '@' 连接 (例如,第一行应该是这样的形式 'root@/bin/bash' ) 9. 用 ':' 作为分隔符,把整个文档的第四段相加,求和 awk习题答案 .. testcode:: 1. awk '{print $0}' test.txt 2. awk '/bash/' test.txt 3. awk -F':' '$3=="0"' test.txt 4. awk -F':' '$1=="root"' test.txt |sed 's/root/toor/' 5. awk -F':' '{print $NF}' test.txt 6. awk -F':' 'NR>20' test.txt 7. awk -F':' '$3<$4' test.txt 8. awk -F':' '{print $1"@"$NF}' test.txt 9. awk -F':' '{(sum+=$4)}; END {print sum}' test.txt 阿铭建议你最好再扩展学习一下: http://www.aminglinux.com/bbs/thread-5438-1-1.html 教程答疑: `请移步这里 `_. 欢迎你加入 `阿铭学院 `_ 和阿铭一起学习Linux,让阿铭成为你Linux生涯中永远的朋友吧!