Shell 编程之文本处理三剑客与正则表达式

文章目录

  • 1. 正则表达式
    • 1.1 正则表达式概述
    • 1.2 基本正则表达式元字符
      • (1) 字符匹配
      • (2) 匹配次数
      • (3) 位置锚定
      • (4) 分组
      • (5) 或者
      • (6) 非打印字符
    • 1.3 扩展正则表达式
      • (1) 字符匹配
      • (2) 次数匹配
      • (3) 位置锚定
      • (4) 其它
  • 2. 文本处理三剑客
    • 2.1 文本处理三剑客之 grep
    • 2.2 文本处理三剑客之 sed
      • (1) sed 概述
      • (2) sed 基本用法
      • (3) sed 高级用法
    • 2.3 文本处理三剑客之 awk
      • (1) awk 概述
      • (2) 动作 print
      • (3) awk变量
        • ① 内置变量
        • ② 自定义变量(区分字符大小写)
      • (4) 动作 printf
      • (5) 操作符
        • ① 算术操作符
        • ② 字符串操作符
        • ③ 赋值操作符
        • ④ 比较操作符
        • ⑤ 模式匹配符
        • ⑥ 逻辑操作符
        • ⑦ 条件表达式(三目表达式)
      • (6) 模式 PATTERN
        • ① 空模式
        • ② regular expression
        • ③ relational expression
        • ④ line ranges
        • ⑤ BEGIN/END 模式
      • (7) 条件判断 if-else
      • (8) switch 语句
      • (9) while 循环
      • (10) do-while 循环
      • (11) for 循环
      • (12) continue 和 break
      • (13) next
      • (14) 数组
      • (15) awk 函数
        • ① 常见内置函数
        • ② 自定义函数
      • (16) awk 脚本
  • 3. 其他文本处理工具
    • 3.1 cut
    • 3.2 tr
    • 3.3 sort
    • 3.4 uniq
    • 3.5 分类


1. 正则表达式

1.1 正则表达式概述

  REGEXP(Regular Expressions)由一类特殊字符及文本字符所编写的模式,其中有些字符(元字符)不表示字符字面意义,而表示控制或通配的功能,类似于增强版的通配符功能,但与通配符不同,通配符功能是用来处理文件名,而正则表达式是处理文本内容。

  • 正则表达式被很多程序和开发语言所广泛支持
vim,less,grep,sed,awk,nginx,mysql 等
  • 正则表达式分两类
基本正则表达式	BRE
扩展正则表达式	ERE
  • 正则表达式引擎
采用不同算法,检查处理正则表达式的软件模块,如:PCRE(Perl Compatible Regular Expressions)
  • 正则表达式的元字符分类
字符匹配、匹配次数、位置锚定、分组
  • 帮助
man 7 regex

1.2 基本正则表达式元字符

(1) 字符匹配

.   	匹配任意单个字符,可以是一个汉字
[]   	匹配指定范围内的任意单个字符,示例:[abcd] [0-9] [a-z] [a-zA-Z]
[^] 	匹配指定范围外的任意单个字符,即取反。注意 ^ 在括号内,在括号外代表行首,示例:[^abcd]
[:alnum:] 	字母和数字
[:alpha:] 	代表任何英文大小写字符,亦即 A-Z,a-z
[:lower:] 	小写字母,示例:[[:lower:]],相当于[a-z]
[:upper:] 	大写字母
[:blank:] 	空白字符(空格和制表符)
[:space:] 	水平和垂直的空白字符(比[:blank:]包含的范围广)
[:cntrl:] 	不可打印的控制字符(退格、删除、警铃...)
[:digit:] 	十进制数字
[:xdigit:]	十六进制数字
[:graph:] 	可打印的非空白字符
[:print:] 	可打印字符

示例

[root@c7-1 ~]#ll /etc/ | grep 'rc[.0-9]'
lrwxrwxrwx.  1 root root       11 92 09:31 init.d -> rc.d/init.d
lrwxrwxrwx.  1 root root       10 92 09:31 rc0.d -> rc.d/rc0.d
lrwxrwxrwx.  1 root root       10 92 09:31 rc1.d -> rc.d/rc1.d
lrwxrwxrwx.  1 root root       10 92 09:31 rc2.d -> rc.d/rc2.d
lrwxrwxrwx.  1 root root       10 92 09:31 rc3.d -> rc.d/rc3.d
lrwxrwxrwx.  1 root root       10 92 09:31 rc4.d -> rc.d/rc4.d
lrwxrwxrwx.  1 root root       10 92 09:31 rc5.d -> rc.d/rc5.d
lrwxrwxrwx.  1 root root       10 92 09:31 rc6.d -> rc.d/rc6.d
drwxr-xr-x. 10 root root      127 92 09:32 rc.d
lrwxrwxrwx.  1 root root       13 92 09:32 rc.local -> rc.d/rc.local

(2) 匹配次数

用在要指定次数的字符后面,用于指定前面的字符要出现的次数

* 		匹配前面的字符任意次,包括 0 次
.* 		任意长度的任意字符
\? 		匹配其前面的字符 01 次,即:可有可无
\+ 		匹配其前面的字符至少 1 次,即:>=1
\{n\} 	匹配前面的字符 n 次
\{m,n\} 匹配前面的字符至少 m 次,至多 n 次
\{,n\} 	匹配前面的字符至多 n 次,<=n
\{n,\} 	匹配前面的字符至少 n 次

示例:

[root@c7-1 ~]#cat /etc/passwd | grep 'ro\{2\}'
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
[root@c7-1 ~]#echo /etc | grep '/etc/\?'
/etc
[root@c7-1 ~]#echo /etc/ | grep '/etc/\?'
/etc/

(3) 位置锚定

位置锚定可以用于定位出现的位置

^	行首锚定,用于模式的最左侧
$	行尾锚定,用于模式的最右侧
^$	空行
^[[:space:]]*$	空行或包含空白字符的行
^PATTERN$ 	用于模式匹配整行
\<\b 	匹配单词边界,表示锚定词首,其后面的字符必须作为单词首部出现
\>\b 	匹配单词边界,表示锚定词尾,其前面的字符必须作为单词尾部出现
\<PATTERN\> 匹配整个单词

