文章目录
概述:什么是正则表达式
什么是正则表达式
正则表达式 (Regular Expression, RE, 或称为常规表达式)是通过一些特殊字符的排列,用以“搜寻/取代/删除”一列或多列文字字串。简言之,RE是一个字符串处理的方式(以行为单位)。要以RE的方式处理字符串,就需要支持RE的程序(如vi,sed,grep,awk等)。
例如:若系统开机总是出现一个关于mail程序的错误,而开机过程的相关程序都在/lib/systemd/system/ 下,就需要搜索该目录下某个文件内是否有mail关键字,此时执行grep 'mail' /lib/systemd/system/*
即可。
正则表达式对于系统管理员的用途
筛选系统中的登录信息,辅助管理系统
正则表达式的广泛用途
邮件标题筛选,屏蔽垃圾邮件等
正则表达式与shell再Linux中的地位
基础,很重要。
延展的正则表达式
扩展的正则表达式可以同时搜寻多个目标,做群组的字串处理。
基础正则表达式
语系对正则表达式的影响
文件的记录在计算机底层就是一组0,1代码串,通过编码转化为我们看到的字符。不同语系字母表的顺序不同。如:
LAGN=C:0 1 2 ...A B C ..Z a b c ...z #一种字符顺序
LAGN=zh_TW:0 1 ...a A b B...z Z #另外一种字符顺序
使用[A-Z]筛选:LANG=C,筛选的是大写字母。LANG=zh_TW.bg5时会选中大写字母和b-z这些字符。
特殊符号以及意义:
特殊符号 | 代表意义 |
---|---|
[:alnum:] | 代表0-9,A-Z,a-z(即代表数字和英文字母) |
[:alpha:] | 代表A-Z,a-z |
[:blank:] | 代表空白键和[TAB]键 |
[:cntrl:] | 代表键盘上的控制键,即CR,LF,TAB,DEL…等 |
[:digit:] | 代表0-9 |
[:graph:] | 代表除了空白字符(空白键,[TAB]键)外的所有键 |
[:lower:] | 代表小写字符,即a-z |
[:print:] | 代表可以被打印出来的字符 |
[:punct:] | 代表标点符号(punctuation symbol),即 : ? # $ …等 |
[:upper:] | 代表大写字母,即A-Z |
[:space:]: | 代表会产生空白的字符,包含空白键,[TAB],CR等 |
[:xdigit:] | 代表16进位的数字类型,包括:0-9,A-F,a-f的数字与字符 |
用法示例:
grep '[[:upper:]]' /etc/passwd #匹配文件passwd下的大写字母。注意匹配时外面还要再加一层中括号。
#这是因为[:upper:]代表A-Z,匹配一个大写字母的写法为[A-Z],自然有了[[:upper:]]
grep '[^[:lower:]]' /etc/passwd #匹配一个非小写字符。
grep的一些高级参数(搭配正则表达式)
grep筛选单位是一行,会返回匹配的行,其他丢弃。
#基础语法
grep [-A] [-B] [--color=auto] '搜索字串' filename
参数说明:
-A:after,参数值是数字。除了列出筛选出的行,该行后续的n行也列出来
-B:before,参数值也是数字。除了列出筛选出的行,该行前面的n行也列出来
--color=auto:可将匹配的关键字着色。
#下面这个选项很重要
-o:只返回匹配的内容,而非整个行。
示例:
grep '^a' /etc/passwd #筛选passwd文件中以a开头的行
dmesg|grep -n 'gen' #筛选dmesg(存有内核信息,包括硬件侦测的流程)中有'gen'的行。-n显示行号。
dmesg|grep -A2 -B1 'gen' #同时列出目标的前2行和后1行。
grep '^a' /etc/passwd
ls -l /etc|grep '^l' #列出/etc下的所有链接文件结果筛选用|传输。
基础正则表达式练习
基础正则表达式字符(characters)
^ $ . \ * [a] [a-z] [^a-z] {m,n}
- 搜索特点给字串
grep -n 'lily' /etc/passwd #搜索含lily的行。-n显示行号。可以不要
- 反向选择
grep -vn 'the' /etc/passwd #-v参数反向筛选。即筛选不含the的行。-n显示行号,-v反向筛选。
grep -vn 'the' /etc/passwd #可以不必显示行号
- 忽略大小写(默认是区分大小写的)
grep -in 'the' /etc/passwd #-i忽略大小写匹配。thE,The...等都可以匹配
.
代表一个且仅仅一个字符
grep xxx /etc/passwd #筛选文件passwd中含有xxx的行
grep 'a.b' /etc/passwd #筛选文件passwd中形如axb格式的字符。x是任意一个字符
grep 'a..b' /etc/passwd #筛选文件passwd中形如axxb格式的字符。x是任意一个字符
[]
匹配括号中的任意一个字符
注意:它只匹配括号里的一个字符。
grep '[Tt].b' /etc/passwd #匹配形如Txb,txb的串。x是任何一个字符,无论[]中有多少字符,[]只匹配括号里的任何一个字符。
grep '[a-zA-Z0-9]' /etc/passwd #匹配字符x,x是数字或英文字母。a-z,A-Z,0-9无固定顺序。
grep 'Tt.b' /etc/passwd #匹配Tt*b,*代表任何一个字符。
grep -n '[2-5]' /etc/passwd #-连接连续的一组字符。此处匹配有2,3,4,5的行。
grep -n '[a-d]' /etc/passwd #匹配有a,b,c,d的行
grep -n '[0-9]' regular_express.txt #匹配一个数字
grep -n '[[:digit:]]' regular_express.txt #匹配一个数字
^
字符在 [] 内代表“反向选择”,在 [] 之外则代表定位在行首
grep '[^a]oo' /etc/passwd #匹配xoo串。但x不能是a。
grep '[^a-z]oo' /etc/passwd #匹配xoo形式的串。x不能是小写字母
grep -n '[^[:lower:]]o'/etc/passwd #匹配xo形式,x不是小写字母
^
表示开头,它和\<符号等效
grep '^[a-z]' /etc/passwd #匹配开头是小写字母的行
grep '^tom' /etc/passwd #匹配开头为tom的串
grep '^[^a-zA-Z]' /etc/passwd #不以英文字母开头的串
grep '^[^[:alpha:]]' /etc/passwd #同上
$
表示结束 ,它和\>符号等效
是 l i n u x 下 的 断 行 符 。 一 个 空 行 , 则 他 的 一 是linux下的断行符。一个空行,则他的一 是linux下的断行符。一个空行,则他的一开头。
grep 'tom$' /etc/passwd #匹配结束为tom的串,即可以找结尾和开头为某个串的行。
grep '\.$' /etc/passwd #匹配以.结尾的行。.有特殊意义,所以要转义。windows下的断行符^M$,无法匹配linux下的断行符$。cat -A filename可以查看到断行符
grep -n '^$' /etc/passwd #返回空白行,空白行只有一个断行符$,所以以$开头。
grep -v '^#' /etc/rsyslog.conf |grep -v '^$' #匹配文件中不以#开头的非空白行。这十分方便查看配置文件等。
x\{m,n\}
:匹配m~n个x字符。实际上用的是{}字符。而由于它在linux中有特殊意义,因此需要转义。作用于紧挨前面的那个字符。
grep 'to\{4\}m$' /etc/passwd #匹配t和m间有4个o的串!!!! 注意与下面的区别
grep 'to\{4,\}m' /etc/passwd #匹配t和m间有4,5,6...个o的串
grep 'to\{2,4\}m' /etc/passwd #匹配t和m间有2~4个o的串
*
重复前一个字符0到无穷次。匹配只和*前的一个字符相关。
grep 'o*' /etc/passwd #匹配o出现0次到无穷次的行。所以会列出整个文件!!!!
grep 'oo*' /etc/passwd #匹配至少有一个o的行
grep 'go*g' /etc/passwd #匹配gg,gog,goog,gooog...
grep -n 'goo*g' regular_express.txt #匹配gog,goog,gooog...
.
匹配一个任意的字符
因此.*
匹配0个或多个任意字符
grep 'g..g' /etc/passwd #匹配gxxd格式 ,xx为任意两个字符
grep 'g.*g' /etc/passwd #匹配gg,gxg,gxxg,gxxxg... x是任意一个字符。即以g开头,以g结尾的串,含有此类串的行会选中。
grep '^g' /etc/passwd |grep 'g$' #匹配以g开头,以g结尾的行
grep '[0-9][0-9]*' /etc/passwd #匹配含任意数字的排列的行!!!
注意:一行中既有匹配的串,也有不匹配的串,仍然会匹配上。如上述匹配要求,对aooo也会匹配上(aoo不符合,ooo却符合)。
sed工具配合正则表达式:找出来后修改
sed会先把文件读到内存,按要求修改后再显示到屏幕上。并不改变源文件(默认情况)。sed倾向于一整行的处理。sed本身是一个管线命令,可以分析标准输入,还可以将数据进行取代、删除、新增、撷取特定行等。
基本语法
sed [-nefr] [动作]
-n:使用安静(silent)模式,sed的一般用法中,所有来自STDIN的数据会输出到屏幕上.加-n参数后,只有处理后的结果会显示出来。
-e:直接在命令行界面上进行sed的动作编辑
-f:直接将sed的动作写在一个文件里。-f filename则可以执行filename里面的sed动作。
-r:sed的动作支持的是延展性正则表达式的语法(默认是基础正则表达式语法)
-i:直接修改读取的内容,而不是由屏幕输出(会修改源文件)
动作说明:[n1,n2] function
n1,n2:可选的。代表进行动作的行数。如"10,20[动作]"则表示在10到20行间进行操作。
function有以下选择:
a:新增。a后面可以跟字串,字串会出现在当前行的下一行
c:取代。c后面可以跟字串,这些字串可以取代n1,n2之间的行
d:删除。d后通常不跟内容
i:插入。i后面可以跟字串,字串会出现在当前行的上一行
p:大于,将某个选择的数据印出。p通常会和sed -n 一起使用
s:取代,可直接进行取代。通常s的动作可以搭配正则表达式。
#增删(以行为单位)
#!!!sed后面的动作最好使用单引号包裹
nl /etc/passwd|sed '2,5d' #列出文件,显示行号。并删除2-5行。结果显示修改后的文件。
nl /etc/passwd|sed '2d' #仅仅删除第2行
nl /etc/passwd|sed '3,$d' #删除3到最后一行,注意用$代表最后一行
nl /etc/passwd|sed '2a appendix_content' #在第三行开头添加appendix_content。a在后添加
nl /etc/passwd|sed '2i appendix_content' #在第二行前面添加appendix_content。i在前插入
nl /etc/passwd|sed '2i appendix_content1\
appendix_content1' #在第二行前面添加两行:appendix_content1,appendix_content2。i在前插入
#取代和显示(以行为单位)
nl /etc/passwd|sed '2,5c No 2-5 number' #用’No 2-5 number‘取代2-5行
#下面这个功能很有用。注意-n不可少,否则会出现多次输出5-7行。
nl /etc/passwd | sed -n '5,7p' #列出5-7行!!! ==head -n 7|tail -n 3
cat /etc/passwd | sed -e '4d' -e '6c no six line' #两个动作,每个动作前加e。
#搜索和取代!!!
sed 's/要被取代的字串/新的字串/g' #类似vi的搜索和取代
sed 's/条件/d' filename#d是删除,s是替换
sed '1,2d' filename #删除filename的1~2行,结果显示到屏幕上
sed -i '1,2d' filename #删除filename源文件的1~2行。屏幕无显示。-i选项直接修改源文件。!!!
sed '$d' filename #删除文件最后一行
sed 's/user/USER/g' filename #g表示整个文件,s表示替换。即把filename中所有的user替换车USER
sed 's/#.*$//g' filename #替换掉每行从#开始到行末的内容。无#的行不管
sed -e 's/user/USER/g' -e 's/aaa/AAA/g' filename #同时做两种修改
sed '/user/ixxx' filename #找到含有user的行,在这些行上添加xxx。i是时在行上添加。注意添加内容不能有特殊语义。
sed '/user/axxx' filename #找到含有user的行,在这些行下面添加xxx。a是在行下添加。注意添加内容不能有特殊语义。
sed '/user/cxxx' filename #找到含有user的行,把此行替换为xxx
也可把多个操作写在一个文件里,然后执行 sed -f filename。
这个文件filename的内容可以如下:
s/root/ROOT/g
s/bin/BIN/g
/adm/cxxx
#利用sed筛选出机器的IP
/sbin/ifconfig ens33 #会列出网卡ens33的信息。包含本机IP。
/sbin/ifconfig ens33|grep 'inet ' #在上一步的基础上,挑选出含IP地址的行
/sbin/ifconfig ens33|grep 'inet '|sed 's/.*inet //g' #在上一步的基础上,将"xxx..inet "这块替换为空。xxx..是inet前的任意文本。注意后面有一个空格。
/sbin/ifconfig ens33|grep 'inet '|sed 's/.*inet //g'|sed 's/ .*$//g' #在上一步的基础上,将空格开头及其以后的的所有文本删除。
#实际上,被替换内容中的$符合可以不要,但严格来说,此时的结果末尾有一个断行符$,只是无法看到。可以用cat -A查看到。
#从上面可以看出.*这个组合十分有用
延申正则表达式
grep -E可以使用扩展的正则表达式,但为了区分,直接用egrep(与grep -E类似别名的关系)即可。相比基础正则表达式,就是多了几个符号而已。如下:
注意:以下搜索需要用egrep(grep -E)
命令
符号 | 说明 |
---|---|
+ | 匹配前一个字符一次或一次以上。egrep ‘go+d’ filename ,匹配god,good,goood… |
? | 匹配前一个字符0次或1次。如egrep ‘go?d’ filename,匹配gd,god |
| | 逻辑或。egrep ‘god|gd’ filename,匹配god或gd。逻辑或两侧的条件可以是正则表达式 |
() | 括起几个串中的不同部分。egrep ‘g(o|oo)d’ filename ,匹配god,good |
()+ | 多个重复群组的筛选。egrep ‘a(xyz)+c’ filename,匹配a开头,c结尾,中间至少有一个xyz的串 |
从上可以看出,!并不是正则表达式中的特殊字符。也不能做反向选择。需要注意。
文件的格式化和相关处理
格式化打印:printf
语法:
printf '打印格式' 实际内容
选项与参数:
格式方面的几个特殊样式:
\a 警告声输出
\b 倒退键(backspace)
\f 清除屏幕(form feed)
\n 输出新的一行
\r 即Enter按键
\t 水平的tab按键
\v 垂直的tab按键
\xNN NN为两位数的数字,可以转换数字成为字符
关于c程序里,常见的变量格式
%ns n是数字,s是string,亦即多少个字符
%ni n是数字,i代表integer,亦即多少数字
%N.nf n和N都是数字,f代表floating(浮点),如果有小数时。如有10个位数,2位小数,则为%10.2f
#实例
#如下一段文本,保持在当前目录的printf.txt文件中。
Name Chinese English Math Average
DmTsai 80 60 92 77.33
VBird 75 55 80 70.00
Ken 60 90 70 73.33
#如何整齐的输出呢?
printf '%s\t %s\t %s\t %s\t %s\t \n' $(cat printf.txt)
Name Chinese English Math Average
DmTsai 80 60 92 77.33
VBird 75 55 80 70.00
Ken 60 90 70 73.33
#printf不是管线命令,所以需要将文件内容提出来给printf作为后续的数据才行。
#上例我们将每个数据以“tab+一个空格”的形式分隔。但由于每个字段长度不固定,导致对齐效果不好。
#所以我们将每个字段所占长度固定
printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt | grep -v Name)
DmTsai 80 60 92 77.33
VBird 75 55 80 70.00
Ken 60 90 70 73.33
#上例中删除掉Name行,然后对每列所占的长度进行设定。
#%10s代表长度为10的串;%5i代表长度为5的数;%8.2f代表小数位数为2,8是整数的长度。同C。
awk:好用的数据处理工具
awk可以从文件种找到符合需求的内容,重新输出。awk倾向于将一行分成数个字段来处理,默认的字段分割符号是空白键和tab键。awk可以处理前一个指令的标准输出,也可以处理后续接的文件。awk是管线命令。
#简单示例
last -n 5 | awk '{print $1 "\t" $3}' #last取出登录者信息的5条,大括号里是操作:输出第一和第三个字段,以tab键分开。
lib 10.32.8.236
lib 10.32.8.236
lib 10.32.8.236
lib 10.32.8.236
lib 10.32.8.236
wtmp Thu
#上述的awk使用时,没有条件操作,则处理所有内容。
#可以看到最后一行有些奇怪,这是因为数据格式的问题。因此在使用awk时,若是连续的数据,请不要有空格和tab,否则就会出现上面的结果。
-
处理方式:
1.读取一行,分成几个字段放入$0,$1…等变量中。($0存放一整行,$1存放第一个字段…)
2.根据条件判断是否进行后面的操作
3.若条件满足,则进行操作。否则跳转步骤4
4.若还有后续的行,重复1~3步,直至所有行处理完为止。 -
awk内置的变量
变量名 | 含义 |
---|---|
NF | 每一行($0)拥有的字段总数 |
NR | 目前awk所处理的是"第几行"数据 |
FS | 目前的分割字符,默认是空白键 |
注意:
- $0,$1…在awk 的括号内(即动作部分)才有效。
- awk后面的动作用单引号包裹(这是awk的固定用法)。所以awk的格式内容若要以printf打印出来,则对于非变量的文字部分以及printf提到的格式,都需要用双引号。
awk处理特点:以行为一次处理的单位(逐行处理),字段为最小的处理单位。
awk '条件1{操作1} 条件2{操作2}...' filename #条件缺失,则对全文操作。多个操作用分号隔开。
awk -F: '{print $1}' filename #查看整个文件的第一个部分。-F指明分隔符时冒号。$0是整行。
awk -F: '$3<10 {print $1}' filename #第三部分小于10的行进行上述操作。
date | awk -F' ' '{print "year:"$6 "\n" "month:" $2}' #此句可按c语法去理解
last -n 5| awk '{print $1 "\t lines: " NR "\t columns: " NF}' #NR行号,NF每行有几个字段。
- 条件可以使用的逻辑运算符
类似C:>=,==,!=…
#/etc/passwd文件以:为分割符;第一字段为账户,第三个字段为UID。如要获取第三个字段小于4的行
#{FS=":"}设置分割符为:
cat /etc/passwd | awk '{FS=":"};$3<4{print $1 "\t " $3}'
root:x:0:0:root:/root:/bin/bash
daemon 1
bin 2
sys 3
#发现第一行没有正确显示出来。这是因为我们读入第一行是,哪些$1,$2...默认是以空白键分割的,虽然定义了冒号为分隔符,但却仅能在第二行后开始生效。
#因此可以利用BDGIN这个关键字预先设置awk的变量。
cat /etc/passwd | awk 'BEGIN {FS=":"};$3<4{print $1 "\t " $3}'
root 0
daemon 1
bin 2
sys 3
#利用awk实现计算
#将下面文件保存为pay.txt
Name 1st 2nd 3th
VBird 23000 24000 25000
DMTsai 21000 20000 23000
Bird2 43000 42000 41000
#如何计算每个人的总额?
cat pay.txt | \
awk 'NR==1{printf "%10s %10s %10s %10s %10s\n",$1,$2,$3,$4,"Total" }\
;NR>=2{total = $2 + $3 + $4 \
;printf "%10s %10d %10d %10d %10.2f\n", $1, $2, $3, $4, total}'
Name 1st 2nd 3th Total
VBird 23000 24000 25000 72000.00
DMTsai 21000 20000 23000 64000.00
Bird2 43000 42000 41000 126000.00
#awk中多个操作用分号隔开。{}前的是条件,{}里面的是操作。
#每行输出时,务必加上"\n"
#不同于bash shell,在awk中,变量可以直接用,不用$符号
awk场合printf结合使用。同时awk的动作{}可以有if条件。如:
cat pay.txt | \
awk '{if(NR==1) printf "%10s %10s %10s %10s %10s\n",$1,$2,$3,$4,"Total"}\
;NR>=2{total = $2 + $3 + $4 \
;printf "%10s %10d %10d %10d %10.2f\n", $1, $2, $3, $4, total}'
文件比较工具
diff
diff以行为单位对比ASCII纯文本文件。通常对比的是文件(软件)的新旧版本,也可以对比两个目录。
diff [-bBi] 被对比文件 目标文件
参数说明:
-b:忽略一行中,极有多规格空白的差异。(例如 "about me" 与 "about me"认为一样。)
-B:忽略空白行的差异
-i:忽略大小写的不同
diff filename1 filename2 #比较两个文件
diff pay.txt pay1.txt
2d1 #左边第二行被删除(d)了
< VBird 23000 24000 25000 #被删除的内容
4c3,4 #左边的第四行被右边的三四行取代(c):右边添加了一行
< Bird2 43000 42000 41000
---
> ird2 43000 42000 41000
> ddd
diff directory1 directory2 #比较两个目录
#对比不同的开机执行等级。执行等级0和5的启动脚本分别放在/etc/rc0.d 及 /etc/rc5.d
diff /etc/rc0.d/ /etc/rc5.d/
cmp
diff逐行对比,cmp以字节为单位对比,自然可以对比二进制文件。
cmp [-l] file1 file2
-l:将所有不同点的自己都列出来。默认会列出第一个不同的点。
cmp pay.txt pay1.txt
pay.txt pay1.txt differ: byte 18, line 2
patch
和diff密不可分。diff可以比较不同。若要将旧的文件升级为新的恩济,就需要在比较后,将差异制作成补丁文件,再由补丁更新旧文件即可。默认可能没有安装patch,Ubuntu可以执行sudo apt install patch
安装。
diff -Naur pay1.txt pay.txt>pay.patch
cat pay.patch
--- pay1.txt 2022-04-15 04:12:05.072752920 +0000
+++ pay.txt 2022-04-15 03:40:50.075113263 +0000
@@ -1,4 +1,4 @@
Name 1st 2nd 3th
+VBird 23000 24000 25000
DMTsai 21000 20000 23000
-ird2 43000 42000 41000
-ddd
+Bird2 43000 42000 41000
#如何将pay1.txt的内容更新为(升级)pay.txt?
patch -pN < patch_file #更新
patch -R -pN < patch_file #还原
选项与参数:
-p :后面可以接“取消几层目录”的意思。
-R :代表还原,将新的文件还原成原来旧的版本。
#将刚刚制作出来的 patch file 用来更新旧版数据
patch -p0<passwd.patch
patching file passwd.old
#恢复旧文件的内容
patch -R -p0 < passwd.patch
[dmtsai@study testpw]$ ll passwd*
#文件就这样恢复成为旧版本啰
文件打印准备
pr /etc/man_db.conf #打印出man_db.conf