第十二章 正则表达式与文件格式化处理

本文介绍了正则表达式的基础知识及其在Linux系统管理中的应用,包括grep和sed工具的使用。此外,还探讨了文件的格式化处理,如printf和awk的运用,以及文件比较工具diff、cmp和patch的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述:什么是正则表达式

什么是正则表达式

正则表达式 (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 &lt; patch_file #还原
选项与参数:
-p :后面可以接“取消几层目录”的意思。
-R :代表还原,将新的文件还原成原来旧的版本。

#将刚刚制作出来的 patch file 用来更新旧版数据
 patch -p0<passwd.patch
patching file passwd.old
#恢复旧文件的内容
patch -R -p0 &lt; passwd.patch
[dmtsai@study testpw]$ ll passwd*
#文件就这样恢复成为旧版本啰

文件打印准备

pr /etc/man_db.conf  #打印出man_db.conf
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值