示例

#查看 /etc/profile 文件内容,排除 # 开头的行和空白行
cat /etc/profile | grep -v "^#" | grep -v "^$"
grep -v "^$" /etc/profile | grep -v "^#"

#去除文本中的空格
[root@c7-1 /data]#cat 1.txt 
zhangsan
ds	sdds
lisi
[root@c7-1 /data]#sed 's/[[:space:]]//g' 1.txt 
zhangsan
dssdds
lisi

位置锚定参考:
linux 中的正则表达式-位置匹配

(4) 分组

分组

() 将多个字符捆绑在一起,当作一个整体处理,如:\(root\)+

后向引用

分组括号中的模式匹配到的内容会被正则表达式引擎记录于内部的变量中,这些变量的命名方式为: \1, \2, \3,...
\1 表示从左侧起第一个左括号以及与之匹配右括号之间的模式所匹配到的字符

注意: 后向引用引用前面的分组括号中的模式所匹配字符,而非模式本身

示例

\(string1\(string2\)\)
\1 :string1\(string2\)
\2 :string2

(5) 或者

或者:\|

示例:

a\|b	# a 或 b  
C\|cat	# C 或 cat  
\(C\|c\)at	# Cat 或 cat

(6) 非打印字符

\n	匹配一个换行符
\r	匹配一个回车符
\t	匹配一个制表符

1.3 扩展正则表达式

(1) 字符匹配

. 			任意单个字符
[abc...]	指定范围的字符
[^abc...]	不在指定范围的字符
[:alnum:]	字母和数字
[:alpha:]	代表任何英文大小写字符,亦即 A-Z, a-z
[:lower:]	小写字母,示例:[[:lower:]],相当于[a-z]
[:upper:]	大写字母
[:blank:]	空白字符(空格和制表符)
[:space:]	水平和垂直的空白字符(比[:blank:]包含的范围广)
[:cntrl:] 	不可打印的控制字符(退格、删除、警铃...)
[:digit:] 	十进制数字
[:xdigit:]	十六进制数字
[:graph:] 	可打印的非空白字符
[:print:] 	可打印字符
[:punct:] 	标点符号

(2) 次数匹配

*   匹配前面字符任意次
? 	01 次
+ 	1 次或多次
{n} 	匹配 n 次
{m,n} 	至少 m,至多 n 次
{m,}	至少 m 次
{,n}	至多 n 次

(3) 位置锚定

^	行首
$	行尾
\<\b	语首,从匹配正则表达式的行开始
\>\b	语尾,到匹配正则表达式的行结束

(4) 其它

()	分组
后向引用	\1\2 ...
| 	或者
a|b 	# a 或 b
C|cat 	# C 或 cat
(C|c)at # Cat 或 cat

2. 文本处理三剑客

2.1 文本处理三剑客之 grep

作用:文本搜索工具,根据用户指定的 “模式” 对目标文本逐行进行匹配检查,打印匹配到的行。

格式

grep [选项] [匹配模式] [文件]

常用选项

-m	匹配指定次数后停止
-v	显示不被模式匹配到的行,取反
-i	忽略字符大小写
-n	显示行号
-c	统计匹配的行数
-o	仅显示匹配到的字符串
-q	静默模式,不输出任何信息
-e	实现多个选项间的逻辑 or 关系,如:grep -e "cat" -e "dog" file
-w	匹配整个单词
-E	使用扩展正则表达式,相当于 egrep
-F	不支持正则表达式,相当于 fgrep
-r	递归目录,但不处理软链接
-R	递归目录,但处理软链接
-f file	根据模式文件处理
-A n	after, 后 n 行
-B n	before, 前 n 行
-C n	context, 前后各 n 行

示例

#查询 /etc/passwd 文件中有 root 的行
[root@c7-1 ~]#grep "root" /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

#查看 /etc/profile 文件内容,排除 # 开头的行和空白行
[root@c7-1 ~]#grep -v "^$" /etc/profile | grep -v "^#"
pathmunge () {
    case ":${PATH}:" in
        *:"$1":*)
............

#使用扩展正则表达式实现,排除 # 开头的行和空白行
[root@c7-1 ~]#grep -Ev "^(#|$)" /etc/profile


#查看指定的主机磁盘使用率
[root@c7-1 ~]#df -h | grep "^/dev/sd*" | awk '{print $1,$5}'
/dev/sda2 11%
/dev/sda5 1%
/dev/sda1 18%
/dev/sr0 100%

#查看与本机建立连接的 IP 前三个
[root@c7-1 ~]#ss -nt | grep "^ESTAB" | tr -s ' ' : | cut -d: -f4 | sort | uniq -c | sort -nr | head -n3
      1 192.168.10.20

#只显示模式匹配到的内容
[root@c7-1 ~]#grep -o 'r..t' /etc/passwd
root
root
root
root
r/ft

#显示当 IP
[root@c7-1 ~]#ifconfig ens33 | grep -Eo '([0-9]{1,3}\.){3}([0-9]{1,3})' | head -1
192.168.10.20

#多个模式条件匹配
[root@c7-1 ~]#grep -e "root" -e "/bin/bash" /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
syhj:x:1000:1000:syhj:/home/syhj:/bin/bash

#位置锚定,匹配行首和行尾单词相同的行
[root@c7-1 ~]#grep "^\(.*\)\>.*\<\1$" /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

#扩展正则表达式实现,匹配行首和行尾单词相同的行
[root@c7-1 ~]#grep -E "^(.*)\>.*\<\1$" /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

#算出所有人年龄总和
[root@centos8 ~]#cat /data/nianling.txt
xiaoming=20
xiaohong=18
xiaoqiang=22
[root@centos8 ~]#cut -d"=" -f2 /data/nianling.txt|tr '\n' + | grep -Eo ".*[0-9]"|bc
60
[root@centos8 ~]#grep -Eo "[0-9]+" /data/nianling.txt | tr '\n' + | grep -Eo ".*[0-9]"|bc
60

2.2 文本处理三剑客之 sed

(1) sed 概述

  • 文本处理工具,读取文本内容,根据指定的条件进行处理,如删除、替换、添加等
  • 可在无交互的情况下实现相当复杂的文本处理操作
  • 被广泛应用于 Shell 脚本,以完成自动化处理任务
  • sed 依赖于正则表达式
  • 工作原理
读取:sed 从输入流(文件、管道、标准输入)中读取一行内容并存储到临时的缓冲区中(又称模式空间,pattern space)
执行:默认情况下,所有的 sed 命令都在模式空间中顺序地执行,除非指定行的地址,否则 sed 命令将会在所有的行上依次执行
显示:发送修改后的内容到输出流。在发送数据后,模式空间将会被清空。

Shell 编程之文本处理三剑客与正则表达式_第1张图片

  如果使用 vi 命令打开几十 M 上百 M 的文件,明显会出现有卡顿的现象,这是因为 vi 命令打开文件是一次性将文件加载到内存,然后再打开。sed 就避免了这种情况,一行一行的处理,打开速度非常快,执行速度也很快。

(2) sed 基本用法

格式

sed [命令选项] '操作1;操作2......' [文件]
sed [命令选项] -f scriptfile [文件]

常用命令选项

-e	多点编辑,多个匹配模式组合
-f	表示用指定的脚本文件来读取操作命令
-n  表示仅显示处理后的结果
-i	直接编辑文本文件
-i.bak	备份文件并原处编辑
-r/-E 	使用扩展正则表达式

操作选项

a	增加,在当前行下面增加一行指定内容,支持使用 \n 实现多行追加
i	插入,在选定行上面插入一行指定内容
c	替换,将选定行替换为指定内容
d	删除,删除选定的行
H	复制到剪贴板
w /PATH/file	保存模式匹配的行至指定文件
r /PATH/file	读取指定文件的文本至模式空间中匹配到的行后
p	打印,如果同时指定行,表示打印指定行;如果不指定行,则表示打印所有内容;如果有非打印字符,则以 ASCII 码输出。其通常与 "-n" 选项一起使用
Ip	忽略大小写输出
y	字符转换,和 s 用法类似,但只能替换大小写
=	为模式空间中的行打印行号
!	模式空间中匹配行取反处理

s	替换,替换指定字符,格式 s/pattern/string/修饰符
g	行内全局替换
p	显示替换成功的行
I/i	忽略大小写
w /PATH/FILE	将替换成功的行保存至文件中

地址格式

不给地址则对全文进行处理

单地址:	
"n"		指定的行
"$"		最后一行
/pattern/	被此处模式所能够匹配到的每一行

地址范围:
"m,n"   从第 m 行到第 n 行
"m,+n"  从第 m 行到第 m+n 行	# 3,+4 表示从第 3 行到第 7 行

步进:~ 第一个数为起始行后一个数字为前进步数
1~2 奇数行,cat /etc/passwd | sed -n "1~2p"
2~2 偶数行,cat /etc/passwd | sed -n "2~2p"

参考:
sed 地址定位方式
sed 用法总结及演示
sed 命令详解

示例:

#模式空间输出一次然后 p 显示一次,所以输出两遍
[root@c7-1 ~]#sed 'p' /etc/issue
\S
\S
Kernel \r on an \m
Kernel \r on an \m

#使用了 -n ,表示仅显示处理后的结果,所以输出一遍
[root@c7-1 ~]#sed -n 'p' /etc/issue
\S
Kernel \r on an \m

#输出 /etc/passwd 第一行内容,如果不加 -n 则会显示所有内容
[root@c7-1 ~]#sed -n '1p' /etc/passwd
root:x:0:0:root:/root:/bin/bash

#显示 IP,子网掩码,广播地址
[root@c7-1 ~]#ifconfig ens33 | sed -n '2p'
        inet 192.168.10.20  netmask 255.255.255.0  broadcast 192.168.10.255

#根据条件匹配指定行
[root@c7-1 ~]#ifconfig ens33 | sed -n '/netmask/p'
        inet 192.168.10.20  netmask 255.255.255.0  broadcast 192.168.10.255

#显示 /etc/passwd 尾行内容
[root@c7-1 ~]#cat /etc/passwd | sed -n '$p'
syhj:x:1000:1000:syhj:/home/syhj:/bin/bash

#显示匹配到的磁盘信息
[root@c7-1 ~]#df -h | sed -n '/^\/dev\/sda*/p'
/dev/sda2        50G  5.2G   45G   11% /
/dev/sda5        44G   33M   44G    1% /data
/dev/sda1      1014M  179M  836M   18% /boot

[root@c7-1 ~]#seq 10 | sed -n '3,5p'
3
4
5

[root@c7-1 ~]#seq 10 | sed -n '3,+2p'
3
4
5

[root@c7-1 ~]#seq 10 | sed -n '8,$p'
8
9
10
 
#输出奇数,等于 seq 6 | sed '2~2d'
[root@c7-1 ~]#seq 6 | sed -n '1~2p'
1
3
5

#输出偶数,等于 seq 6 | sed '1~2d'
[root@c7-1 ~]#seq 6 | sed -n '2~2p'
2
4
6

#删除第二行和第四行
[root@c7-1 ~]#seq 6 | sed '2d;4d'
1
3
5
6

#备份 seq.log 为 seq.log.bak,并编辑 seq.log 文件
sed -i.bak '2d;4d' seq.log

#删除空行
sed -i '/^$/d' test.log

#替换每行第二个 o 为 O
sed -i 's/o/O/2' test.log 

#每行插入 # 号
sed -i 's/^/#/' test.log

#将匹配到的 the 每行开头插入 #
sed -i '/the/s/^/#/' test.log

#每行的行尾添加 EOF
sed -i 's/$/EOF/' test.log

#替换 3 到 5 行所有 the
sed -i '3,5s/the/THE/g' test.log

#多条件组合
[root@c7-1 ~]#cat /etc/passwd | sed -n -e '/root/p' -e '/syhj/p'
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
syhj:x:1000:1000:syhj:/home/syhj:/bin/bash

#关闭 selinux,-i 直接编辑文件,不输出信息到屏幕
sed -i 's/enforcing/disabled/g' /etc/selinux/config && reboot

#删除所有以 # 开头的行
sed -i '/^#/d' test.log

#只显示非 # 开头的行
sed -n '/^#/!p' test.log

#查看本机 IP,-r 为使用扩展正则表达式
ifconfig ens33 | sed -nr "2s/[^0-9]+([0-9.]+).*/\1/p"
ifconfig ens33 | sed -nr '2s/^[^0-9]+([0-9.]+).*$/\1/p'
ifconfig ens33 | sed -n '2s/^.*inet //;s/ netmask.*//p'

#取基名
echo "/etc/sysconfig/network-scripts/" | sed -r 's#(^/.*/)([^/]+/?)#\2#'
#取目录名
echo "/etc/sysconfig/network-scripts/" | sed -r 's#(^/.*/)([^/]+/?)#\1#'

#将 # 开头的行删除 #
sed -ri.bak '/^#/s/^#//' test.log

#取分区利用率
df | sed -nr '/^\/dev\/sd/s# .* ([0-9]+)%.*# \1#p'

#提取 file 文件中包含 the 的行,存放在 other.file 文件中
sed '/the/w other.file' file

#读取 /etc/hostname 文件内容,将其添加到 file 文件中所有包含 the 的行后
sed '/the/r /etc/hostname' file

#在 file 文件的第三行后添加 NEW
sed '3aNEW' file

#在 file 文件结尾添加 NEW 行
sed '$aNEW' file

#在所有包含 the 的行后加 NEW
sed '/the/aNEW' file

#第三行后添加 NEW1 NEW2,\n 换行
sed '3aNEW1\nNEW2' file

# -f 调用脚本实现操作
sed -f script.list file

FTP 服务控制:

#!/bin/bash
#用来调整 vsftpd 服务配置,要求禁止匿名用户,但允许本地用户(也允许写入)
systemctl stop firewalld && systemctl disable firewalld
setenforce 0
pgrep vsftpd
[ $? -ne 0 ] && yum -y install vsftpd

#指定样本文件路径,配置文件路径,定义两个变量
SAMPLE="/usr/share/doc/vsftpd-3.0.2/EXAMPLE/INTERNET_SITE/vsftpd.conf"
CONFIG="/etc/vsftpd/vsftpd.conf"

#备份原配置文件,检测文件名为 /etc/vsftpd/vsftpd.conf.bak 备份文件是否存在,若不存在则使用 cp 命令进行备份
# -e 检测文件是否存在,! 取反
[ ! -e "$CONFIG.bak" ] && cp $CONFIG $CONFIG.bak

sed -e '/anonymous_enable/s/YES/NO/g' $SAMPLE > $CONFIG

sed -i -e '/^local_enable/s/NO/YES/g' -e '/^write_enable/s/NO/YES/g' $CONFIG

grep "listen" $CONFIG || sed -i '$alisten=YES' $CONFIG

#启动并设置开机自启
systemctl restart vsftpd && systemctl enable vsftpd
[ $? -eq 0 ] && echo -e "\033[32m服务开启成功\033[0m" || echo "\033[31m服务开启失败,请检测配置是否有误\033[0m"

(3) sed 高级用法

  sed 中除了模式空间之外还支持保持空间(Hold Space),利用此空间,可以将模式空间中的数据临时保存至保持空间,从而后续接着处理,实现更为丰富的功能。
参考:
sed 命令高级用法精讲
常见的高级命令:

P	打印模式空间开端至 \n 内容,并追加到默认输出之前
h	把模式空间中的内容覆盖至保持空间中
H	把模式空间中的内容追加至保持空间中
g	从保持空间取出数据覆盖至模式空间
G	从保持空间取出内容追加至模式空间
x	把模式空间中的内容与保持空间中的内容进行互换
n	读取匹配到的行的下一行覆盖至模式空间
N	读取匹配到的行的下一行追加至模式空间
d	删除模式空间中的行,也可以叫剪切
D	如果模式空间包含换行符,则删除直到第一个换行符的模式空间中的文本,并不会读取新的输入行,而使用合成的模式空间重新启动循环。如果模式空间不包含换行符,则会像发出 d 命令那样启动正常的新循环

参考:
sed 命令 n,N,d,D,p,P,h,H,g,G,x 解析

示例:

# d 剪切文档中的所有包含 the 的行,将其存入保持空间中,G 将保持空间中的内容取出放在末行 $ 之后
sed '/the/{H;d};$G' file

#将 1 到 3 行迁移到 5 行之后
sed '1,3{H;d};5G' file

#输出偶数
[root@c7-1 ~]#seq 6 | sed -n 'n;p'
2
4
6
#输出奇数
[root@c7-1 ~]#seq 6 | sed -n 'p;n'
1
3
5

[root@c7-1 ~]#seq 6 | sed -n '1,5{n;p}'
2
4
6
[root@c7-1 ~]#seq 6 | sed -n '2,5{n;p}'
3
5
[root@c7-1 ~]#seq 6 | sed -n '1,5{p;n}'
1
3
5
[root@c7-1 ~]#seq 6 | sed -n '2,5{p;n}'
2
4

sed '1!G;h;$!d' FILE
sed 'N;D' FILE
seq 10 | sed '3h;9G;9!d'
sed '$!N;$!D' FILE
sed '$!d' FILE
sed 'G' FILE
sed 'g' FILE
sed '/^$/d;G' FILE
sed 'n;d' FILE
sed -n '1!G;h;$p' FILE

2.3 文本处理三剑客之 awk

(1) awk 概述

  awk 是一种处理文本文件的语言,是一个强大的文本分析工具。之所以叫 awk 是因为其取了三位创始人 Alfred Aho,Peter Weinberger, 和 Brian Kernighan 的 Family Name 的首字符。

awk 工作原理参考:
Linux AWK 工作原理

awk 有多种版本:

AWK:原先来源于 AT & T 实验室的的 AWK
NAWK:New awk,AT & T 实验室的 AWK 的升级版
GAWK:即 GNU AWK,所有的 GNU/Linux 发布版都自带 GAWK,它与 AWK 和 NAWK 完全兼容

gawk:模式扫描和处理语言,可以实现下面功能

文本处理
输出格式化的文本报表
执行算数运算
执行字符串操作

格式:

awk [选项] '模式或条件{编辑指令}' 文件1 文件2 ......
awk -f [脚本文件] 文件1 文件2 ......

常见选项:

-F	"分隔符" 指明输入时用到的字段分隔符
-f	指定调用脚本
-v	var=value 变量赋值

说明:模式 (program) 通常是被放在单引号中,并可以由三种部分组成

BEGIN 语句块
模式匹配的通用语句块
END 语句块

program 格式:

pattern{action statements;..}
pattern:决定动作语句何时触发及触发事件,比如:BEGIN,END
action statements:对数据进行处理,放在{}内指明,常见:print, printf

分割符、域和记录:

由分隔符分隔的字段(列 column,域 field)标记 $1,$2...$n 称为域标识,$0 为所有域,注意:和 shell 中变量 $ 符含义不同
文件的每一行称为记录 record
如果省略 action,则默认执行 print $0 的操作

常用的 action 分类:

output statements:print,printf
Expressions:算术,比较表达式等
Compound statements:组合语句
Control statements:if, while 等
input statements

awk 控制语句:

{ statements;} 组合语句
if(condition) {statements;}
if(condition) {statements;} else {statements;}
while(conditon) {statments;}
do {statements;} while(condition)
for(expr1;expr2;expr3) {statements;}
break
continue
exit 

(2) 动作 print

格式:

print item1,item2, ...

逗号做分隔符,用 {} 包起来
输出的 item 可以是字符串,也可是数值,当前记录的字段、变量或 awk 的表达式
如省略 item,相当于 print $0

示例:

awk -F: '{print}' /etc/passwd	#默认 print $0
awk -F: '{print $1,$7}' /etc/passwd		#以 : 为分隔符,打印第一列和第七列
awk -F: '{print $1"\t"$7}' /etc/passwd	# \t 制表符,规范输出格式
grep "^UUID" /etc/fstab |awk '{print $2,$3}'
awk '{print $1}' /var/log/cups/access_log | sort | uniq -c | sort -nr | head
df -h | tr -s ' ' @ | awk -F@ '{print $4}'	#查看磁盘可用容量
df | awk -F"[[:space:]]+|%" '{print $5}'	#以多个空格或者 % 作为分隔符

(3) awk变量

① 内置变量

$0	代表整行
$1	代表第一列
$2	代表第二列
......
NF		一行的列数
NR		总行数
FS		输入字段分隔符,默认为空白字符,功能相当于 -F
OFS		输出字段分隔符,默认为空白字符
RS		输入记录分隔符,指定输入时的换行符
ORS		输出记录分隔符,输出时用指定符号代替换行符
FNR		各文件分别计数,记录号
ARGC	命令行参数的个数
ARGV	数组,保存的是命令行所给定的各参数
FILENAME	当前文件名

示例:

# $0
awk '{print $0}' /etc/passwd
# $1,$2...
awk -F: '{print $1,$7}' /etc/passwd
# NF
awk -F: '{print NF}' /etc/passwd
ss -nt | grep "^ESTAB" | awk -F'[[:space:]]+|:' '{print $(NF-2)}'
# NR
awk -F: '{print NR}' /etc/passwd
cat /etc/passwd | awk 'NR==2{print}'	#打印第二行
awk -F: 'NR==2{print $7}' /etc/passwd	#打印第二行第七列
# FS
awk -v FS=':' '{print $1FS$3}' /etc/passwd
awk -v FS=':' '{print $1,FS,$3}' /etc/passwd
# OFS
awk -v FS=':' -v OFS=':' '{print $1,$3,$7}' /etc/passwd
# RS
awk -v RS=' ' '{print}' /etc/passwd
# ORS
awk -v RS=' ' -v ORS='###' '{print $0}' /etc/passwd
# FNR
awk '{print NR,$0}' /etc/issue /etc/redhat-release
# ARGC
awk '{print ARGC}' /etc/issue /etc/redhat-release
awk 'BEGIN{print ARGC}' /etc/issue /etc/redhat-release
# ARGV
awk 'BEGIN{print ARGV[0]}' /etc/issue /etc/redhat-release
awk 'BEGIN{print ARGV[1]}' /etc/issue /etc/redhat-release
# FILENAME
awk '{print FILENAME}' /etc/fstab
awk '{print FNR,FILENAME,$0}' /etc/issue /etc/redhat-release

② 自定义变量(区分字符大小写)

-v var=value
在 program 中直接定义

示例:

awk  -v test='hello gawk' '{print test}' /etc/fstab
awk  -v test='hello gawk' 'BEGIN{print test}'
awk  'BEGIN{test="hello,gawk";print test}'
awk  -F: '{sex="male";print $1,sex,age;age=18}' /etc/passwd

(4) 动作 printf

printf 可以实现格式化输出

格式:

printf "FORMAT", item1, item2, ...

必须指定 FORMAT
不会自动换行,需要显式给出换行控制符,\n
FORMAT 中需要分别为后面每个 item 指定格式符

格式符:与 item 一 一对应

%c	显示字符的 ASCII 码
%f	显示为浮点数
%s	显示字符串
%u	无符号整数
%%	显示 % 自身
%d / %i	显示十进制整数
%e / %E	显示科学计数法数值
%g / %G	以科学计数法或浮点形式显示数值

修饰符:

"#[.#]"	第一个数字控制显示的宽度;第二个 "#" 表示小数点后精度,如:%3.1f
- 	左对齐(默认右对齐)	如:%-15s
+   显示数值的正负符号		如:%+d

示例:

awk -F:	'{printf "%s",$1}' /etc/passwd
awk -F: '{printf "%s\n",$1}' /etc/passwd
awk -F: '{printf "%-20s %10d\n",$1,$3}' /etc/passwd
awk -F: '{printf "Username: %s\n",$1}' /etc/passwd
awk -F: '{printf "Username: %sUID:%d\n",$1,$3}' /etc/passwd
awk -F: '{printf "Username: %25sUID:%d\n",$1,$3}' /etc/passwd
awk -F: '{printf "Username: %-25sUID:%d\n",$1,$3}' /etc/passwd

(5) 操作符

① 算术操作符

x+y, x-y, x*y, x/y, x^y, x%y
-x:转换为负数
+x:将字符串转换为数值

② 字符串操作符

没有符号的操作符,字符串连接

③ 赋值操作符

=, +=, -=, *=, /=, %=, ^=,++, --

示例:

[root@centos8 ~]#awk 'BEGIN{i=0;print ++i,i}'
1 1
[root@centos8 ~]#awk 'BEGIN{i=0;print i++,i}'
0 1

④ 比较操作符

==, !=, >, >=, <, <=

示例:

seq 10 | awk 'i=0'
seq 10 | awk 'i=1'
seq 10 | awk 'i=!i'
seq 10 | awk '{i=!i;print i}'
seq 10 | awk '!(i=!i)'              
seq 10 | awk -v i=1 'i=!i'

⑤ 模式匹配符

~	左边是否和右边匹配,包含关系
!~	是否不匹配

示例:

[root@centos8 ~]#awk -F: '$0 ~ /root/{print $1}' /etc/passwd
[root@centos8 ~]#awk -F: '$0 ~ "^root"{print $1}' /etc/passwd
[root@centos8 ~]#awk '$0 !~ /root/' /etc/passwd
[root@centos8 ~]#awk '/root/' /etc/passwd
[root@centos8 ~]#awk -F: '$3==0' /etc/passwd
[root@centos8 ~]#df | awk -F"[[:space:]]+|%" '$0 ~ /^\/dev\/sd/{print $5}'
5
1
92
[root@centos8 ~]#ifconfig eth0 | awk 'NR==2{print $2}'
10.0.0.8

⑥ 逻辑操作符

&&||!

示例:

awk -F:	'$3>=0 && $3<=1000 {print $1}' /etc/passwd
awk -F: '$3==0 || $3>=1000 {print $1}' /etc/passwd
awk -F: '!($3==0) {print $1}' /etc/passwd
awk -F: '!($3>=500) {print $3}' /etc/passwd

⑦ 条件表达式(三目表达式)

selector?if-true-expression:if-false-expression

示例:

awk -F: '{$3>=1000?usertype="Common User":usertype="SysUser";printf "%-20s:%12s\n",$1,usertype}' /etc/passwd

(6) 模式 PATTERN

根据 pattern 条件,过滤匹配的行,再做处理

① 空模式

如果未指定意为空模式,匹配每一行

示例:

awk -F: '{print $1,$3}' /etc/passwd

② regular expression

/regular expression/:仅处理能够模式匹配到的行,需要用 / / 括起来

示例:

awk '/^UUID/{print $1}' /etc/fstab
awk '!/^UUID/{print $1}' /etc/fstab
df | awk '/^\/dev\/sd/'

③ relational expression

relational expression: 关系表达式,结果为 “真” 才会被处理

真:结果为非 0 值,非空字符串
假:结果为空字符串或 0

示例:

awk	-F: 'i=1;j=1{print i,j}' /etc/passwd
awk	'!0' /etc/passwd;awk '!1' /etc/passwd
awk	-F: '$3>=1000{print $1,$3}' /etc/passwd
awk	-F: '$3<1000{print $1,$3}' /etc/passwd
awk -F: '$NF=="/bin/bash"{print $1,$NF}' /etc/passwd
awk -F: '$NF ~ /bash$/{print $1,$NF}' /etc/passwd

④ line ranges

line ranges:行范围

/pat1/,/pat2/ 不支持直接给出数字格式

示例:

awk '/^bin/,/^adm/' /etc/passwd
sed -n '/^bin/,/^adm/p' /etc/passwd

awk 'NR>=3 && NR<=6{print NR,$0}' /etc/passwd
sed -n '3,6p' /etc/passwd

⑤ BEGIN/END 模式

BEGIN{}:仅在开始处理文件中的文本之前执行一次
END{}:仅在文本处理完成之后执行一次

示例:

awk -F: 'BEGIN {print "USER USERID"} {print $1":"$3} END{print "END FILE"}' /etc/passwd
awk -F: '{print "USER USERID";print $1":"$3} END{print "END FILE"}' /etc/passwd
awk -F: 'BEGIN{print "USER UID \n--------------- "}{print $1,$3}' /etc/passwd
awk -F: 'BEGIN{print "USER UID \n------"} {print $1,$3} END{print "========"}' /etc/passwd

调用函数 getline,读取一行数据的时候并不是得到当前行而是当前行的下一行

df -h | awk 'BEGIN{getline}/root/{print $0}'
seq 10 | awk '{getline;print $0}'   #显示偶数行
seq 10 | awk '{print $0;getline}'   #显示奇数行

(7) 条件判断 if-else

使用场景:对 awk 取得的整行或某个字段做条件判断

语法:

if(condition){statement;}[else statement]
if(condition1){statement1}else if(condition2){statement2}else{statement3}

示例:

awk -F: '{if($3>=1000)print $1,$3}' /etc/passwd
awk -F: '{if($NF=="/bin/bash") print $1}' /etc/passwd
awk '{if(NF>5) print $0}' /etc/fstab
df | awk -F"[[:space:]]+|%" '/^\/dev\/sd/{if($5>80)print $1,$5}'
df -h | awk -F% '/^\/dev\/sd/{print $1}' | awk '$NF>=80{print $1,$5}'
awk -F: '{if($3>=1000) {printf "Common user: %s\n",$1} else {printf "root or Sysuser: %s\n",$1}}' /etc/passwd
awk -F: '{if($3>=1000) printf "Common user: %s\n",$1; else printf "root or Sysuser: %s\n",$1}' /etc/passwd
awk 'BEGIN{ test=100;if(test>90){print "very good"} else if(test>60){ print "good"} else{print "no pass"}}'

(8) switch 语句

语法:

switch(expression) {case VALUE1 or /REGEXP/: statement1; case VALUE2 or /REGEXP2/: statement2; ...; default: statementn}

(9) while 循环

使用场景:

对一行内的多个字段逐一类似处理时使用
对数组中的各元素逐一处理时使用

语法:

while (condition) {statement;}

条件 "真",进入循环
条件 "假",退出循环

示例:

awk 'BEGIN{print length("hello")}'
awk 'BEGIN{print length("中华人民共和国")}'
awk '/^[[:space:]]*linux16/{i=1;while(i<=NF){print $i,length($i); i++}}' /etc/grub2.cfg
awk 'BEGIN{ total=0;i=1;while(i<=100){total+=i;i++};print total}'

(10) do-while 循环

语法:

do {statement;}while(condition)

无论真假,至少执行一次循环体

示例:

awk 'BEGIN{ total=0;i=1;do{ total+=i;i++;}while(i<=100);print total}'

(11) for 循环

语法:

for(expr1;expr2;expr3) {statement;}

常见用法:

for(variable assignment;condition;iteration process) {for-body}

特殊用法:

for(var in array) {for-body}
遍历数组中的元素

示例:

awk 'BEGIN{total=0;for(i=1;i<=100;i++){total+=i};print total}'
awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg

性能比较:

time (awk 'BEGIN{ total=0;for(i=0;i<=10000;i++){total+=i;};print total;}')
time (total=0;for i in {1..10000};do total=$(($total+i));done;echo $total)
time (for ((i=0;i<=10000;i++));do let total+=i;done;echo $total)
time (seq -s "+" 10000|bc)

(12) continue 和 break

格式:

continue [n]
break [n]

示例:

awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2==0)continue;sum+=i}print sum}'
awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i==50)break;sum+=i}print sum}'

(13) next

next 可以提前结束对本行处理而直接进入下一行处理(awk 自身循环)

示例:

awk -F: '{if($3%2!=0) next; print $1,$3}' /etc/passwd

(14) 数组

awk 的数组为关联数组

格式:

array[index-expression]

示例:

weekdays["mon"]="Monday"

index-expression

可使用任意字符串,字符串要使用双引号括起来
如果某数组元素事先不存在,在引用时,awk 会自动创建此元素,并将其值初始化为 "空串"
若要判断数组中是否存在某元素,要使用 "index in array" 格式进行遍历

示例:

awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";print weekdays["mon"]}'
awk '!line[$0]++' file
awk '{!line[$0]++;print $0, line[$0]}' file

#判断数组索引是否存在
awk 'BEGIN{array["i"]="x"; array["j"]="y" ; print "i" in array, "y" in array }'
awk 'BEGIN{array["i"]="x"; array["j"]="y" ;if ("i" in array){print "存在"}else{print "不存在"}}'
awk 'BEGIN{array["i"]="x"; array["j"]="y" ;if ("abc" in array){print "存在"}else{print "不存在"}}'

若要遍历数组中的每个元素,要使用 for 循环

for(var in array) {for-body}
var 会遍历 array 的每个索引

示例:

awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for(i in weekdays){print weekdays[i]}}'
awk -F: '{user[$1]=$3}END{for(i in user){print "username: "i,"uid: "user[i]}}' /etc/passwd

awk 'BEGIN {
a["x"] = "welcome"
a["y"] = "to"
a["z"] = "Magedu"
for (i in a) {
     print i,a[i]
}
}'

cat ss.log | sed -nr '1!s/^([^0-9]+) .*/\1/p' | sort | uniq -c
ss -ant | awk 'NR!=1{state[$1]++}END{for(i in state){print i,state[i]}}'
netstat -tan | awk '/^tcp/{state[$NF]++}END{for(i in state){print i,state[i]}}'
awk '{ip[$1]++}END{for(i in ip){print i,ip[i]}}' /var/log/httpd/access_log
awk '{ip[$1]++}END{for(i in ip){print ip[i],i}}' access_log | sort -nr | head -3
awk '{ip[$1]++}END{for(i in ip){print i,ip[i]}}' access_log | sort -k2 -nr| head -3

多维数组

awk 'BEGIN{
array[1][1]=11
array[1][2]=12
array[1][3]=13
array[2][1]=21
array[2][2]=22
array[2][3]=23
for (i in array)
    for (j in array[i])
        print array[i][j]
}'

(15) awk 函数

awk 的函数分为内置和自定义函数

① 常见内置函数

数值处理

rand()		返回 01 之间一个随机数
srand()		配合 rand() 函数,生成随机数的种子
int()		返回整数

示例:

awk 'BEGIN{srand();print rand()}'
awk 'BEGIN{srand(); for (i=1;i<=10;i++)print int(rand()*100)}'

字符串处理

length([s])		返回指定字符串的长度
sub(r,s,[t])	对 t 字符串搜索 r 表示模式匹配的内容,并将第一个匹配内容替换为 s
gsub(r,s,[t])	对 t 字符串进行搜索 r 表示的模式匹配的内容,并全部替换为 s 所表示的内容
split(s,array,[r])	以 r 为分隔符,切割字符串 s,并将切割后的结果保存至 array 所表示的数组中,第一个索引值为 1, 第二个索引值为 2,…

示例:

echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'
echo "2008:08:08 08:08:08" | awk '{sub(/:/,"-",$1);print $0}'

echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$0)'
echo "2008:08:08 08:08:08" | awk '{gsub(/:/,"-",$0);print $0}'

netstat -tn | awk '/^tcp/{split($5,ip,":");count[ip[1]]++}END{for(i in count){print i,count[i]}}'

system 函数

可以 awk 中调用 shell 命令
空格是 awk 中的字符串连接符,如果 system 中需要使用 awk 中的变量可以使用空格分隔,或者说除了 awk 的变量外其他一律用 "" 引用起来

示例:

awk 'BEGIN{system("hostname")}'
awk 'BEGIN{score=100; system("echo your score is " score) }'
netstat -tn | awk '/^tcp/{split($5,ip,":");count[ip[1]]++}END{for(i in count){if(count[i]>=10) {system("iptables -A INPUT -s "i" -j REJECT")}}}'

② 自定义函数

格式:

function name ( parameter, parameter, ... )
{
	statements
	return expression
}

示例:

[root@centos8 ~]#cat func.awk
function max(x,y) {
 x>y?var=x:var=y
 return var
}
BEGIN{print max(a,b)}

[root@centos8 ~]#awk -v a=30 -v b=20 -f func.awk
30

(16) awk 脚本

将 awk 程序写成脚本,调用或直接执行

示例:

[root@c7-1 ~]#awk -F: -f passwd.awk /etc/passwd
nfsnobody 65534
syhj 1000
[root@c7-1 ~]#cat passwd.awk 
{if($3>=1000)print $1,$3}
————————————————————
[root@c7-1 ~]#cat test.awk 
#!/bin/awk -f
#this is a awk script
{if($3>=1000)print $1,$3}
[root@c7-1 ~]#chmod +x test.awk 
[root@c7-1 ~]#./test.awk -F: /etc/passwd
nfsnobody 65534
syhj 1000

向 awk 脚本传递参数

awkfile var1=value1 var2=value2... Inputfile

注意:在 BEGIN 过程中不可用。直到首行输入完成以后,变量才可用。可以通过 -v 参数,让 awk 在执行 BEGIN 之前得到变量的值。
命令行中每一个指定的变量都需要一个 -v 参数。

示例:

[root@c7-1 ~]#cat test.awk 
#!/bin/awk -f
{if($3 >=min && $3<=max)print $1,$3}
[root@c7-1 ~]#chmod +x test.awk 
[root@c7-1 ~]#./test.awk -F: min=100 max=200 /etc/passwd
systemd-network 192
abrt 173
rtkit 172
qemu 107
usbmuxd 113
pulse 171

3. 其他文本处理工具

3.1 cut

列截取工具,cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段写至标准输出。
如果不指定 File 参数,cut 命令将读取标准输入。必须指定 -b、-c 或 -f 标志之一

常用选项:

-b	按字节截取
-c	按字符截取,常用于中文
-d	指定以什么为分隔符截取,默认为制表符
-f	通常和 -d 一起

示例:

[root@c7-1 ~]#cat /etc/passwd | cut -d: -f1,6,7
root:/root:/bin/bash
bin:/bin:/sbin/nologin
daemon:/sbin:/sbin/nologin
......

[root@c7-1 ~]#who | cut -b 3
o
o
[root@c7-1 ~]#who | cut -c 3
o
o
[root@c7-1 ~]#who
root     :0           2021-09-02 10:04 (:0)
root     pts/1        2021-09-14 08:27 (192.168.10.1)

[root@c7-1 ~]#ifconfig | head -n2 | tail -n1 | tr -s " " | cut -d " " -f3
192.168.10.20

3.2 tr

tr 可以用一个字符来替换另一个字符,或者可以完全除去一些字符,也可以用它来除去重复字符,从标准输入中替换、缩减或删除字符,并将结果写到标准输出。

格式:

tr [选项] [格式] [文件或内容]

常用选项:

-d	删除字符
-s	删除所有重复出现的字符,只保留第一个

示例:

#转换大小写
[root@localhost ~]#cat fruit | tr 'a-z' 'A-Z'
APPLE
PEAR
BANANA
CHERRY
ORANGE

#对 p 字符去重,只保留第一个
cat file | tr -s 'p'  

#压缩多个空格为一个
[root@c7-1 ~]#df -h | tr -s ' '
文件系统 容量 已用 可用 已用% 挂载点
devtmpfs 1.9G 0 1.9G 0% /dev
tmpfs 1.9G 0 1.9G 0% /dev/shm
tmpfs 1.9G 13M 1.9G 1% /run
tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup
/dev/sda2 50G 5.2G 45G 11% /
/dev/sda5 44G 33M 44G 1% /data
/dev/sda1 1014M 179M 836M 18% /boot
tmpfs 378M 4.0K 378M 1% /run/user/42
tmpfs 378M 36K 378M 1% /run/user/0
/dev/sr0 4.4G 4.4G 0 100% /run/media/root/CentOS 7 x86_64

#删除所有 a
cat file | tr -d 'a'    

#删除换行符
cat file | tr -d '\n'    

#遇到多个回车只保留一个回车,相当于去除空行
cat file | tr -s '\n'  

3.3 sort

是一个以行为单位对文件内容进行排序的工具,也可以根据不同的数据类型来排序。

格式:

sort [选项] 参数

常用选项:

-t	指定分隔符,默认使用 Tab 键或空格分隔
-k	指定排序区域,哪个区间排序
-n	按照数字进行排序,默认是以文字形式排序
-u	等同于 uniq,表示相同的数据仅显示一行,注意:如果行尾有空格去重就不成功
-R	随机排序
-r	反向排序,默认是升序,-r 就是降序
-o	将排序后的结果转存至指定文件

示例:

sort passwd.txt    //不加任何选项默认按第一列升序,字母的话就是从 a 到 z 由上而下显示
sort -n -t: -k3 passwd.txt    //以冒号为分隔符,以数字大小对第三列排序(升序)
sort -nr -t: -k3 passwd.txt   //以冒号为分隔符,以数字大小对第三列排序(降序)
sort -nr -t: -k3 passwd.txt -o passwd.bak    //将输结果不在屏幕上输出而是输出到 passwd.bak 文件
sort -u passwd.txt    //去掉文件中重复的行(重复的行可以是不连续的)

3.4 uniq

uniq 主要用于去除连续的重复行,通常和 sort 结合使用先排序使之变成连续的行再执行去重操作,否则不连续的重复行不能去重。

格式:

uniq [选项] 参数
sort file.txt | uniq -c

常用选项:

-c	对重复的行进行计数
-d	仅显示重复行
-u	仅显示不重复的行

示例:

#统计日志访问量最多的请求
cut -d" " -f1 /PATH/to/access_log |sort |uniq -c|sort -nr |head -3
lastb -f file | tr -s ' ' |cut -d ' ' -f3|sort |uniq -c|sort -nr | head -3

#并发连接最多的远程主机 IP
ss -nt|tail -n+2 |tr -s ' ' : |cut -d: -f6|sort|uniq -c|sort -nr |head -n2

#取两个文件的相同的行
cat test1.txt test2.txt | sort |uniq -d

#取文件的不同行
cat test1.txt test2.txt | sort |uniq -u

#统计重复行的次数,不连续的重复行不算做重复行
cat file | uniq -c    

#查看登陆过系统的用户
last | awk '{print $1}' | sort | uniq | grep -v "^$" | grep -v wtmp

3.5 分类

文本编辑

vim

内容查看

cat
nl
tac
rev

非文本文件内容查看

hexdump
od
xxd

分页查看

more
less

显示文本前或后行内容

head
tail

列抽取

cut

合并文件

paste

文本分析

wc
sort
uniq

比较文件

diff
patch
cmp

你可能感兴趣的:(云计算,linux运维,shell,正则表达式)