文本编辑工具之神VIM

1

vi和vim简介

在Linux中我们经常编辑修改文本文件,即由ASCII, Unicode 或其它编码的纯文字的文件。之前介绍过nano,实际工作中我们会使用更为专业,功能强大的工具

文本编辑种类:

  • 全屏编辑器:nano(字符工具),gedit(图形化工具),vi,vim
  • 行编辑器:sed

VI

Visual editor,文本编辑器,是 Linux 必备工具之一,功能强大,学习曲线较陡峭,学习难度大

VIM

VIsual editor iMproved ,和 vi 使用方法一致,但功能更为强大,不是必备软件

VIM 官网 https://www.vim.org

其他相关编辑器:gvim 一个Vim编辑器的图形版本

VIM 小抄

2

参考链接: https://www.w3cschool.cn/vim/

如何退出VIM

使用 vim 初步

vim 命令格式

1
vim [OPTION]... FILE...

常用选项

1
2
3
4
5
6
7
+N                  #打开文件后让光标处于第N行的行首,+默认尾行
+/PATTERN #让光标处于第一个被PATTERN匹配到的行行首
-b file #二进制方式打开文件
-d file1 file2… #比较多个文件,相当于 vimdiff
-m file #只读打开文件
-e file #直接进入ex模式,相当于执行ex file
-y file #Easy mode (like "evim", modeless),直接可以操作文件,ctrl+o:wq|q! 保存和不保存退出

说明:

  • 如果该文件存在,文件被打开并显示内容
  • 如果该文件不存在,当编辑后第一次存盘时创建它

范例:

1
2
3
4
5
6
7
[root@rocky8 ~]# vim f1.txt         #光标在第一行
[root@rocky8 ~]# vim +3 f1.txt #光标在第3行
[root@rocky8 ~]# vim + f1.txt #光标在最后一行


[root@rocky8 ~]# vim +/^abc f2.txt #模式匹配定位
[root@rocky8 ~]# vim +/^bc* f2.txt #模式匹配定位

三种主要模式和转换

vim 是 一个模式编辑器,击键行为是依赖于 vim的 的“模式”

三种模式:

  • 命令或普通(Normal)模式:默认模式,可以实现移动光标,剪切/粘贴文本
  • 插入(Insert)或编辑模式:用于修改文本
  • 扩展命令(extended command )或命令(末)行模式:保存,退出等

3

三种模式之间的切换:

vim 是 一个模式编辑器,击键行为是依赖于 vim的“模式”,就是说,在不同模式下,相同的按键,是不同的意思;

插入模式和扩展命令模式之间不能直接切换,要走命令模式中转;

打开vim 默认就是命令模式;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#命令模式 --------------------------> 插入模式
#下列按键皆可

#i insert, 在光标所在处输入
#I 在当前光标所在行的行首输入
#a append, 在光标所在处后面输入
#A 在当前光标所在行的行尾输入
#o 在当前光标所在行的下方打开一个新行
#O 在当前光标所在行的上方打开一个新行




#命令模式 --------------------------> 扩展命令模式
#直接敲冒号 :

#################################################################

#插入模式 -------------------------> 命令模式
#按ESC键


#插入模式 -------------------------> 扩展命令模式
#按ESC到命令模式,再按冒号 : 到扩展命令模式


#################################################################

#扩展命令模式 ----------------------> 命令模式
#ESC键或Enter键或退格健


#扩展命令模式 ---------------------> 插入模式
#ESC|Enter|退格键 到命令模式,再i|I|a|A|o|O 到插入模式

范例: 插入颜色字符

1
[root@rocky8 ~]# echo -e '\e[1;31mhello\e[0m'

vim 中实现

1
2
3
4
5
1 切换至插入模式
2 按ctrl+v+[ 三个键,显示^[
3 后续输入颜色信息,如:^[[32mhello^[[0m
4 切换至扩展命令模式,保存退出
5 cat 文件可以看到下面显示
1
2
^[[1;31mhello vim^[[0m
^[[1;32mGreen ^[[0m

4

扩展命令模式

按:进入Ex模式 ,创建一个命令提示符: 处于底部的屏幕左侧

扩展命令模式基本命令

1
2
3
4
5
6
7
8
9
10
w               #写(存)磁盘文件
wq #写入并退出
x #写入并退出
X #加密
q #退出
q! #不存盘退出,即使更改都将丢失
r filename #读文件内容到当前文件中
w filename #将当前文件内容写入另一个文件
!command #执行命令
r!command #读入命令的输出

范例:

1
2
3
4
5
6
7
8
9
10
:w              #保存当前更改,不退出
:q! #不保存退出
:r /etc/fstab #将/etc/fstable
:w test #将当前打开的文件保存至另外一个文件
:!hostname #执行hostname
:r!uname -a #将 uname -a 的命令执行结果读到当前文件中

:X #加密码
Enter encryption key: ******
Enter same key again: ******

地址定界

格式:

1
:start_pos,end_pos CMD

地址定界格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
N               #具体第N行,例如2表示第2行
M,N #从左侧M表示起始行,到右侧N表示结尾行
M,+N #从左侧M表示起始行,右侧表示从光标所在行开始,再往后+N行结束
M,-N #从左侧M表示起始行,右侧表示从光标所在行开始,-N所在的行行结束
M;+N #从第M行处开始,往后数N行,2;+3 表示第2行到第5行,总共取4行
M;-N #从第M-N行开始,到第M行结束
. #当前行
$ #最后一行
.,$-1 #当前行到倒数第二行
% #全文, 相当于1,$
/pattern/ #从当前行向下查找,直到匹配pattern的第一行,即正则匹配
/pat1/,/pat2/ #从第一次被pat1模式匹配到的行开始,一直到第一次被pat2匹配到的行结束
N,/pat/ #从指定行开始,一直找到第一个匹配pattern的行结束
/pat/,$ #向下找到第一个匹配patttern的行到整个文件的结尾的所有行

地址定界后跟一个编辑命令

1
2
3
4
5
6
7
p               #输出
d #删除
y #复制
w file #将范围内的行另存至指定文件中
r file #在指定位置插入指定文件中的所有内容
t行号 #将前面指定的行复制到N行后
m行号 #将前面指定的行移动到N行后

范例:

1
2
3
4
5
6
7
8
9
10
[root@rocky8 ~]# seq 10 > 10.txt
:2d #删除第2行
:2,4d #删除第2到第4行
:2;+3y #复制第2到第5行,总共4行
:3;+4w test #将第2到第5行,总共4行内容写入新文件
:5r /etc/issue #将/etc/issue 文件读取到第5行
:t2 #将光标所在行复制到第2行之后的行
:2,+3t10 #将第2到第5行,总共4行内容复制到第10行之后
:.d #删除光标所在行
:$y #复制最后一行

查找并替换

格式

1
2
:s/要查找的内容/替换为的内容/修饰符
:%s 表示全文查找替换

说明:

1
2
要查找的内容:可使用基本正则表达式模式
替换为的内容:不能使用模式,但可以使用\1, \2, ...等后向引用符号;还可以使用“&”引用前面查找时查找到的整个内容

修饰符:

1
2
3
i                   #忽略大小写
g #全局替换,默认情况下,每一行只替换第一次出现
gc #全局替换,每次替换前询问

查找替换中的分隔符/可替换为其它字符,如:#,@

范例:

1
2
3
4
5
6
:s/root/ROOT/       #替换当前行第一个root
:s/root/ROOT/g #替换当前行所有root
:%s/root/ROOT/g #全文替换
:%s/root/test/ig #不区分大小写全文替换

:%s/#// #将每行第一个#替换成空

定制vim的工作特性

扩展命令模式的配置只是对当前vim进程有效,可将配置存放在文件中持久保存;

写配置文件必须保证能识别,有些短格式在 配置文件;

1
2
/etc/vimrc              #全局配置        
~/.vimrc #个人配置

行号

1
2
:set number|set nu           #显示行号
:set nonumber|set nonu #取消显示行号

忽略字符的大小写

1
2
:set ignorecase|set ic            #忽略字符大小写
:set noignorecase|set noic #不忽略

自动缩进

1
2
:set autoindent|set ai              #启用自动缩进
:set noautoindent|set noai #禁用自动缩进

复制保留格式

1
2
:set paste                #复制时保留格式
:set nopaste #禁用复制时保留格式选项

显示Tab ^I和换行符 和$显示

1
2
:set list                 #显示系统字符
:set nolist #隐藏系统字符

高亮搜索

1
2
:set hlsearch                  #高亮显示搜索结果
:set nohlsearch|nohl #不高亮显示搜索结果

语法高亮

1
2
:syntax on               #语法高亮
:syntax off #关闭语法高亮

文件格式

1
2
:set fileformat=dos|set ff=doc      #启用windows格式
:set fileformat=unix|set ff=unix #启用unix格式

Tab 用空格代替

1
2
:set expandtab|set et               #使用空格代替Tab,默认8个空格
:set noexpandtab|set noet #禁用空格代替Tab

Tab用指定空格的个数代替

1
:set tabstop=N|set ts=N         #指定N个空格代替Tab

设置缩进宽度

1
2
3
4
>>                            #向右缩进 命令模式
<< #向左缩进 命令模式

:set shiftwidth=4 #设置缩进为4个字符

设置光标所在行的标识线

1
2
:set cursorline|set cul             #给光标所在行加下划线
:set nocursorline|set nocul #取消光标下划线

加密

1
2
:set key=password                   #加密文档
:set key= #取消加密

了解更多

set 帮助

1
2
:help option-list 
:set|:set all

命令模式

命令模式,又称为Normal模式,功能强大,但是在此模式下输入的命令不会在屏幕上显示,只能靠记忆记住到底输入了什么,所以此处我们 要记住大量快捷键;

退出VIM

1
2
ZZ              #保存退出
ZQ #不保存退出

光标跳转

字符间跳转:

1
2
3
4
5
h               #左
L #右,小写字母
j #下
k #上
Nh|Nl|Nj|Nk #每次跳转N个长度 2h表示向左移两个字符,3j表示向下走3行

单词间跳转

1
2
3
4
w               #下一个单词的词首
e #当前或下一单词的词尾
b #当前或前一个单词的词首
Nw|Ne|Nb #一次跳N个单词

当前页跳转:

1
2
3
4
5
6
H               #页首
M #页中间行
L #页底
zt #将光标所在当前行移到屏幕顶端
zz #将光标所在当前行移到屏幕中间
zb #将光标所在当前行移到屏幕底端

行首行尾跳转:

1
2
3
^               #跳转至行首的第一个非空白字符
0 #跳转至行首
$ #跳转至行尾

行间移动:

1
2
3
NG|:N           #跳转至指定行,N表示正整数,比如 10G,或在扩展命令模式下:10,都表示跳转到第10行
G #最后一行
gg|1G #第一行

句间移动:

1
2
(               #上一句
) #下一句

段落间移动:

1
2
{               #上一段
} #下一段

命令模式翻屏操作

1
2
3
4
Ctrl+f          #向文件尾部翻一屏,相当于Pagedown
Ctrl+b #向文件首部翻一屏,相当于Pageup
Ctrl+d #向文件尾部翻半屏
Ctrl+u #向文件首部翻半屏

字符编辑

1
2
3
4
5
x               #剪切光标处的字符
Nx #剪切光标处起始的N个字符,N为正整数
xp #交换光标所在处的字符及其后面字符的位置,本质是先剪切,再粘贴
~ #转换大小写
J #删除当前行后的换行符,就是把下一行接到当前行后面

替换命令(replace)

1
2
r               #只替换光标所在处的一个字符,先敲r,再输入想要替换的新字符
R #切换成REPLACE模式(在末行出现-- REPLACE -- 提示),按ESC回到命令模式

删除命令(delete)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
d               #删除命令,可结合光标跳转字符,实现范围删除

d$|D #删除光标处到行尾
d^ #删除光标处到非空行首
d0 #删除光标处到处行首
dw #从光标处删到下一个单词的词首
de #从光标处删到当前单词词尾或下一单词的词尾
db #从光标处删到当前单词词首或下一单词的词首
dG #删除光标到文件末尾
dgg #删除光标到文件开始
dd #删除光标所在行
Ndd #从当前行开始,删N行,N表示正整数,2dd表示从当前行开始,总共删2行


#这些内容都在缓冲区,可以用p键粘贴出来

复制命令(yank)

1
2
3
4
5
6
7
8
9
10
y               #复制,行为相似于d命令

y$ #复制光标处到行尾
y0 #复制光标处到行首
y^ #复制光标处到非空行首
yy|Y #复制整行,yy前面加数字,表示从当前处往后复制多少行; 3yy 表示往下复制3行
Nyy #从当前处往后复制N行, N表示正整数,2yy表示从当前行开始,总共复制2行
yw #从光标处复制到下一个单词的词首
ye #从光标处复制到当前单词词尾或下一单词的词尾
yb #从光标处复制到当前单词词首或下一单词的词首

粘贴命令(paste)

1
2
p               #缓冲区存的如果为整行,则粘贴当前光标所在行的下方;否则,则粘贴至当前光标所在处的后面
P #缓冲区存的如果为整行,则粘贴当前光标所在行的上方;否则,则粘贴至当前光标所在处的前面

改变命令(change)

命令 c 删除后切换成插入模式

1
2
3
4
5
6
7
8
c$|C            #删除光标处到行尾,并切换成插入模式
c^ #删除光标处到非空行首,并切换成插入模式
c0 #删除光标处到行首,并切换成插入模式
cc #删除当前行并输入新内容,相当于S,加数字表示向下删除多少行,然后切换成插入模式
Ncc #N表示正整数
cw #从光标处删到下一个单词的词首,并切换成插入模式
ce #从光标处删到当前单词词尾或下一单词的词尾,并切换成插入模式
cb #从光标处删到当前单词词首或下一单词的词首,并切换成插入模式

命令模式操作文本总结

5

查找

1
2
3
4
/PATTERN        #从当前光标所在处向文件尾部查找
?PATTERN #从当前光标所在处向文件首部查找
n #与命令同方向
N #与命令反方向

撤消更改

1
2
3
4
5
6
u               #撤销最近的更改,相当于windows中ctrl+z
Nu #撤销之前多次更改,N表示正整数
U #撤消光标落在这行后所有此行的更改
Ctrl+r #重做最后的“撤消”更改,相当于windows中crtl+y
. #重复前一个操作
N. #重复前一个操作N次,N为正整数

高级用法

1
<start position><command><end position>

常见Command:y 复制、d 删除、gU 变大写、gu 变小写

范例:

1
2
3
4
5
6
7
8
9
#START COMMAND END

0y$ #复制整行,0表示到行首,y表示复制,$表示行尾,连起来就是复制整行
di"" #光标在" "之间,则删除" "之间的内容
yi() #光标在()之间,则复制()之间的内容
vi[] #光标在[]之间,则选中[]之间的内容
dtx #删除字符直到遇见光标之后的第一个 x 字符
ytx #复制字符直到遇见光标之后的第一个 x 字符
10iabc ESC #在光标处插入10个abc

可视化模式

在末行有“– VISUAL –” 指示,表示在可视化模式

允许选择的文本块

  • v 面向字符,– VISUAL –
  • V 面向整行,– VISUAL LINE –
  • ctrl-v 面向块,– VISUAL BLOCK –

可视化键可用于与移动键结合使用

w ) } 箭头等

突出显示的文字可被删除,复制,变更,过滤,搜索,替换等

范例:在文件指定行的行首插入#

1
2
3
4
5
6
1、先将光标移动到指定的第一行的行首
2、输入ctrl+v 进入可视化模式
3、向下移动光标,选中希望操作的每一行的第一个字符
4、输入大写字母 I 切换至插入模式
5、输入 #
6、按 ESC 键

范例:在指定的块位置插入相同的内容

1
2
3
4
5
1、光标定位到要操作的地方
2、CTRL+v 进入“可视块”模式,选取这一列操作多少行
3、SHIFT+i(I)
4、输入要插入的内容
5、按 ESC 键

多窗口模式

多文件分割

1
2
3
4
5
vim FILE1 FILE2...

#多文件之间切换
:next
:prev
1
2
3
4
5
6
7
vim -o|-O FILE1 FILE2 ...

-o #水平或上下分割
-O #垂直或左右分割(vim only)

:wqall #退出
Ctrl+w, Arrow #在窗口间切换

单文件窗口分割

1
2
3
4
5
6
Ctrl+w,s            #split, 水平分割,上下分屏
Ctrl+w,v #vertical, 垂直分割,左右分屏
ctrl+w,q #取消相邻窗口
ctrl+w,o #取消全部窗口
:wqall #退出
Ctrl+w, Arrow #在窗口间切换

帮助

1
2
3
4
5
6
:help
:help topic #topic 是指主题,比如 :help yy 就表示查看yy命令的用法

:q #退出help

:help index #列出所有主题

vim 帮助程序

1
[root@rocky8 ~]# vimtutor

vim 总结图

6

7

8

文本常见处理工具

文件内容查看命令

查看文本文件内容

cat

cat 可以查看文本内容

格式:

1
2
3
4
5
6
7
8
cat [OPTION]... [FILE]...

#常见选项
-E|--show-ends #显示行结束符$
-A|--show-all #显示所有控制符
-n|--number #对显示出的每一行进行编号
-b|--number-nonblank #非空行编号
-s|--squeeze-blank #压缩连续的空行成一行

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@rocky86 ~]# cat -A /data/fa.txt
a b$
c $
d^Ib^Ic$

[root@rocky86 ~]# cat /data/fa.txt
a b
c
d b c

[root@rocky86 ~]# cat /data/fb.txt
a
b
c

[root@rocky86 ~]# hexdump -C /data/fb.txt
00000000 61 0d 0a 62 0d 0a 63 0d 0a |a..b..c..|
00000009

[root@rocky86 ~]# cat -A /data/fb.txt
a^M$
b^M$
c^M$

[root@rocky86 ~]# file /data/fb.txt
/data/fb.txt: ASCII text, with CRLF line terminators

nl

显示行号,相当于cat -b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@rocky86 ~]# cat /data/f1.txt
a
b
c
d
e
f
g
h

[root@rocky86 ~]# nl /data/f1.txt
1 a
2 b
3 c
4 d
5 e
6 f
7 g
8 h

tac

逆向显示文本内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[root@rocky86 ~]# cat /data/fa.txt
1
2
3
4
5

[root@rocky86 ~]# tac /data/fa.txt
5
4
3
2
1

[root@rocky86 ~]# tac
a
bb
ccc 按ctrl+d
ccc
bb
a

[root@rocky86 ~]# seq 10 | tac
10
9
8
7
6
5
4
3
2
1

rev

将同一行的内容逆向显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@rocky86 ~]# cat /data/fa.txt
1 2 3 4 5
a b c

#上下顺序逆向
[root@rocky86 ~]# tac /data/fa.txt
a b c
1 2 3 4 5

#逐行逆向
[root@rocky86 ~]# rev /data/fa.txt
5 4 3 2 1
c b a

#标准输入
[root@rocky86 ~]# rev
abcdef
fedcba

[root@rocky86 ~]# echo {1..10} |rev
01 9 8 7 6 5 4 3 2 1

查看非文本文件内容

范例:hexdump

1
2
3
4
5
6
7
[root@rocky8 ~]# hexdump -C -n 512 /dev/sda
00000000 eb 63 90 10 8e d0 bc 00 b0 b8 00 00 8e d8 8e c0 |.c..............|

[root@rocky8 ~]# echo {a..z} | tr -d ' '|hexdump -C
00000000 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 |abcdefghijklmnop|
00000010 71 72 73 74 75 76 77 78 79 7a 0a |qrstuvwxyz.|
0000001b

分页查看文件内容

more

可以实现分页查看文件,可以配合管道实现输出信息的分页

格式

1
2
3
4
5
more [OPTIONS...] FILE...

#常用选项
-d #在底部显示提示
-s #压缩连续空行

命令选项

1
2
3
4
5
6
空格键             #翻页
回车键 #下一行
!cmd #执行命令
h #显示帮助
:f #显示文件名和当前行号
= #显示行号

示例:

1
2
3
[root@rocky86 ~]# more /var/log/messages

[root@rocky86 ~]# cat /var/log/messages | more

less

less 也可以实现分页查看文件或STDIN输出,less 命令是man命令使用的分页器

格式:

1
2
3
4
5
6
7
less [OPTIONS...] FILE...

#常用选项
-e #显示完成后自动退出
-N #显示行号
-s #压缩连续空行
-S #不换行显示较长的内容

命令选项

1
2
3
4
:h              #显示帮助
/string #搜索
:!cmd #执行命令
b #向上翻

范例:

1
2
3
[root@rocky86 ~]# less -N /etc/init.d/functions

[root@rocky86 ~]# tree / -d | less

显示文本前面或后面的行内容

可以显示文件或标准输入的前面行

格式:

1
2
3
4
5
6
7
8
9
head [OPTION]... [FILE]...

#常用选项
-c|--bytes=N #指定获取前N字节
-n|--lines=N #指定获取前N行,N如果为负数,表示从文件头取到倒数第N前
-N #同上
-q|--quiet|--silent #不输出文件名
-v|--verbose #输出文件名
-z|--zero-terminated #以NULL字符而非换行符作为行尾分隔符

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
[root@rocky8 ~]# head -n 3 /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

[root@rocky8 ~]# head -3 /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

[root@rocky8 ~]# echo a我b|head -c 4
a我

[root@rocky8 ~]# cat /dev/urandom | tr -dc '[:alnum:]'|head -c 10
b6TeJQlZBJ

[root@rocky8 ~]# cat /dev/urandom | tr -dc '[:alnum:]'|head -c 10 |tee pass.txt|passwd --stdin jerry
Changing password for user jerry.
passwd: all authentication tokens updated successfully.

[root@centos8 ~]# cat seq.log
1
2
3
4
5
6
7
8
9
10

[root@centos8 ~]# head -n 3 seq.log
1
2
3

[root@centos8 ~]# head -n -3 seq.log
1
2
3
4
5
6
7

[root@centos8 ~]# head -n +3 seq.log
1
2
3

tail

tail 和head 相反,查看文件或标准输入的倒数行

格式:

1
2
3
4
5
6
7
8
9
10
11
12
tail [OPTION]... [FILE]...

#常用选项
-c|--bytes=N #指定获取后N字节
-n|--lines=N #指定获取后N行,如果写成+N,表示从第N行开始到文件结束
-N #同上
-f|--follow=descriptor #跟踪显示文件fd新追加的内容,常用日志监控,当删除再新建同名文件,将无法继续跟踪
-F|--follow=name --retry #跟踪文件名,相当于--follow=name --retry,当删除文件再新建同名文件,可继续追踪
-q|--quiet|--silent #不输出文件名
-z|--zero-terminated #以 NULL字符而非换行符作为行尾分隔符

tailf #类似 tail –f,当文件不增长时并不访问文件,节约资源,CentOS8已经无此工具

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@centos8 ~]# cat /data/f1.txt
1
2
3
4
5
6
7
8
9
10

[root@centos8 ~]# tail -n 3 /data/f1.txt
8
9
10

[root@centos8 ~]# tail -n +3 /data/f1.txt
3
4
5
6
7
8
9
10

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@rocky8 ~]# tail -3 /var/log/messages
Sep 5 14:04:24 rocky8 systemd[1]: openvpn-server@server.service: Main process exited, code=exited, status=1/FAILURE
Sep 5 14:04:24 rocky8 systemd[1]: openvpn-server@server.service: Failed with result 'exit-code'.
Sep 5 14:04:24 rocky8 systemd[1]: Failed to start OpenVPN service for server.

[root@rocky8 ~]# tail -f /var/log/messages
Sep 5 14:04:55 rocky8 openvpn[1798]: Options error: --ca fails with 'ca.crt': No such file or directory (errno=2)
Sep 5 14:04:55 rocky8 openvpn[1798]: Options error: --cert fails with 'server_A7r9AfMWXrKibJBp.crt': No such file or directory (errno=2)

#只查看最新发生的日志
[root@rocky8 ~]# tail -fn0 /var/log/messages

[root@rocky8 ~]# tail -0f /var/log/messages

#取IP行
[root@rocky8 ~]# ifconfig | head -2 | tail -1
inet 192.168.1.11 netmask 255.255.255.0 broadcast 192.168.1.255

[root@rocky8 ~]# ifconfig | head -2 | tail -1 | tr -s ' '| cut -d ' ' -f 3
192.168.1.11

head 和 tail 总结

命令 含义 示例
head -n k file 取 file 前 k 行内容 head -n 3 /etc/passwd
head -k file 取 file 前 k 行内容 head -3 /etc/passwd
head -n +k file 取 file 前 k 行内容 head -n +3 /etc/passwd
head -n -k file 取 file 第一行到倒数第 k 行内容 head -n -3 /etc/passwd
tail -n k file 从后往前数,取 file 第一行到第 k 行内容 tail -n 3 /etc/passwd
tail -k file 从后往前数,取 file 第一行到第 k 行内容 tail -3 /etc/passwd
tail -n -k file 从后往前数,取 file 第一行到第 k 行内容 tail -n -3 /etc/passwd
tail -n +k file 从后往前数,取 file 第一行到倒数第 k 行内容 tail -n +3 /etc/passwd

9

范例: 显示第6行

1
2
3
4
5
[root@rocky8 ~]# seq 20 | head -n 6 | tail -n 1
6

[root@rocky8 ~]# seq 20 | tail -n +6 | head -n 1
6

按列抽取文本 cut

cut 命令可以提取文本文件或 STDIN 数据的指定列

格式

1
2
3
4
5
6
7
8
9
10
cut OPTION... [FILE]...

#常用选项
-b|--bytes=LIST #以字节分割,指定要显示的列
-c|--characters=LIST #以字符分割,指定要显示的列
-d|--delimiter=DELIM #指定分割符,默认是tab
-f|--fields=LIST #要显示的列,-f1, -f1,2,3, -f 1-3,4
-s|--only-delimited #不显示没有包括分割符的内容
--output-delimiter=STRING #输出的时候用指定字符代替分割符
-z|--zero-terminated #以 NUL 字符而非换行符作为行尾分隔符

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
[root@rocky8 ~]# cut -d : -f 1,3-4,7 /etc/passwd

[root@rocky8 ~]# ifconfig | head -n 2 | tail -n 1 | cut -d " " -f 10
192.168.1.11

[root@rocky8 ~]# ifconfig | head -n 2 | tail -n 1 | tr -s " " | cut -d " " -f 3
192.168.1.11

[root@rocky8 ~]# df | tr -s ' ' | cut -d ' ' -f 5 | tr -dc "[0-9\n]"
0
0
1
0
3
1
21
0

[root@rocky8 ~]# df | tr -s ' ' % | cut -d % -f 5 | tr -d '[:alpha:]'
0
0
1
0
3
1
21
0

[root@rocky8 ~]# df | cut -c49-51 | tr -d '[:alpha:]'
# 49-51 是指
# devtmpfs 897456 0 897456 0% /dev
# 从头开始数第49个字符到第51个字符,根据实际情况看%前面的就行
0
0
1
0
3
1
21
0

[root@rocky8 ~]# cut -d : -f 1,3,7 --output-delimiter="---" /etc/passwd
root---0---/bin/bash
bin---1---/sbin/nologin
daemon---2---/sbin/nologin

[root@rocky8 dict]# echo {1..10} | cut -d ' ' -f 1-10 --output-delimiter="+" | bc
55

范例: 取分区利用率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#取分区利用率
[root@rocky8 dict]# df | tr -s ' '| cut -d ' ' -f 5 | tr -d %

[root@rocky8 ~]# df | tr -s ' ' '%'|cut -d % -f 5
Use
0
0
1
0
3
1
21
0

[root@rocky8 ~]# df | cut -c49-51 | tail -n +2
0
0
2
0
3
1
21
0

[root@rocky8 ~]# df | tail -n +2 | tr -s ' ' % | cut -d % -f 5
0
0
2
0
3
1
21
0

[root@rocky8 ~]# df | tail -n +2 | tr -s ' ' | cut -d ' ' -f 5 |tr -d %
0
0
2
0
3
1
21
0

合并多个文件 paste

paste 合并多个文件同行号的列到一行

格式

1
2
3
4
5
6
paste [OPTION]... [FILE]...

#常用选项
-d|--delimiters=LIST #指定分割符,默认用TAB
-s|--serial #合成一行显示
-z|--zero-terminated #以 NUL 字符而非换行符作为行尾分隔符

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
[root@rocky8 ~]# echo {a..h} | tr ' ' "\n" > alpha.log
[root@rocky8 ~]# cat alpha.log
a
b
c
d
e
f
g
h

[root@rocky8 ~]# seq 5 > seq.log
[root@rocky8 ~]# cat seq.log
1
2
3
4
5

[root@rocky8 ~]# cat alpha.log seq.log
a
b
c
d
e
f
g
h
1
2
3
4
5

[root@rocky8 ~]# paste alpha.log seq.log
a 1
b 2
c 3
d 4
e 5
f
g
h

[root@rocky8 ~]# paste -d ":" alpha.log seq.log
a:1
b:2
c:3
d:4
e:5
f:
g:
h:

[root@rocky8 ~]# paste -s alpha.log
a b c d e f g h

[root@rocky8 ~]# paste -s seq.log
1 2 3 4 5

[root@rocky8 ~]# paste -s alpha.log seq.log
a b c d e f g h
1 2 3 4 5


[root@rocky8 ~]# cat title.txt
ceo
coo
cto

[root@rocky8 ~]# cat emp.txt
mage
zhang
wang
xu

[root@rocky8 ~]# paste title.txt emp.txt
ceo mage
coo zhang
cto wang
xu

[root@rocky8 ~]# paste -s title.txt emp.txt
ceo coo cto
mage zhang wang xu

[root@rocky8 ~]# cat f1.log
1
2
3
4
5
6
7
8
9
10
[root@rocky8 ~]# cat f2.log
a
b
c
d
e
f
g
h
i
j

[root@rocky8 ~]# paste -s -d : f1.log f2.log
1:2:3:4:5:6:7:8:9:10
a:b:c:d:e:f:g:h:i:j

[root@rocky8 ~]# seq 10 | paste -s -d + |bc
55

范例: 批量修改密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@rocky8 ~]# cat > user.txt <<EOF
> jose
> jerry
> EOF

[root@rocky8 ~]# cat > pass.txt <<EOF
> 123456
> 000000
> EOF

[root@rocky8 ~]# paste -d : user.txt pass.txt
jose:123456
jerry:000000

[root@rocky8 ~]# paste -d : user.txt pass.txt | chpasswd

分析文本的工具

文本数据统计:wc

整理文本:sort

比较文件:diff和patch

收集文本统计数据 wc

wc 命令可用于统计文件的行总数、单词总数、字节总数和字符总数,可以对文件或STDIN中的数据统计

格式:

1
2
3
4
5
6
7
8
wc [OPTION]... [FILE]...

#常用选项
-l|--lines #只计数行数
-w|--words #只计数单词总数
-c|--bytes #只计数字节总数
-m|--chars #只计数字符总数
-L|--max-line-length #显示文件中最长行的长度

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
wc story.txt 
39 237 1901 story.txt
行数 单词数 字节数

[root@rocky8 ~]# cat title.txt
ceo
coo
cto

[root@rocky8 ~]# wc title.txt
3 3 12 title.txt

#仅显示行
[root@rocky8 ~]# wc -l title.txt
3 title.txt

#管道重定向
[root@rocky8 ~]# cat title.txt | wc -l
3

#不显示第一行
[root@rocky8 ~]# df | tail -n $(echo `df|wc -l` -1|bc)
devtmpfs 897456 0 897456 0% /dev
tmpfs 916616 0 916616 0% /dev/shm
tmpfs 916616 20332 896284 3% /run
tmpfs 916616 0 916616 0% /sys/fs/cgroup
/dev/mapper/rl-root 136248320 3148716 133099604 3% /
/dev/mapper/rl-home 68124160 508224 67615936 1% /home
/dev/sda1 1038336 216736 821600 21% /boot
tmpfs 183320 0 183320 0% /run/user/0

文本排序 sort

把整理过的文本显示在STDOUT,不改变原始文件

格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sort [OPTION]... [FILE]...

#常用选项
-b|--ignore-leading-blanks #忽略文件中的空白
-f|--ignore-case #忽略大小写
-h|--human-numeric-sort #人类可读排序
-M|--month-sort #以月份排序
-n|--numeric-sort #以数字大小排序
-R|--random-sort #随机排序
-r|-reverse #倒序
-t|--field-separator=SEP #指定列分割符
-k|--key=KEYDEF #指定排序列
-u|--unique #去重
-z|--zero-terminated #以 NUL 字符而非换行符作为行尾分隔符

范例:

1
2
3
4
5
6
7
8
[root@rocky8 ~]# cut -d : -f 1,3 /etc/passwd | sort -t : -k 2 -nr | head -3
nobody:65534
varnish:1029
nginx:1028

#统计日志访问量
[root@rocky8 ~]# cut -d" " -f1 /var/log/nginx/access_log |sort -u|wc -l
202

范例:统计分区利用率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[root@rocky8 ~]# df
Filesystem 1K-blocks Used Available Use% Mounted on
devtmpfs 897456 0 897456 0% /dev
tmpfs 916616 0 916616 0% /dev/shm
tmpfs 916616 20332 896284 3% /run
tmpfs 916616 0 916616 0% /sys/fs/cgroup
/dev/mapper/rl-root 136248320 3149228 133099092 3% /
/dev/mapper/rl-home 68124160 508224 67615936 1% /home
/dev/sda1 1038336 216736 821600 21% /boot
tmpfs 183320 0 183320 0% /run/user/0

#查看分区最高利用率
[root@rocky8 ~]# df | tr -s ' ' % |cut -d % -f 5|tr -d '[:alpha:]'|sort -rn|head -1
21

[root@rocky8 ~]# df | tr -s ' ' % |cut -d % -f 5|tr -d '[:alpha:]'|sort -r
3
3
21
1
0
0
0
0

[root@rocky8 ~]# df | tr -s ' ' % |cut -d % -f 5|tr -d '[:alpha:]'|sort -n
0
0
0
0
1
3
3
21

[root@rocky8 ~]# df | tr -s ' ' % |cut -d % -f 5|tr -d '[:alpha:]'|sort -n|tail -n 1
21

[root@rocky8 ~]# df | tr -s ' ' % |cut -d % -f 5|tr -d '[:alpha:]'|sort -nr|head -n 1
21

面试题:有两个文件,a.txt与b.txt ,合并两个文件,并输出时确保每个数字也唯一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[root@rocky8 ~]# cat 1.txt 
1111
222
33
4444
5
666
77
8
9999
100000

[root@rocky8 ~]# cat 2.txt
8
1111
33
222
100000
77
159
666
137
189

[root@rocky8 ~]# cat 1.txt 2.txt |sort -n |uniq
[root@rocky8 ~]# cat 1.txt 2.txt |sort -nu
5
8
33
77
137
159
189
222
666
1111
4444
9999
100000

去重 uniq

uniq命令从输入中删除前后相接的重复的行,常和 sort 配合使用

格式:

1
2
3
4
5
6
7
8
uniq [OPTION]... [INPUT [OUTPUT]]

#常见选项
-c|--count #显示每行出现次数
-d|--repeated #仅显示有重复行
-D #显示所有重复行具体内容
-u|--unique #仅显示不重复的行
-z|--zero-terminated #以 NUL 字符而非换行符作为行尾分隔符

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@rocky8 ~]# sort -n 1.txt 2.txt |uniq -c
1 5
2 8
2 33
2 77
1 137
1 159
1 189
2 222
2 666
2 1111
1 4444
1 9999
2 100000

范例:统计日志访问量最多的前三名的请求

1
2
[root@rocky86 ~]# lastb | head -n $(echo `lastb | wc -l`-2 | bc) | tr -s ' ' | cut -d " " -f3 | sort | uniq -c | sort -nr | head -3
17 10.0.0.1

范例:取两个文件的相同和不同的行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#取相同行
[root@rocky86 ~]# cat 1.txt 2.txt | sort | uniq -d
100000
1111
222
33
666
77
8

#取文件的不同行
[root@rocky86 ~]# cat 1.txt 2.txt | sort | uniq -u
137
159
189
4444
5
9999

比较文件

diff

diff 命令比较两个文件之间的区别

格式:

1
2
3
4
diff [OPTION]... FILES

#常用选项
-u #选项来输出“统一的(unified)”diff格式文件,最适用于补丁文件

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[root@rocky8 ~]# cat f1.txt 
1
2
3

[root@rocky8 ~]# cat f2.txt
3
7
2
5

[root@rocky8 ~]# diff -u f1.txt f2.txt
--- f1.txt 2024-09-08 16:54:47.648321426 +0800
+++ f2.txt 2024-09-08 16:55:03.037322301 +0800
@@ -1,3 +1,4 @@ #比较结果,- 表示第一个文件, + 表示第二个文件,第一个文件有3行,第二个文件有4行
-1 #第一个文件此行去掉
-2 #第一个文件此行去掉
3
+7 #第一个文件加上此行内容
+2 #第一个文件加上此行内容
+5 #第一个文件加上此行内容

#将对比结果保存至文件
[root@rocky8 ~]# diff -u f1.txt f2.txt > f.patch

[root@rocky8 ~]# rm -f f2.txt

#用来还原
[root@rocky8 ~]# patch -b f1.txt f.patch
patching file f1.txt

#原还出来的是 f2
[root@rocky8 ~]# cat f1.txt
3
7
2
5

[root@rocky8 ~]# cat f1.txt.orig
1
2
3

patch

patch 复制在其它文件中进行的改变(要谨慎使用)

格式:

1
2
3
4
patch [OPTION]... [ORIGFILE [PATCHFILE]]

#常用选项
-b|--backup #备份

范例:

1
2
3
4
5
[root@rocky8 ~]# patch -b f1.txt f.patch 
patching file f1.txt

#用f1.txt和 f.patch 生成一个名为f1.txt的文件,但内容是原来f2.txt文件中的内容,
#-b 选项则备份了现有的 f1.txt

vimdiff

相当于 vim -d

1
2
3
4
5
6
7
[root@rocky8 ~]# which vimdiff 
/usr/bin/vimdiff

[root@rocky8 ~]# ll /usr/bin/vimdiff
lrwxrwxrwx. 1 root root 3 Aug 2 2022 /usr/bin/vimdiff -> vim

[root@rocky8 ~]# vimdiff f1.txt f2.txt

cmp

范例:查看二进制文件的不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@rocky8 ~]# ll /usr/bin/dir /usr/bin/ls
-rwxr-xr-x. 1 root root 143400 Oct 21 2021 /usr/bin/dir
-rwxr-xr-x. 1 root root 143400 Oct 21 2021 /usr/bin/ls

[root@rocky8 ~]# ll /usr/bin/dir /usr/bin/ls -i
267929 -rwxr-xr-x. 1 root root 143400 Oct 21 2021 /usr/bin/dir
267950 -rwxr-xr-x. 1 root root 143400 Oct 21 2021 /usr/bin/ls

[root@rocky8 ~]# diff /usr/bin/dir /usr/bin/ls
Binary files /usr/bin/dir and /usr/bin/ls differ

[root@rocky8 ~]# cmp /bin/dir /bin/ls
/bin/dir /bin/ls differ: byte 793, line 1

#跳过前735个字节,观察后面30个字节
[root@rocky8 ~]# hexdump -s 735 -Cn 30 /bin/ls
000002df 00 03 00 00 00 00 00 00 00 04 00 00 00 10 00 00 |................|
000002ef 00 01 00 00 00 47 4e 55 00 00 00 00 00 03 |.....GNU......|
000002fd

[root@rocky8 ~]# hexdump -s 735 -Cn 30 /bin/dir
000002df 00 03 00 00 00 00 00 00 00 04 00 00 00 10 00 00 |................|
000002ef 00 01 00 00 00 47 4e 55 00 00 00 00 00 03 |.....GNU......|
000002fd

练习

  1. 找出ifconfig “网卡名” 命令结果中本机的IPv4地址
  2. 查出分区空间使用率的最大百分比值
  3. 查出用户UID最大值的用户名、UID及shell类型
  4. 查出/tmp的权限,以数字方式显示
  5. 统计当前连接本机的每个远程主机IP的连接数,并按从大到小排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.
[root@rocky8 ~]# ifconfig | head -2 | tail -1 | tr -s ' ' | cut -d ' ' -f 3
192.168.1.11

2.
[root@rocky8 ~]# df | tr -s ' ' % | cut -d % -f 5 | tr -d "[:alpha:]" | sort -nr | head -1
21

3.
[root@rocky8 ~]# cut -d : -f 1,3-4,7 /etc/passwd | sort -t : -k 2 -nr | head -1
nobody:65534:65534:/sbin/nologin

4.
[root@rocky8 ~]# stat /tmp/ | tail -n +4 | head -1 | tr -d "[:alpha:]" | cut -d : -f 2 | tr -d "(/)"
1777

5.
[root@rocky8 ~]# lastb | head -n $(echo `lastb | wc -l`-2 | bc) | tr -s ' ' | cut -d " " -f3 | sort | uniq -c | sort -nr | head -3

正则表达式

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

正则表达式被很多程序和开发语言所广泛支持:vim, less,grep,sed,awk, nginx,mysql 等;

正则表达式分两类:

  • 基本正则表达式:BRE Basic Regular Expressions;
  • 扩展正则表达式:ERE Extended Regular Expressions;

正则表达式引擎:

采用不同算法,检查处理正则表达式的软件模块,如:PCRE(Perl Compatible Regular Expressions)

正则表达式的元字符分类:

字符匹配、匹配次数、位置锚定、分组

帮助:man 7 regex

基本正则表达式元字符

字符匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.               #匹配任意单个字符(除了\n),可以是一个汉字或其它国家的文字
[] #匹配指定范围内的任意单个字符,示例:[wang] [0-9] [a-z] [a-zA-Z]
[^] #匹配指定范围外的任意单个字符,示例:[^wang]
[:alnum:] #字母和数字
[:alpha:] #代表任何英文大小写字符,亦即 A-Z, a-z
[:lower:] #小写字母,示例:[[:lower:]],相当于[a-z]
[:upper:] #大写字母
[:blank:] #空白字符(空格和制表符)
[:space:] #包括空格、制表符(水平和垂直)、换行符、回车符等各种类型的空白,比[:blank:]包含的范围广
[:cntrl:] #不可打印的控制字符(退格、删除、警铃...)
[:digit:] #十进制数字
[:xdigit:] #十六进制数字
[:graph:] #可打印的非空白字符
[:print:] #可打印字符
[:punct:] #标点符号

--------------------------------------

\s #匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [\f\r\t\v], Unicode正则表达式会匹配全角空格符
\S #匹配任何非空白字符。等价于 [^\f\r\t\v]
\w #匹配一个字母,数字,下划线,汉字,其它国家文字的字符,等价于[_[:alnum:]字]
\W #匹配一个非字母,数字,下划线,汉字,其它国家文字的字符,等价于[^_[:alnum:]字]

范例:匹配任意单个字符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@rocky8 ~]# grep "r.t" /etc/passwd
operator:x:11:0:operator:/root:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin

[root@rocky8 ~]# grep "r..t" /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

[root@rocky8 ~]# echo a你好b | grep a..b
a你好b

[root@rocky8 ~]# echo aaa你好b | grep a..b
aaa你好b

[root@rocky8 ~]# echo aaa你-b | grep a..b
aaa你-b

范例:范围匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
[root@rocky8 ~]# ls /etc/ | grep 'rc[.0-6]'
rc0.d
rc1.d
rc2.d
rc3.d
rc4.d
rc5.d
rc6.d
rc.d
rc.local

[root@rocky8 ~]# ls /etc/ | grep 'rc[.0-6].'
rc0.d
rc1.d
rc2.d
rc3.d
rc4.d
rc5.d
rc6.d
rc.d
rc.local

[root@rocky8 ~]# ls /etc/ | grep 'rc[.0-6]\.'
rc0.d
rc1.d
rc2.d
rc3.d
rc4.d
rc5.d
rc6.d

[root@rocky8 ~]# echo ma | grep [lmn]
ma

[root@rocky8 ~]# echo ma | grep [^lmn]
ma

[root@rocky8 ~]# echo "ab1c2d3e4444fABCg$%^" | grep "[0-Z]"
ab1c2d3e4444fABCg$%^

[root@rocky8 ~]# echo "ab1c2d3e4444fABCg$%^" | grep "[0-z]"
ab1c2d3e4444fABCg$%^

[root@rocky8 ~]# echo "ab1c2d3e4444fABCg$%^" | grep "[0-9]"
ab1c2d3e4444fABCg$%^

[root@rocky8 ~]# echo "ab1c2d3e4444fABCg$%^" | grep "[a-z]"
ab1c2d3e4444fABCg$%^

[root@rocky8 ~]# echo "ab1c2d3e4444fABCg$%^" | grep "[A-Z]"
ab1c2d3e4444fABCg$%^

[root@rocky8 ~]# echo "ab1c2d3e4444fABCg$%^" | grep "[^0-Z]"
ab1c2d3e4444fABCg$%^

[root@rocky8 ~]# echo "ab1c2d3e4444fABCg$%^" | grep "[[:punct:]]"
ab1c2d3e4444fABCg$%^

#匹配空格
[root@rocky8 ~]# echo "1 2 3" | grep "[[:blank:]]"
1 2 3

#匹配非空格
[root@rocky8 ~]# echo "1 2 3" | grep "[^[:blank:]]"
1 2 3

匹配次数

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

1
2
3
4
5
6
7
8
*           #匹配前面的字符任意次,包括0次,贪婪模式:尽可能长的匹配
.* #任意长度的任意字符
\? #匹配其前面的字符出现0次或1次,即:可有可无
\+ #匹配其前面的字符出现最少1次,即:肯定有且 >=1 次
\{n\} #匹配前面的字符n次
\{m,n\} #匹配前面的字符至少m次,至多n次
\{,n\} #匹配前面的字符至多n次,<=n
\{n,\} #匹配前面的字符至少n次

范例:

1
2
3
4
5
6
7
8
9
10
11
[root@rocky8 ~]# echo "rt" | grep "ro*t"
rt

[root@rocky8 ~]# echo "rot" | grep "ro*t"
rot

[root@rocky8 ~]# echo "root" | grep "ro*t"
root

[root@rocky8 ~]# echo "rooooooot" | grep "ro*t"
rooooooot

范例:任意长度任意字符

1
[root@rocky8 ~]# grep "r.*t" /etc/passwd

范例:0次或1次

1
2
3
4
5
[root@rocky8 ~]# echo /etc/ | grep "/etc/\?"
/etc/

[root@rocky8 ~]# echo /etc | grep "/etc/\?"
/etc

范例:1次或多次

1
2
3
4
5
6
7
8
9
10
[root@rocky8 ~]# echo google | grep "go\+gle"
google

[root@rocky8 ~]# echo gogle | grep "go\+gle"
gogle

[root@rocky8 ~]# echo ggle | grep "go\+gle"

[root@rocky8 ~]# echo gooogle | grep "go\+gle"
gooogle

范例:自定义次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#2次
[root@rocky8 ~]# echo google | grep "go\{2\}gle"
google

#2次到5次
[root@rocky8 ~]# echo google | grep "go\{2,5\}gle"
google

#2次到多次
[root@rocky8 ~]# echo google | grep "go\{2,\}gle"
google

#0次到2次
[root@rocky8 ~]# echo google | grep "go\{,2\}gle"
google

范例: 匹配正负数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@rocky8 ~]# echo -1 -2 123 -123 234 | grep '-\?[0-9]\+'
grep: invalid option -- '\'
Usage: grep [OPTION]... PATTERN [FILE]...
Try 'grep --help' for more information.

# 需要把 - 转义
[root@rocky8 ~]# echo -1 -2 123 -123 234 | grep '\-\?[0-9]\+'
-1 -2 123 -123 234


[root@rocky8 ~]# echo -1 -2 123 -123 234 | grep -E '-?[0-9]+'
grep: invalid option -- '?'
Usage: grep [OPTION]... PATTERN [FILE]...
Try 'grep --help' for more information.

[root@rocky8 ~]# echo -1 -2 123 -123 234 | grep -E '\-?[0-9]+'
-1 -2 123 -123 234

[root@rocky8 ~]# echo -1 -2 123 -123 234 | grep -E -- '-?[0-9]+'
-1 -2 123 -123 234

[root@rocky8 ~]# echo -1 -2 123 -123 234 | grep -E '(-)?[0-9]+'
-1 -2 123 -123 234

范例: 取IP地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@rocky8 ~]# ifconfig ens160
ens160: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.1.11 netmask 255.255.255.0 broadcast 192.168.1.255
inet6 fe80::20c:29ff:fe51:f282 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:51:f2:82 txqueuelen 1000 (Ethernet)
RX packets 2128 bytes 179640 (175.4 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1506 bytes 144441 (141.0 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0


[root@rocky8 ~]# ifconfig ens160 | grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}' | head -1
192.168.1.11

# grep -o 只输出匹配到的

[root@rocky8 ~]# ifconfig ens160 | grep netmask | grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}' | head -1
192.168.1.11

位置锚定

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

1
2
3
4
5
6
7
8
9
10
^               #行首锚定, 用于模式的最左侧
$ #行尾锚定,用于模式的最右侧
^PATTERN$ #用于模式匹配整行
^$ #空行
^[[:space:]]*$ #空白行
\< 或 \b #词首锚定,用于单词模式的左侧
\> 或 \b #词尾锚定,用于单词模式的右侧
\<PATTERN\> #匹配整个单词

#注意: 单词是由字母,数字,下划线组成

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#以 # 开头
[root@rocky8 ~]# grep "^#" /etc/fstab

#以 bash 结尾
[root@rocky8 ~]# grep "bash$" /etc/passwd

#所有空行
[root@rocky8 ~]# grep "^$" /etc/profile

#所有非空行
[root@rocky8 ~]# grep -v "^$" /etc/profile

#所有非注释行
[root@rocky8 ~]# grep "^[^#]" /etc/profile

#排除所有空行和注释行
[root@rocky8 ~]# grep -v "^$" /etc/profile | grep -v "^#"

#同上
[root@rocky8 ~]# grep -v -e "^$" -v -e "^#" /etc/profile

#同上
[root@rocky8 ~]# grep -v "^$\|^#" /etc/profile

#匹配单词
[root@rocky8 ~]# echo mage | grep "\<mage\>"
mage

[root@rocky8 ~]# echo mage- | grep "\<mage\>"
mage-

[root@rocky8 ~]# echo magee | grep "\<mage\>"

范例:

1
2
3
4
5
[root@rocky8 ~]# grep '^[^#]' /etc/fstab 
/dev/mapper/rl-root / xfs defaults 0 0
UUID=a8e3c539-a7e7-4828-9a4d-08a9ec290d07 /boot xfs defaults 0 0
/dev/mapper/rl-home /home xfs defaults 0 0
/dev/mapper/rl-swap none swap defaults 0 0

分组其它

分组

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

后向引用:分组括号中的模式匹配到的内容会被正则表达式引擎记录于内部的变量中,这些变量的命名方式为: \1, \2, \3, …

\1 表示从左侧起第一个左括号以及与之匹配右括号之间的模式所匹配到的字符

注意: \0 表示正则表达式匹配的所有字符

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#ab,然后c出现3次
[root@rocky8 ~]# echo abccc | grep "abc\{3\}"
abccc

#abc作为一个整体出现3次
[root@rocky8 ~]# echo abcabcabc | grep "\(abc\)\{3\}"
abcabcabc

#后向引用
[root@rocky8 ~]# echo abcabcabc | grep "\(abc\)\1"
abcabcabc

[root@rocky8 ~]# echo abcabcabc | grep "\(abc\)\1\1"
abcabcabc


#\1表示引用第一个分组的内容,即 abc
[root@rocky8 ~]# echo "abcdefabc" | grep "\(abc\)def\1"
abcdefabc

#\1表示引用第一个分组的内容,即abc,\{3\}表示引用内容出现3次
[root@rocky8 ~]# echo "abc-def-abcabcabc" | grep "\(abc\)-\(def\)-\1\{3\}"
abc-def-abcabcabc

[root@rocky8 ~]# echo abc-def-abcabcabc-def-abc-defdef | grep "^\(abc\)-\(def\)-\1\{3\}-\2-\1-\2\{2\}"
abc-def-abcabcabc-def-abc-defdef

#取IP
[root@rocky8 ~]# ifconfig ens160 | grep -o "\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}"|head -1
192.168.1.11

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

或者

或者:|

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
a\|b			#a或b
C\|cat #C或cat
\(C\|c\)at #Cat或cat


#a或b a\|b
[root@rocky8 ~]# echo "a" | grep "a\|b"
a
[root@rocky8 ~]# echo "b" | grep "a\|b"
b
[root@rocky8 ~]# echo "ab" | grep "a\|b"
ab
[root@rocky8 ~]# echo "abc" | grep "a\|b"
abc
[root@rocky8 ~]# echo "cab" | grep "a\|b"
cab
[root@rocky8 ~]# echo "acb" | grep "a\|b"
acb


#12a 或 b 12a\|b
[root@rocky8 ~]# echo "12a" | grep "12a\|b"
12a
[root@rocky8 ~]# echo "b" | grep "12a\|b"
b
[root@rocky8 ~]# echo "12ab" | grep "12a\|b"
12ab
[root@rocky8 ~]# echo "12ba" | grep "12a\|b"
12ba


#12a 或 12b 12a\|12b
[root@rocky8 ~]# echo "12a" | grep "12\(a\|b\)"
12a
[root@rocky8 ~]# echo "12b" | grep "12\(a\|b\)"
12b
[root@rocky8 ~]# echo "12ab" | grep "12\(a\|b\)"
12ab
[root@rocky8 ~]# echo "12ba" | grep "12\(a\|b\)"
12ba


#12a 或 12b 12\(a\|b\)
[root@rocky8 ~]# echo "12a" | grep "12\(a\|b\)"
12a
[root@rocky8 ~]# echo "12b" | grep "12\(a\|b\)"
12b
[root@rocky8 ~]# echo "12ab" | grep "12\(a\|b\)"
12ab
[root@rocky8 ~]# echo "12ba" | grep "12\(a\|b\)"
12ba


#12a 或 12b 12[ab]
[root@rocky8 ~]# echo "12a" | grep "12[ab]"
12a
[root@rocky8 ~]# echo "12b" | grep "12[ab]"
12b
[root@rocky8 ~]# echo "12ab" | grep "12[ab]"
12ab
[root@rocky8 ~]# echo "12ba" | grep "12[ab]"
12ba

范例:排除空行和#开头的行

1
2
3
4
5
6
7
[root@rocky8 ~]# grep -v "^#" /etc/httpd/conf/httpd.conf | grep -v ^$

[root@rocky8 ~]# grep -v '^#\|^$' /etc/httpd/conf/httpd.conf

[root@rocky8 ~]# grep -v '^\(#\|$\)' /etc/httpd/conf/httpd.conf

[root@rocky8 ~]# grep "^[^#]" /etc/httpd/conf/httpd.conf

正则表达式练习

  1. 显示/proc/meminfo文件中以大小s开头的行(要求:使用两种方法)
  2. 显示/etc/passwd文件中不以/bin/bash结尾的行
  3. 显示用户rpc默认的shell程序
  4. 找出/etc/passwd中的两位或三位数
  5. 显示CentOS7的/etc/grub2.cfg文件中,至少以一个空白字符开头的且后面有非空白字符的行
  6. 找出“netstat -tan”命令结果中以LISTEN后跟任意多个空白字符结尾的行
  7. 显示CentOS7上所有UID小于1000以内的用户名和UID
  8. 添加用户bash、testbash、basher、sh、nologin(其shell为/sbin/nologin),找出/etc/passwd用户名 和shell同名的行
  9. 利用df和grep,取出磁盘各分区利用率,并从大到小排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
1.
[root@rocky8 ~]# grep "^\(s\|S\)" /proc/meminfo

[root@rocky8 ~]# grep "^[sS]" /proc/meminfo


2.
[root@rocky8 ~]# grep -v "/bin/bash$" /etc/passwd | grep bin/bash

3.
[root@rocky8 ~]# grep rpc /etc/passwd | cut -d : -f 7

4.
[root@rocky8 ~]# grep ":\([0-9]\{2\}\|[0-9]\{3\}\):" /etc/passwd

5.
[root@rocky8 ~]# grep "^ \+" /etc/grub2.cfg

6.
[root@rocky8 ~]# netstat -tan | grep "LISTEN \+$"

7.
[root@rocky8 ~]# grep -v "[0-9]\{4\}" /etc/passwd | cut -d : -f 1,3

8.
[root@rocky8 ~]# cut -d : -f 1,7 /etc/passwd | grep -E "^([^:]+):/s?bin/\\1$"

9.
[root@rocky8 ~]# df | grep -o "[0-9]\{,4\}%" | tail -n +2 | sort -nr

扩展正则表达式元字符

字符匹配

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

匹配次数

1
2
3
4
5
*               #匹配前面字符任意次
? #0或1次
+ #1次或多次
{n} #匹配n次
{m,n} #至少m,至多n次

位置锚定

1
2
3
4
^           #行首
$ #行尾
\<, \b #词首
\>, \b #词尾

分组其它

1
2
3
4
5
() 分组         #后向引用:\1, \2, ... 注意: \0 表示正则表达式匹配的所有字符
| #或者
a|b #a或b
C|cat #C或cat
(C|c)at #Cat或cat

范例:

1
2
[root@rocky8 ~]# ifconfig | grep -Ewo "(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])"|head -1
192.168.1.11

扩展正则表达式练习

  1. 显示三个用户root、mage、wang的UID和默认shell
  2. 找出/etc/rc.d/init.d/functions文件中行首为某单词(包括下划线)后面跟一个小括号的行
  3. 使用egrep取出/etc/rc.d/init.d/functions中其基名
  4. 使用egrep取出上面路径的目录名
  5. 统计last命令中以root登录的每个主机IP地址登录次数
  6. 利用扩展正则表达式分别表示0-9、10-99、100-199、200-249、250-255
  7. 显示ifconfig命令结果中所有IPv4地址
  8. 将此字符串:welcome to magedu linux 中的每个字符去重并排序,重复次数多的排到前面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1.
[root@rocky8 ~]# grep "^\(root\|mage\|wang\)" /etc/passwd | cut -d : -f 1,7

2.
[root@rocky8 ~]# grep "^[[:alpha:]]*()" /etc/rc.d/init.d/functions

3.
[root@rocky8 ~]# echo "/etc/rc.d/init.d/functions" | egrep -o '[^/]+$'

4.
[root@rocky8 ~]# echo "/etc/rc.d/init.d/functions" | egrep -o '^/.*/'

5.
[root@rocky8 ~]# last | grep root | awk '{print $3}' | grep -oP '\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b' | sort | uniq -c

6.
[0-9] 1[0-9][0-9] 2[0-4][0-9] 25[0-5]

7.
[root@rocky8 ~]# ifconfig | grep -Eo "(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])"

8.
[root@rocky8 ~]# echo "welcome to megedu linux" | fold -w1 | grep -v "^ $" | sort | uniq -c | sort -nr
4 e
2 u
2 o
2 m
2 l
1 x
1 w
1 t
1 n
1 i
1 g
1 d
1 c

文本处理三剑客

grep 命令主要对文本的(正则表达式)行基于模式进行过滤

sed:stream editor,文本编辑工具

awk:Linux上的实现gawk,文本报告生成器

文本处理三剑客之 grep

grep: Global search REgular expression and Print out the line

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

模式:由正则表达式字符及文本字符所编写的过滤条件

帮助

1
https://man7.org/linux/man-pages/man1/grep.1.html

格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
grep [OPTIONS] PATTERN [FILE...]

#常见选项:
-E|--extended-regexp #使用ERE,相当于egrep
-F|--fixed-strings #不支持正则表达式,相当于fgrep
-G|--basic-regexp #将样式视为普通的表示法来使用
-P|--perl-regexp #支持Perl格式的正则表达式
-e|--regexp=PATTERN #实现多个选项间的逻辑or关系,如:grep –e ‘cat ' -e ‘dog' file
-f|--file=FILE #从文件中读取匹配规则,每行一条
-i|--ignore-case #忽略字符大小写
-w|--word-regexp #匹配整个单词
-x|--line-regexp #整行匹配
-s|--no-messages #不显示错误信息
-v|--invert-match #显示没有被匹配上的行,即取反
-B|--before-context=N #显示匹配到的字符串所在的行及其前N行
-A|--after-context=N #显示匹配到的字符串所在的行及其后n行
-C|--context=N #显示匹配到的字符串所在的行及其前后各N行
-N #同 --context=N
--color=auto #对匹配到的内容高亮显示[always|never|auto]
-m|--max-count=N #只匹配N行,是行,不是次数,一行可能匹配两个,但是,这里是行
-b|--byte-offset #显示匹配行第一个字符的编号
-n|--line-number #显示匹配的行号
-H|--with-filename #显示匹配行所在的文件名
-h|--no-filename #不显示匹配行所在的文件名
-o|--only-matching #仅显示匹配到的字符串
-q|--quiet|--silent #静默模式,不输出任何信息,结果要从变量$0拿
--binary-files=TYPE #指定二进制文件类型 [binary|text|without-match]
-a|--text #同 --binary-files=text
-I #同 --binary-files=without-match
-d|--directories=ACTION #怎样查找目录 [read|recurse|skip]
-D|--devices=ACTION #怎样查找设备文件 [read|skip]
-r|--recursive #递归目录,但不处理软链接
-R|--dereference-recursive #递归目录,但处理软链接
-L|--files-without-match #显示没有匹配上的文件名,只显示文件名
-l|--files-with-matches #显示匹配上的文件名,只显示文件名
-c|--count #统计匹配的行数,是行数,一行可以匹配一次到多次

范例:标准输入

1
2
3
4
5
6
7
[root@rocky86 ~]# grep hello
123hello456
123hello456

[root@rocky86 ~]# grep hello -
123hello456
123hello456

范例:处理文件

1
2
3
[root@rocky86 ~]# grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

范例:管道

1
2
3
[root@rocky86 ~]# cat /etc/passwd | grep root
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

范例:标准输入重定向

1
2
3
[root@rocky86 ~]# grep root < /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

范例:取前三行

1
2
3
4
[root@rocky86 ~]# grep -m 3 bin /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

范例:取反,取不匹配的行

1
2
3
4
5
6
7
8
9
10
11
[root@rocky86 ~]# grep -v 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
jose:x:1010:1010::/home/jose:/bin/bash
jerry:x:1011:1011::/home/jerry:/bin/bash
mage:x:1000:1000::/home/mage:/bin/bash

#不看注释行
[root@rocky86 ~]# grep -v "#" /etc/fstab

范例:不区分大小写

1
2
3
[root@rocky86 ~]# grep -i ROOt /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

范例:显示行号

1
2
3
[root@rocky86 ~]# grep  -n root /etc/passwd
1:root:x:0:0:root:/root:/bin/bash
10:operator:x:11:0:operator:/root:/sbin/nologin

范例:显示匹配的行数

1
2
[root@rocky86 ~]# grep -c root /etc/passwd
2

范例:仅显示匹配到的内容

1
2
3
4
5
[root@rocky86 ~]# grep -o root /etc/passwd
root
root
root
root

范例:静默模式

1
2
3
4
5
6
7
8
9
[root@rocky86 ~]# grep -q root /etc/passwd
[root@rocky86 ~]# echo $?
0

# 0 为执行成功,1 为执行失败

[root@rocky86 ~]# grep -q roottttttt /etc/passwd
[root@rocky86 ~]# echo $?
1

范例:显示匹配到的行及后两行

1
2
3
4
5
6
7
8
[root@rocky86 ~]# grep -A 2 root /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
--
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

范例:显示匹配到的行及前两行

1
2
3
4
5
6
7
8
9
10
[root@rocky86 ~]# grep -C 2 root /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
--
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

范例:显示匹配root的行或匹配 bash 的行

1
2
3
4
5
6
[root@rocky86 ~]# grep -e root -e bash /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
jose:x:1010:1010::/home/jose:/bin/bash
jerry:x:1011:1011::/home/jerry:/bin/bash
mage:x:1000:1000::/home/mage:/bin/bash

范例:显示匹配root的行且匹配 bash 的行

1
2
[root@rocky86 ~]# grep root /etc/passwd | grep bash
root:x:0:0:root:/root:/bin/bash

范例:从文件读取匹配规则

1
2
3
4
5
6
7
8
9
10
[root@rocky86 ~]# cat test.txt 
root
bash

[root@rocky86 ~]# grep -f test.txt /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
jose:x:1010:1010::/home/jose:/bin/bash
jerry:x:1011:1011::/home/jerry:/bin/bash
mage:x:1000:1000::/home/mage:/bin/bash

范例:递归匹配

1
2
3
4
5
#不处理链接
[root@rocky86 ~]# grep -r root /etc/*

#处理链接
[root@rocky86 ~]# grep -R root /etc/*

范例:只显示有匹配到的文件的文件名,不显示具体内容

1
2
3
[root@rocky86 ~]# grep root -l /etc/passwd /etc/sudoers /etc/my.cnf /etc/issue
/etc/passwd
/etc/sudoers

范例:显示内容来自于哪个文件

1
2
3
4
5
6
7
[root@rocky86 ~]# grep -H root /etc/passwd /etc/sudoers /etc/my.cnf /etc/issue
/etc/passwd:root:x:0:0:root:/root:/bin/bash
/etc/passwd:operator:x:11:0:operator:/root:/sbin/nologin
/etc/sudoers:## the root user, without needing the root password.
/etc/sudoers:## Allow root to run any commands anywhere
/etc/sudoers:root ALL=(ALL) ALL
/etc/sudoers:## cdrom as root

范例:命令行展开

1
2
3
4
5
6
7
8
9
10
[root@rocky86 ~]# grep `whoami` /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

[root@rocky86 ~]# grep $(whoami) /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

[root@rocky86 ~]# echo Linux123 | grep $(uname)
Linux123

范例:变量展开

1
2
3
[root@rocky86 ~]# grep "$USER" /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

范例:取CPU核数

1
2
[root@rocky86 ~]# grep -c processor /proc/cpuinfo
2

范例: 分区利用率最大的值

1
2
3
4
5
[root@rocky8 ~]# df | grep '^/dev/sd' | tr -s ' ' % | cut -d % -f 5 | sort -n | tail -1

[root@rocky8 ~]# df | grep '^/dev/sd' | grep -oE '\<[0-9]{,3}%' | tr -d % | sort -nr | head -1

[root@rocky8 ~]# df | grep '^/dev/sd' | grep -oE '\<[0-9]{,3}%' | grep -Eo '[0-9]+' | sort -nr | head -1

范例: 哪个IP和当前主机连接数最多的前三位

1
[root@rocky8 ~]# ss -nt | grep "^ESTAB" |tr -s ' ' : |cut -d: -f6|sort |uniq -c|sort -nr|head -n3

范例: 连接状态的统计

1
2
3
4
5
6
7
[root@rocky8 ~]# ss -nta | grep -v '^State' |cut -d" " -f1|sort |uniq -c
1 ESTAB
3 LISTEN

[root@rocky8 ~]# ss -nta | tail -n +2 |cut -d" " -f1|sort |uniq -c
1 ESTAB
3 LISTEN

范例:

1
2
3
4
5
6
[root@rocky8 ~]# grep -v "^#" /etc/profile | grep -v "^$"
[root@rocky8 ~]# grep -v "^#\|^$" /etc/profile
[root@rocky8 ~]# grep -v "^\(#\|$\)" /etc/profile
[root@rocky8 ~]# grep -Ev "^(#|$)" /etc/profile
[root@rocky8 ~]# egrep -v "^(#|$)" /etc/profile
[root@rocky8 ~]# egrep -v '^(#|$)' /etc/httpd/conf/httpd.conf

范例:

1
2
3
4
5
6
[root@rocky8 ~]# grep -o r..t /etc/passwd
root
root
root
root
r/ft

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@rocky8 ~]# ifconfig | grep -E '[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}'
inet 192.168.1.11 netmask 255.255.255.0 broadcast 192.168.1.25
inet 127.0.0.1 netmask 255.0.0.0

[root@rocky8 ~]# ifconfig | grep -E '([0-9]{1,3}.){3}[0-9]{1,3}'
inet 192.168.1.11 netmask 255.255.255.0 broadcast 192.168.1.25
inet 127.0.0.1 netmask 255.0.0.0

[root@rocky8 ~]# ifconfig ens160 | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1
192.168.1.11

[root@rocky8 ~]# cat > regex.txt << EOF
> ([0-9]{1,3}\.){3}[0-9]{1,3}
> EOF
[root@rocky8 ~]# ifconfig | grep -oEf regex.txt
192.168.1.11
255.255.255.0
192.168.1.255
127.0.0.1
255.0.0.0

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@rocky8 ~]# grep -E 'root|bash' /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
jose:x:1000:1000::/home/jose:/bin/bash
u1:x:1024:1024::/home/u1:/bin/bash
u2:x:1025:1025::/home/u2:/bin/bash
u3:x:1026:1026::/home/u3:/bin/bash
jerry:x:1001:1001:tom to jerry:/home/tom:/bin/bash
bash:x:1030:1032::/home/bash:/sbin/nologin
basher:x:1031:1033::/home/basher:/sbin/nologin
testbash:x:1034:1036::/home/testbash:/sbin/nologin

[root@rocky8 ~]# grep -e 'root' -e 'bash' /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
jose:x:1000:1000::/home/jose:/bin/bash
u1:x:1024:1024::/home/u1:/bin/bash
u2:x:1025:1025::/home/u2:/bin/bash
u3:x:1026:1026::/home/u3:/bin/bash
jerry:x:1001:1001:tom to jerry:/home/tom:/bin/bash
bash:x:1030:1032::/home/bash:/sbin/nologin
basher:x:1031:1033::/home/basher:/sbin/nologin
testbash:x:1034:1036::/home/testbash:/sbin/nologin

范例:

1
2
3
4
5
6
7
[root@rocky8 ~]# grep -w root /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

[root@rocky8 ~]# grep '\<root\>' /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@rocky8 ~]# 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
nologin:x:1033:1035::/home/nologin:/sbin/nologin

[root@rocky8 ~]# 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
nologin:x:1033:1035::/home/nologin:/sbin/nologin

[root@rocky8 ~]# egrep "^(.*)\>.*\<\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
nologin:x:1033:1035::/home/nologin:/sbin/nologin

范例: 过滤掉文件的注释(包括#号的行)和空行

1
2
3
4
5
6
[root@rocky8 ~]# grep -Ev "#|^$)" /etc/fstab 

/dev/mapper/rl-root / xfs defaults 0 0
UUID=a8e3c539-a7e7-4828-9a4d-08a9ec290d07 /boot xfs defaults 0 0
/dev/mapper/rl-home /home xfs defaults 0 0
/dev/mapper/rl-swap none swap defaults 0 0

范例:面试题,算出所有人的年龄总和

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@rocky8 ~]# cat age.txt 
xiaoming=20
xiaohong=18
xiaoqiang=22

[root@rocky8 ~]# cut -d = -f 2 age.txt | tr '\n' + | grep -Eo ".*[0-9]" | bc
60

[root@rocky8 ~]# grep -Eo "[0-9]+" age.txt | tr '\n' + | grep -Eo ".*[0-9]" | bc
60

[root@rocky8 ~]# grep -Eo "[0-9]+" age.txt | paste -s -d + | bc
60

文本处理三剑客之 sed

sed 工作原理

sed 即 Stream EDitor,和 vi 不同,sed是行编辑器

10

Sed是从文件或管道中读取一行,处理一行,输出一行;再读取一行,再处理一行,再输出一行,直到最后一行。

每当处理一行时,把当前处理的行存储在临时缓冲区 模式空间(Pattern Space) 中,接着用sed命令处理缓冲区中的内容,处理完成后, 把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。

一次处理一行的设计模式使得sed性能很高,sed在读取大文件时不会出现卡顿的现象。

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

相关文档

http://sed.sourceforge.net/

https://man7.org/linux/man-pages/man1/sed.1.html

http://www.gnu.org/software/sed/manual/sed.html

sed 基本用法

格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sed [OPTION]... {script-only-if-no-other-script} [input-file]...

#常用选项
-n|--quiet|--silent #不输出模式空间内容到屏幕,即不自动打印
-e script|--expression=script #多个script,or 的关系
-f script-file|--file=script-file #从指定文件中读取编辑脚本
-i[SUFFIX]|--in-place[=SUFFIX] #-i 直接修改文件,-i.bak 以.bak后缀备份原文件
-c|--copy #配合i一起使用,保留原文件
-l N|--line-length=N #指定每行长度,如果过长,就拆分成多行,要加 -l
--posix #禁用GNU扩展
-E|-r|--regexp-extended #扩展正则表达式
-s|--separate #将多个文件视为独立文件,而不是单个连续的长文件流


-ir #此组合不支持
-ri #支持
-i -r #支持
-ni #此组合危险,会清空文件

script 格式:

1
'AddrCmd'          #地址命令 在哪些行,执行什么操作

地址格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#为空,则表示对全文进行处理

#单地址,指定行
N #具体行号
$ #最后一行
/pattern/ #能被匹配到的每一行

#范围地址
M,N #第M行到第N行
M,+N #第M行到第M+N行 3,+4 表示从第3行到第7行
/pattern1/,/pattern2/ #从第一个匹配行开始,到第二个匹配行中间的行
M,/pattern/ #行号开始,匹配结束
/pattern/,N #匹配开始,行号结束

#步长
1~2 #奇数行
2~2 #偶数行

命令:

1
2
3
4
5
6
7
8
9
10
11
p               #打印当前模式空间内容,追加到默认输出之后
Ip #忽略大小写输出
d #删除模式空间匹配的行,并立即启用下一轮循环
a [\]text #在指定行后面追加文本,支持使用\n实现多行追加
i [\]text #在行前面插入文本
c [\]text #替换行为单行或多行文本
w file #保存模式匹配的行至指定文件
r file #读取指定文件的文本至模式空间中匹配到的行后
= #为模式空间中的行打印行号
! #模式空间中匹配行取反处理
q #结束或退出sed

查找替代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
s/pattern/replace/修饰符    #查找替换,支持使用其它分隔符,可以是其它形式:s@@@,s###


#修饰符
g #行内全局替换
p #显示替换成功的行
w file #将替换成功的行保存至文件中
I|i #忽略大小写


#后向引用
\1 #第一个分组
\2 #第二个分组
\N #第N个分组
& #所有搜索内容

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#等待标准输入,script为空,默认是直接输出
[root@rocky8 ~]# sed ''
123
123

[root@rocky8 ~]# sed '' -
123
123

#script为空,默认输出内容
[root@rocky8 ~]# sed '' /etc/issue
\S
Kernel \r on an \m

#script 中执行p命令,再加上默认输出,所有每行都显示了两次
[root@rocky8 ~]# sed 'p' /etc/issue
\S
\S
Kernel \r on an \m
Kernel \r on an \m

#关闭默认输出,script 为空,则无任何输出
[root@rocky8 ~]# sed -n '' /etc/issue

#用 -n 选项关闭默认输出,script 中执行p命令
[root@rocky8 ~]# sed -n 'p' /etc/issue
\S
Kernel \r on an \m

#输出第一行
[root@rocky8 ~]# sed -n '1p' /etc/passwd
root:x:0:0:root:/root:/bin/bash

#输出最后一行
[root@rocky8 ~]# sed -n '$p' /etc/passwd
testbash:x:1034:1036::/home/testbash:/sbin/nologin

#正则匹配,输出包含root的行
[root@rocky8 ~]# sed -n '/root/p' /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

#正则匹配,输出以root开头的行
[root@rocky8 ~]# sed -n "/^root/p" /etc/passwd
root:x:0:0:root:/root:/bin/bash

#正则匹配,输出以bash结尾的行
[root@rocky8 ~]# sed -n "/bash$/p" /etc/passwd
root:x:0:0:root:/root:/bin/bash
jose:x:1000:1000::/home/jose:/bin/bash
jerry:x:1001:1001:tom to jerry:/home/tom:/bin/bash

#正则匹配,显示注释行行号
[root@rocky8 ~]# sed -n '/^#/=' /etc/fstab
2
3
4
5
6
7
8
9
10
11

#行号开始,正则结束
[root@rocky8 ~]# sed -n '8,/root/p' /etc/passwd
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin

范例:添加内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
[root@rocky8 ~]# cat > test.txt <<EOF
aaa
bbb
ccc
bbb
EOF

#匹配行后插入
[root@rocky8 ~]# sed '/bbb/a\---' test.txt
aaa
bbb
---
ccc
bbb
---

#指定行前插入
[root@rocky8 ~]# sed '2i\---' test.txt
aaa
---
bbb
ccc
bbb

[root@rocky8 ~]# sed '2,4i\---' test.txt
aaa
---
bbb
---
ccc
---
bbb

#替换,第一行替换成 ---
[root@rocky8 ~]# sed "1c\---" test.txt
---
bbb
ccc
bbb

#替换,第一行替换成两行
[root@rocky8 ~]# sed "1c\---\n+++" test.txt
---
+++
bbb
ccc
bbb

#替换,多行替换成一行
[root@rocky8 ~]# sed "1,2c\---" test.txt
---
ccc
bbb

# \ 的作用
[root@rocky8 ~]# sed '2a **********' test.txt
aaa
bbb
**********
ccc
bbb

[root@rocky8 ~]# sed '2a\ **********' test.txt
aaa
bbb
**********
ccc
bbb

范例:

1
2
3
4
5
6
7
8
9
#取IP行
[root@rocky8 ~]# ifconfig ens160 | head -n 2 | tail -n 1
inet 192.168.1.11 netmask 255.255.255.0 broadcast 192.168.1.255

[root@rocky8 ~]# ifconfig ens160 | sed -n '2p'
inet 192.168.1.11 netmask 255.255.255.0 broadcast 192.168.1.255

[root@rocky8 ~]# ifconfig ens160 | sed -n '/netmask/p'
inet 192.168.1.11 netmask 255.255.255.0 broadcast 192.168.1.255

范例:命令展开

1
2
3
4
5
6
7
8
9
10
11
[root@rocky8 ~]# sed -n "/$(whoami)/p" /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

#第1行
[root@rocky8 ~]# sed -n "$[$(id -u)+1]p" /etc/passwd
root:x:0:0:root:/root:/bin/bash

#倒数第2行
[root@rocky8 ~]# sed -n "$(echo $[`cat /etc/passwd|wc -l`-1])p" /etc/passwd
nologin:x:1033:1035::/home/nologin:/sbin/nologin

范例:变量展开

1
2
3
4
5
6
[root@rocky8 ~]# sed -n "/$USER/p" /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

[root@rocky8 ~]# number=1;sed -n "${number}p" /etc/passwd
root:x:0:0:root:/root:/bin/bash

范例:变量和命令

1
2
3
4
5
6
7
[root@rocky8 ~]# sed -n "$(echo $UID+1|bc)p" /etc/passwd
root:x:0:0:root:/root:/bin/bash

[root@rocky8 ~]# sed -n "$(echo $UID + 1|bc),$(echo $UID + 3|bc)p" /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

范例:

1
2
[root@rocky8 ~]# df | sed -n "/^\/dev\/sd/p"
/dev/sda1 1038336 216736 821600 21% /boot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
[root@rocky8 ~]# seq 10 | sed -n '2,4p'
2
3
4

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

[root@rocky8 ~]# seq 10 | sed -n '8,$p'
8
9
10

[root@rocky8 ~]# seq 10 | sed -n '1~2p'
1
3
5
7
9

[root@rocky8 ~]# seq 10 | sed -n '2~2p'
2
4
6
8
10

#删除奇数行
[root@rocky8 ~]# seq 10 | sed '1~2d'
2
4
6
8
10

#删除偶数行
[root@rocky8 ~]# seq 10 | sed '2~2d'
1
3
5
7
9

#或
[root@rocky8 ~]# seq 10 | sed -e '2d' -e '4d'
1
3
5
6
7
8
9
10

#多个script 写在一起
[root@rocky8 ~]# seq 10 | sed '2d;4d'
1
3
5
6
7
8
9
10

#只显示非#开头的行
[root@rocky8 ~]# sed -n '/^#/!p' /etc/fstab

/dev/mapper/rl-root / xfs defaults 0 0
UUID=a8e3c539-a7e7-4828-9a4d-08a9ec290d07 /boot xfs defaults 0 0
/dev/mapper/rl-home /home xfs defaults 0 0
/dev/mapper/rl-swap none swap defaults 0 0

范例:

1
2
3
4
5
6
#不显示注释行和空行
[root@rocky8 ~]# sed '/^$/d;/^#/d' /etc/fstab

[root@rocky8 ~]# sed -En '/^(#|$)/!p' /etc/fstab

[root@rocky8 ~]# grep -Ev "^(#|$)" /etc/fstab

范例:修改文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#修改前备份
[root@rocky8 ~]# seq 10 > 10.txt
[root@rocky8 ~]# sed -i.bak '2,7d' 10.txt

[root@rocky8 ~]# ll 10*
-rw-r--r--. 1 root root 9 Sep 14 13:04 10.txt
-rw-r--r--. 1 root root 21 Sep 14 13:04 10.txt.bak

[root@rocky8 ~]# cat 10.txt
1
8
9
10

[root@rocky8 ~]# cat 10.txt.bak
1
2
3
4
5
6
7
8
9
10

#不备份
[root@rocky8 ~]# rm 10.txt.bak
[root@rocky8 ~]# seq 10 > 10.txt
[root@rocky8 ~]# sed -i "2,7d" 10.txt

[root@rocky8 ~]# ll 10*
-rw-r--r--. 1 root root 9 Sep 14 13:06 10.txt

[root@rocky8 ~]# cat 10.txt
1
8
9
10

范例:删除注释行和空行

1
2
3
[root@rocky8 ~]# sed -i "/^#/d;/^$/d" /etc/fstab

[root@rocky8 ~]# sed -i "/^\(#\|$\)/d" /etc/fstab

范例: 搜索替换和&

1
2
3
4
5
6
7
8
9
10
11
12
[root@rocky8 ~]# sed  -n 's/root/ROOT/gp' /etc/passwd
ROOT:x:0:0:ROOT:/ROOT:/bin/bash
operator:x:11:0:operator:/ROOT:/sbin/nologin

[root@rocky8 ~]# sed -n 's/root/&er/gp' /etc/passwd
rooter:x:0:0:rooter:/rooter:/bin/bash
operator:x:11:0:operator:/rooter:/sbin/nologin

[root@rocky8 ~]# sed -n 's/r..t/&er/gp' /etc/passwd
rooter:x:0:0:rooter:/rooter:/bin/bash
operator:x:11:0:operator:/rooter:/sbin/nologin
ftp:x:14:50:FTP User:/var/fterp:/sbin/nologin

范例: 除指定文件外其余删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[root@rocky8 ~]# ls
f-1.txt f-2.txt f-3.txt f-4.txt f-5.txt f-6.txt f-7.txt f-8.txt

#取非 1|3|5|7
[root@rocky8 ~]# ls | grep -Ev "f-(1|3|5|7).txt"
f-2.txt
f-4.txt
f-6.txt
f-8.txt

#删除非1|3|5|7
[root@rocky8 ~]# rm -f `ls | grep -Ev "f-(1|3|5|7)\.txt"`
[root@rocky8 ~]# ls
f-1.txt f-3.txt f-5.txt f-7.txt



#取非 1|3|5|7
[root@rocky8 ~]# ls | sed -n "/f-[^1357]\.txt/p"
f-2.txt
f-4.txt
f-6.txt
f-8.txt

#删除非1|3|5|7
[root@rocky8 ~]# rm -f `ls | sed -n "/f-[^1357]\.txt/p"`
[root@rocky8 ~]# ls
f-1.txt f-3.txt f-5.txt f-7.txt

[root@rocky8 ~]# ls | grep -Ev 'f-(1|3|5|7)\.txt' | sed -n 's/.*/rm &/p'
rm f-2.txt
rm f-4.txt
rm f-6.txt
rm f-8.txt

[root@rocky8 ~]# ls | grep -Ev 'f-(1|3|5|7)\.txt' | sed -n 's/.*/rm &/p' | bash

[root@rocky8 ~]# ls
f-1.txt f-3.txt f-5.txt f-7.txt

范例: 获取分区利用率

1
2
3
4
5
6
7
[root@rocky8 ~]# df | sed -En '/^\/dev\/sd/s/.* ([0-9]+)%.*/\1/p'
21

[root@rocky8 ~]# df | sed -En 's/^\/dev.* ([0-9]+)%.*/\1/p' | sort -nr
21
3
1

范例:取IP 地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@rocky8 ~]# ifconfig ens160 | sed -nr "2s/[^0-9]+([0-9.]+).*/\1/p"
192.168.1.11

[root@rocky8 ~]# ifconfig ens160 | sed -nE "2s/[^0-9]+([0-9.]{7,15}).*/\1/p"
192.168.1.11

[root@rocky8 ~]# ifconfig ens160 | sed -nr "2s/[^0-9]+([0-9.]+) .*$/\1/p"
192.168.1.11

[root@rocky8 ~]# ifconfig ens160 | sed -n "2s/^.*inet //p" | sed -n "s/ netmask.*//p"
192.168.1.11

[root@rocky8 ~]# ifconfig ens160 | sed -n "2s/^.*inet //;s/ netmask.*//p"
192.168.1.11

[root@rocky8 ~]# ifconfig ens160 | sed -rn "2s/(.*inet )([0-9].*)( netmask.*)/\2/p"
192.168.1.11

范例:取基名和目录名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#取基名
[root@rocky8 ~]# basename /etc/sysconfig/network-scripts/
network-scripts

[root@rocky8 ~]# echo "/etc/sysconfig/network-scripts/" | sed -nE 's#(^/.*/)([^/]+)/?#\2#p'
network-scripts


#取目录
[root@rocky8 ~]# dirname /etc/sysconfig/network-scripts/
/etc/sysconfig

[root@rocky8 ~]# echo "/etc/sysconfig/network-scripts/" | sed -nE 's#(^/.*/)([^/]+)/?#\1#p'
/etc/sysconfig/

范例: 取文件的前缀和后缀

1
2
3
4
5
[root@rocky8 ~]# echo a.txt | sed -En 's/(.*)\.([^.]+)$/\1   \2/p'
a txt

[root@rocky8 ~]# echo a.tar.gz.txt | sed -En 's/(.*)\.([^.]+)$/\1 \2/p'
a.tar.gz txt

范例:将非#开头的行加#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@rocky8 ~]# sed -rn 's/^[^#]/#&/p' /etc/fstab 
#/dev/mapper/rl-root / xfs defaults 0 0
#UUID=a8e3c539-a7e7-4828-9a4d-08a9ec290d07 /boot xfs defaults 0 0
#/dev/mapper/rl-home /home xfs defaults 0 0
#/dev/mapper/rl-swap none swap defaults 0 0

[root@rocky8 ~]# sed -rn 's/^([^#])(.*)/#\1\2/p' /etc/fstab
#/dev/mapper/rl-root / xfs defaults 0 0
#UUID=a8e3c539-a7e7-4828-9a4d-08a9ec290d07 /boot xfs defaults 0 0
#/dev/mapper/rl-home /home xfs defaults 0 0
#/dev/mapper/rl-swap none swap defaults 0 0

[root@rocky8 ~]# sed -rn '/^#/!s@^@#@p' /etc/fstab
#/dev/mapper/rl-root / xfs defaults 0 0
#UUID=a8e3c539-a7e7-4828-9a4d-08a9ec290d07 /boot xfs defaults 0 0
#/dev/mapper/rl-home /home xfs defaults 0 0
#/dev/mapper/rl-swap none swap defaults 0 0

范例:将#开头的行删除#

1
2
3
[root@rocky8 ~]# sed -ri.bak '/^#/s/^#//' /etc/fstab

[root@rocky8 ~]# sed -E -i.bak 's/^#(.*)/\1/' /etc/fstab

范例:取分区利用率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@rocky8 ~]# df | sed -nr '/^\/dev/  s# .* ([0-9]+)%.*# \1# p'
/dev/mapper/rl-root 3
/dev/mapper/rl-home 1
/dev/sda1 21

[root@rocky8 ~]# df | sed -rn '/^\/dev/ s#([^[:space:]]+[[:space:]]+){4}(.*)%.*#\2#p'
3
1
21

[root@rocky8 ~]# df | sed -rn '/^\/dev/ s#(\S+\s+){4}(.*)%.*#\2#p'
3
1
21

范例:修改网卡名称

1
2
3
4
5
6
7
8
9
10
[root@rocky8 ~]# sed -Ei.bak 's/^(GRUB_CMDLINE_LINUX=.*)"$/\1 net.ifname=0"/' /etc/default/grub 

[root@rocky8 ~]# cd /etc/default/
[root@rocky8 default]# ls
grub grub.bak useradd useradd-

[root@rocky8 ~]# sed -Ei '/^GRUB_CMDLINE_LINUX/s#"$# net.ifnames=0"#' /etc/default/grub

#修改完成后重启生效
[root@rocky8 ~]# grub2-mkconfig -o /etc/grub2.cfg; reboot; init 6

范例:修改selinux配置

1
2
3
4
5
6
7
8
9
[root@rocky8 ~]# cp /etc/selinux/config .

[root@rocky8 ~]# sed -i.bak '/SELINUX=enforcing/c SELINUX=disabled' config

[root@rocky8 ~]# sed -i.bak '/^SELINUX=enforcing/c SELINUX=disabled/' config

[root@rocky8 ~]# sed -Ei.bak 's/^SELINUX=.*/SELINUX=disabled/' config

[root@rocky8 ~]# sed -Ei.bak 's/^(SELINUX=)(.*)/\1disabled/' config

范例: 显示前4行

1
2
3
4
5
6
[root@rocky8 ~]# seq 10 > test.txt
[root@rocky8 ~]# sed 4q test.txt
1
2
3
4

sed 高级用法

sed 中除了模式空间,还另外还支持保持空间(Hold Space),利用此空间,可以将模式空间中的数据, 临时保存至保持空间,从而后续接着处理,实现更为强大的功能。

11

常见的高级命令

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

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sed -n 'n;p' FILE
seq 10 | sed 'N;s/\n//'
sed '1!G;h;$!d' FILE
seq 10 | sed -n '/3/{g;1!p;};h' #前一行
seq 10 | sed -nr '/3/{n;p}' #后一行
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

范例: 打印偶数行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@rocky8 ~]# seq 10 | sed -n 'n;p'
2
4
6
8
10

[root@rocky8 ~]# seq 10 | sed -n '2~2p'
2
4
6
8
10

[root@rocky8 ~]# seq 10 | sed '1~2d'
2
4
6
8
10

[root@rocky8 ~]# seq 10 | sed -n '1~2!p'
2
4
6
8
10

练习:

  1. 删除centos7系统/etc/grub2.cfg文件中所有以空白开头的行行首的空白字符
  2. 删除/etc/fstab文件中所有以#开头,后面至少跟一个空白字符的行的行首的#和空白字符
  3. 在centos6系统/root/install.log每一行行首增加#号
  4. 在/etc/fstab文件中不以#开头的行的行首增加#号
  5. 处理/etc/fstab路径,使用sed命令取出其目录名和基名
  6. 利用sed 取出ifconfig命令中本机的IPv4地址
  7. 统计centos安装光盘中Package目录下的所有rpm文件的以.分隔倒数第二个字段的重复次数
  8. 统计/etc/init.d/functions文件中每个单词的出现次数,并排序(用grep和sed两种方法分别实现)
  9. 将文本文件的n和n+1行合并为一行,n为奇数行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1. 
sed -i 's/^[[:space:]]*//;T' /etc/grub2.cfg

2.
sed -i "s/^#[[:space:]]+//" /etc/fstab

3.
sed -i "s/^/#/" /root/install.log

4.
sed -i "s/^#*/#/" /etc/fstab

5.
echo "/etc/fstab" | sed -nE 's#(^/.*/)([^/]+)/?#\2#p'
fstab

echo "/etc/fstab" | sed -nE 's#(^/.*/)([^/]+)/?#\1#p'
/etc/

6.
ifconfig ens160 | sed -nE 's/.*inet ([0-9.]+).*/\1/p'
192.168.1.11

7.

文本处理三剑客之 awk

12

awk 工作原理和基本用法说明

​ awk:Aho, Weinberger, Kernighan,报告生成器,格式化文本输出,GNU/Linux发布的AWK目前由自由软件基金会(FSF)进行开发和维护,通常也称它为 GNU AWK

AWK有多种版本:

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

目前主流发行版LINUX中使用的都是GAWK

1
2
[root@rocky8 ~]# ll `which awk`
lrwxrwxrwx. 1 root root 4 May 19 2021 /usr/bin/awk -> gawk

GNU AWK 用户手册文档

1
2
https://www.gnu.org/software/gawk/manual/gawk.html
https://man7.org/linux/man-pages/man1/awk.1p.html

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

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

格式:

1
2
3
4
5
6
7
awk [options]       'program' var=value   file…
awk [options] -f programfile var=value file…

#常用选项
-f progfile|--file progfile #从文件中读入program
-F fs|--field-separator fs #指定分隔符,默认是空白符,可以指定多个
-v var=val|--asign var=val #设置变量

program用法

1
2
3
4
5
6
7
8
9
10
program 通常放在单引号中,由三部份组成,分别是BEGIN{}[pattern]{COMMAND}END{},这三部份可以没有或都有


awk '' /etc/issue

awk 'BEGIN{print "begin"}' /etc/issue
awk '{print $0}' /etc/issue
awk 'END{print "end"}' /etc/issue

awk 'BEGIN{print "begin"}{print $0}END{print "end"}' /etc/issue

Program格式:

1
2
3
4
pattern{action statements;..}

pattern #定义条件,只有符合条件的记录,才会执行后面的action
action statements #处理数据的方式,常见如 print,printf等

awk 工作过程

13

  1. 执行BEGIN{action;… }语句块中的语句
  2. 从文件或标准输入(stdin)读取一行,然后执行pattern{ action;… }语句块,它逐行扫描文件,从 第一行到最后一行重复这个过程,直到 文件全部被读取完毕。
  3. 当读至输入流末尾时,执行END{action;…}语句块

​ BEGIN语句块在awk开始从输入流中读取行之前被执行,这是一个可选的语句块,比如变量初始化、打 印输出表格的表头等语句通常可以写 在BEGIN语句块中

​ END语句块在awk从输入流中读取完所有的行之后即被执行,比如打印所有行的分析结果这类信息汇总 都是在END语句块中完成,它也是一 个可选语句块

​ pattern语句块中的通用命令是最重要的部分,也是可选的。如果没有提供pattern语句块,则默认执行{ print },即打印每一个读取到的行,awk读取的每一行都会执行该语句块

分割符、域和记录

  • 文件的每一行称为记录 record
  • 记录可以由指定的分隔符分隔成多个字段(列 column,域 field),由$1,$2…$N 来标识每一个字段,$0 表示一整行

awk 变量

awk中的变量分为内置和自定义变量两种

变量的作用在于可以一次定义多次调用

内置变量

常用内置变量

变量名 含义
FILENAME 当前文件名
FS 字段分隔符,默认为空白字符,功能相当于-F
RS 换行符,用于分割指定文件的行,默认是换行符
OFS 输出字段分隔符,默认为空白字符
ORS 输出换行符,输出时用指定符号代替换行符
OFMT 数字的输出格式,默认值是%.6g
NF 一条记录的字段数量
NR 已经读出的记录数,就是行号,从1开始
FNR 各文件分别记录行号
ARGC 命令行参数的个数
ARGV 数组,保存的是命令行所给定的各参数,每一个参数:ARGV[0], ….., ARGV[n]

范例:FILENAME

1
2
3
4
5
6
7
8
9
10
#begin中拿不到FILENAME
[root@rocky8 ~]# awk 'BEGIN{print FILENAME}' /etc/issue


[root@rocky8 ~]# awk 'BEGIN{print FILENAME}{print "test"}END{print FILENAME}' /etc/issue

test
test
test
/etc/issue

范例:FS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@rocky8 ~]# awk -v FS=":" 'BEGIN{print FS}{print $1,$1}' /etc/passwd:
root root
bin bin
daemon daemon


[root@rocky8 ~]# awk -v FS=":" 'BEGIN{print FS}{print $1FS$1}' /etc/passwd
:
root:root
bin:bin
daemon:daemon


#使用-F选项指定
[root@rocky8 ~]# awk -F : 'BEGIN{print FS}{print $1FS$1}' /etc/passwd
:
root:root
bin:bin
daemon:daemon


#从shell变量中获取
[root@rocky8 ~]# str=":";awk -v FS=$str 'BEGIN{print FS}{print $1FS$1}' /etc/passwd
:
root:root
bin:bin
daemon:daemon


#-F和FS变量功能一样,同时使用后面会覆盖前面的
[root@rocky8 ~]# awk -v FS=":" -F ";" 'BEGIN{print FS}' /etc/passwd
;

[root@rocky8 ~]# awk -F ";" -v FS=":" 'BEGIN{print FS}' /etc/passwd
:

范例:RS

1
2
3
4
5
6
7
8
9
10
[root@rocky8 ~]# cat test.txt -A
a b c;1 2 3;x y z$

[root@rocky8 ~]# awk '{print $0}' test.txt
a b c;1 2 3;x y z

[root@rocky8 ~]# awk -v RS=";" '{print $0}' test.txt
a b c
1 2 3
x y z

范例:OFS

1
2
3
4
5
6
7
8
9
10
11
[root@rocky8 ~]# awk -v FS=":" '{print $1,$3}' /etc/passwd
root 0
bin 1
daemon 2
adm 3

[root@rocky8 ~]# awk -v FS=":" -v OFS="---" '{print $1,$3}' /etc/passwd
root---0
bin---1
daemon---2
adm---3

范例:ORS

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@rocky8 ~]# cat -A test.txt 
123$
456$
789$

[root@rocky8 ~]# awk '{print $0}' test.txt
123
456
789


[root@rocky8 ~]# awk -v ORS="---" '{print $0}' test.txt
123---456---789---[root@rocky8 ~]#

范例:OFMT

1
2
3
4
5
6
[root@rocky8 ~]# awk 'BEGIN{PI=3.1415926;print PI;OFMT="%.1g";print PI;OFMT="%.2g";print PI;OFMT="%.8g";print PI;OFMT="%.8f";print PI}'
3.14159
3
3.1
3.1415926
3.14159260

范例:NF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@rocky8 ~]# awk -v FS=":" '{print NF}' /etc/passwd
7
7
7

[root@rocky8 ~]# awk -v FS=":" '{print $NF}' /etc/passwd
/bin/bash
/sbin/nologin
/sbin/nologin

[root@rocky8 ~]# awk -v FS=":" '{print $(NF-1)}' /etc/passwd
/root
/bin
/sbin

范例:NR

1
2
3
4
[root@rocky8 ~]# awk '{print NR,$0}' /etc/issue
1 \S
2 Kernel \r on an \m
3

范例:FNR

1
2
3
4
5
[root@rocky8 ~]# awk '{print NR,FNR,$0}' /etc/issue /etc/redhat-release 
1 1 \S
2 2 Kernel \r on an \m
3 3
4 1 Rocky Linux release 8.5 (Green Obsidian)

范例:ARGC

1
2
3
4
5
6
[root@rocky8 ~]# awk 'BEGIN{print ARGC}' 
1
[root@rocky8 ~]# awk 'BEGIN{print ARGC}' /etc/issue
2
[root@rocky8 ~]# awk 'BEGIN{print ARGC}' /etc/issue /etc/os-release
3

范例:ARGV

1
2
[root@rocky8 ~]# awk 'BEGIN{print ARGC,"---",ARGV[0],"---",ARGV[1],"---",ARGV[2]}' /etc/issue /etc/os-release 
3 --- awk --- /etc/issue --- /etc/os-release

自定义变量

AWK中的自定义变量命名要区分大小写

用 -v 选项定义自定义变量

1
2
3
4
5
6
[root@rocky8 ~]# awk -v var1=abc 'BEGIN{print var1}'
abc

[root@rocky8 ~]# awk -v var1=abc -v VAR1=123 'BEGIN{print var1;print VAR1}'
abc
123

在program中定义

1
2
3
4
5
6
7
8
9
10
[root@rocky8 ~]# awk -v var1=abc 'BEGIN{print var1;var1=def;print var1}'
abc


#定义的时候要加双引号
[root@rocky8 ~]# awk -v var1=abc 'BEGIN{print var1;var1="def";print var1}{print var1}' /etc/issue
abc
def
def
def

动作 print

格式:

1
print item1,item2, ...

说明:

  • 逗号分隔符
  • 输出item可以字符串,也可是数值;当前记录的字段、变量或awk的表达式
  • 如省略item,相当于print $0
  • 固定字符符需要用“ ” 引起来,而变量和数字不需要

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[root@rocky8 ~]# awk 'BEGIN{print "hello world"}'
hello world

[root@rocky8 ~]# seq 3 | awk '{print "hello world"}'
hello world
hello world
hello world

[root@rocky8 ~]# seq 3 | awk '{print $0}'
1
2
3

[root@rocky8 ~]# awk 'BEGIN{print 2*3}'
6

[root@rocky8 ~]# awk '{print }' /etc/issue
\S
Kernel \r on an \m


[root@rocky8 ~]# awk -v var1=123 -F : '{print var1,$1,$3}' /etc/passwd
123 root 0
123 bin 1
123 daemon 2
123 adm 3

[root@rocky8 ~]# awk -v var1=123 -F : '{print var1,$1"\t"$3}' /etc/passwd
123 root 0
123 bin 1
123 daemon 2
123 adm 3

动作 printf

printf 可以实现格式化输出

格式:

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

说明:

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

格式符:与item一一对应

1
2
3
4
5
6
7
8
%s                          #显示字符串
%d|%i #显示十进制整数
%f #显示为浮点数
%e|%E #显示科学计数法数值
%c #显示字符的ASCII码
%g|%G #以科学计数法或浮点形式显示数值
%u #无符号整数
%% #显示%自身

修饰符

1
2
3
M[.N]                       #M表示显示的宽度;N表示小数点后精度,如 %3.1f
- #左对齐(默认右对齐) 如 %-15s
+ #显示数值的正负符号 如 %+d

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[root@rocky8 ~]# awk -F : '{printf "%s",$1}' /etc/passwd
rootbindaemonadmlpsyncshutdownhaltmailoperator

[root@rocky8 ~]# awk -F : '{printf "%s\n",$1}' /etc/passwd
root
bin
daemon
adm


[root@rocky8 ~]# awk -F : '{printf "%20s\n",$1}' /etc/passwd
root
bin
daemon


[root@rocky8 ~]# awk -F : '{printf "%-20s %10d\n",$1,$3}' /etc/passwd
root 0
bin 1
daemon 2
adm 3

[root@rocky8 ~]# awk -F : '{printf "Username: %25sUID: %d\n",$1,$3}' /etc/passwd
Username: rootUID: 0
Username: binUID: 1
Username: daemonUID: 2
Username: admUID: 3


[root@rocky8 ~]# awk -F : '{printf "Username: %-25sUID: %d\n",$1,$3}' /etc/passwd
Username: root UID: 0
Username: bin UID: 1
Username: daemon UID: 2
Username: adm UID: 3

范例:

1
2
3
4
5
6
7
8
9
10
11
12
[root@rocky8 ~]# awk -F: 'BEGIN{print "---------------------------\n|username            |uid  |\n---------------------------"}{printf "|%-20s|%5s|\n---------------------------\n",$1,$3}' /etc/passwd
---------------------------
|username |uid |
---------------------------
|root | 0|
---------------------------
|bin | 1|
---------------------------
|daemon | 2|
---------------------------
|adm | 3|
---------------------------

操作符

赋值操作

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

比较操作

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

算术运算符

1
2
3
x+y, x-y, x*y, x/y, x^y, x%y  
-x #转换为负数
+x #将字符串转换为数值

范例:

1
2
3
4
5
6
7
8
[root@rocky8 ~]# awk 'BEGIN{i=0;print i++,i}'
0 1
[root@rocky8 ~]# awk 'BEGIN{i=0;print ++i,i}'
1 1
[root@rocky8 ~]# awk 'BEGIN{i=0;print ++i,++i}'
1 2
[root@rocky8 ~]# awk 'BEGIN{i=0;print ++i,i++}'
1 1

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#默认print,先匹配,再++
[root@rocky8 ~]# seq 3 | awk 'n++'
2
3

[root@rocky8 ~]# awk -v n=0 '!n++' /etc/passwd
root:x:0:0:root:/root:/bin/bash

# !N++当N为0时返回真,然后N递增。
[root@rocky8 ~]# awk -v n=0 '!n++{print n}' /etc/passwd
1

[root@rocky8 ~]# awk -v n=1 '!n++{print n}' /etc/passwd

[root@rocky8 ~]# awk -v n=0 '!++n{print n}' /etc/passwd

[root@rocky8 ~]# awk -v n=0 '!++n' /etc/passwd

[root@rocky8 ~]# awk -v n=-1 '!++n' /etc/passwd
root:x:0:0:root:/root:/bin/bash

范例:

1
2
3
4
5
6
7
8
9
[root@rocky8 ~]# awk 'NR==2' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin


[root@rocky8 ~]# awk -F: '$3>=1000' /etc/passwd
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
jose:x:1000:1000::/home/jose:/bin/bash
jeck:x:1002:1002::/home/jeck:/bin/sh
jerry:x:1001:1001:tom to jerry:/home/tom:/bin/bash

范例:取奇,偶数行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@rocky8 ~]# seq 6 | awk 'NR%2==0'
2
4
6

[root@rocky8 ~]# seq 6 | awk 'NR%2==1'
1
3
5

[root@rocky8 ~]# seq 6 | awk 'NR%2!=0'
1
3
5

[root@rocky8 ~]# seq 6 | awk 'NR%2!=1'
2
4
6

模式匹配操作

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

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#包含root的行
[root@rocky8 ~]# awk -F: '$0 ~ /root/{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

[root@rocky8 ~]# awk '/root/' /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin


#以root开头的行
[root@rocky8 ~]# awk -F: '$0 ~ "^root"{print $0}' /etc/passwd
root:x:0:0:root:/root:/bin/bash

[root@rocky8 ~]# awk -F: '/^root/' /etc/passwd
root:x:0:0:root:/root:/bin/bash



#不包含root的行
[root@rocky8 ~]# awk '$0 !~ /root/' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin


#UID为0的行
[root@rocky8 ~]# awk -F: '$3==0' /etc/passwd
root:x:0:0:root:/root:/bin/bash

范例:

1
2
3
4
5
[root@rocky8 ~]# df | awk -F"[[:space:]]+|%" '$0 ~ /^\/dev\/sd/{print $5}'
21

[root@rocky8 ~]# ifconfig ens160 | awk 'NR==2{print $2}'
192.168.1.11

逻辑操作符

1
2
3
&&                           #与,并且关系
|| #或,或者关系
! #非,取反

范例:! 取反

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@rocky86 ~]# awk 'BEGIN{print i}'

[root@rocky86 ~]# awk 'BEGIN{print !i}'
1

[root@rocky86 ~]# awk -v i=10 'BEGIN{print !i}'
0

[root@rocky86 ~]# awk -v i=0 'BEGIN{print !i}'
1

[root@rocky86 ~]# awk -v i=-10 'BEGIN{print !i}'
0

[root@rocky86 ~]# awk -v i=abc 'BEGIN{print !i}'
0

[root@rocky86 ~]# awk -v i='' 'BEGIN{print !i}'
1

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@rocky86 ~]# awk -F: '$3>=0 && $3<5 {print $1,$3}' /etc/passwd
root 0
bin 1
daemon 2
adm 3
lp 4

[root@rocky86 ~]# awk -F: '$3<1 || $3>1010 {print $1,$3}' /etc/passwd
root 0
nobody 65534
jerry 1011
tom 1012
tom-cat 1013


[root@rocky86 ~]# awk -F: '!($3!=0) {print $1,$3}' /etc/passwd
root 0


[root@rocky86 ~]# awk -F: '!($3>=5) {print $1,$3}' /etc/passwd
root 0
bin 1
daemon 2
adm 3
lp 4

三目运算(条件表达式)

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

范例:

1
2
3
4
5
6
7
8
9
[root@rocky8 ~]# awk -F: '{$3>=1000?utype="Common":utype="Sys";printf "%-20s:%12s\n",$1,utype}' /etc/passwd
root : Sys
bin : Sys
daemon : Sys
adm : Sys


[root@rocky8 ~]# df|awk -F"[ %]+" '/^\/dev\/sd/{$(NF-1)>10?disk="full":disk="OK";print $(NF-1),disk}'
21 full

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@rocky86 ~]# cat scores.txt
wang 100
li 90
zhang 50
zhao 80
han 70


[root@rocky86 ~]# awk '$2>=60?type="pass":type="nopass"{print $1,type}' scores.txt
wang pass
li pass
zhang nopass
zhao pass
han pass

模式PATTERN

PATTERN:根据pattern条件,过滤匹配的行,再做处理,如果没有指定,则匹配每一行

范例:

1
2
3
4
5
6
7
[root@rocky86 ~]# awk '{print $0}' /etc/issue
Welcom to magedu! \d
\S
Kernel \r on an \m

[root@rocky86 ~]# awk 'NR==1{print $0}' /etc/issue
Welcom to magedu! \d

正则匹配

范例:用正则匹配,正则表达式需要用/ / 来锚定开始结束

1
2
3
4
5
6
7
8
9
10
[root@rocky8 ~]#  awk '/^UUID/{print $1}' /etc/fstab
UUID=a8e3c539-a7e7-4828-9a4d-08a9ec290d07


#非空行,非注释行
[root@rocky8 ~]# awk '!/^$|^#/{print $1}' /etc/fstab
/dev/mapper/rl-root
UUID=a8e3c539-a7e7-4828-9a4d-08a9ec290d07
/dev/mapper/rl-home
/dev/mapper/rl-swap

关系表达式

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

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

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

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
[root@centos8 ~]#seq 3 | awk '1' 
1
2
3

[root@centos8 ~]#seq 3 | awk '!1'
[root@centos8 ~]#seq 3 | awk '0'
[root@rocky86 ~]# seq 3 | awk '!0'
1
2
3


#false 这里表示的是一个未定义的变量,不是表示bool值
[root@rocky86 ~]# seq 3 | awk 'false'
[root@rocky86 ~]# seq 3 | awk -v false=123 'false'
1
2
3


#false 这里表示的是字符串
[root@rocky86 ~]# seq 3 | awk '"false"'
1
2
3

[root@rocky86 ~]# seq 3 | awk '""'
[root@rocky86 ~]# seq 3 | awk '" "'
1
2
3

[root@rocky86 ~]# seq 3 | awk '"0"'
1
2
3

[root@rocky86 ~]# seq 3 | awk '0'


#这里true 是变量名,一个不存在的变量
[root@rocky86 ~]# seq 3 | awk 'true'
[root@rocky86 ~]# seq 3 | awk -v true=123 'true'
1
2
3

[root@rocky86 ~]# seq 3 | awk '123'
1
2
3

[root@rocky86 ~]# seq 3 | awk 'mage'
[root@rocky86 ~]# seq 3 | awk -v mage="mage" 'mage'
1
2
3

[root@rocky86 ~]# seq 3 | awk -v mage="" 'mage'
[root@rocky86 ~]# seq 3 | awk -v mage=" " 'mage'
1
2
3

[root@rocky86 ~]# seq 3 | awk -v mage=0 'mage'

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
[root@rocky86 ~]# seq 3 | awk 'i'

#赋值
[root@rocky86 ~]# seq 3 | awk 'i=0'

#比较
[root@rocky86 ~]# seq 3 | awk 'i==0'
1
2
3

#赋值
[root@rocky86 ~]# seq 3 | awk 'i=1'
1
2
3

#比较
[root@rocky86 ~]# seq 3 | awk 'i==1'

#比较
[root@rocky86 ~]# seq 3 | awk 'i==i'
1
2
3
#比较
[root@rocky86 ~]# seq 3 | awk 'i!=i'

#赋值 i=false
[root@rocky86 ~]# seq 3 | awk 'i=i'

#赋值 tru false true
[root@rocky86 ~]# seq 3 | awk 'i=!i'
1
3

[root@rocky86 ~]# seq 4 | awk '{i=!i;print i}'
1
0
1
0

[root@rocky86 ~]# seq 8 | awk '!(i=!i)'
2
4
6
8

[root@rocky86 ~]# seq 8 | awk -v i=1 'i=!i'
2
4
6
8

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@rocky86 ~]# awk 'i=1;j=1{print i,j}' /etc/issue
Welcom to magedu! \d
1 1
\S
1 1
Kernel \r on an \m
1 1
1 1


[root@rocky86 ~]# awk 'i=1,j=1{print i,j}' /etc/issue
1 1
1 1
1 1
1 1

范例:

1
2
3
4
5
6
7
8
9
10
11
[root@rocky86 ~]# awk  -F: '$3>=1000{print $1,$3}' /etc/passwd
nobody 65534
jose 1010
jerry 1011
mage 1000
tom 1012

[root@rocky86 ~]# awk -F: '$NF=="/bin/bash"{print $1,$NF}' /etc/passwd
root /bin/bash
jose /bin/bash
jerry /bin/bash

行范围

不支持直接用行号,但可以使用变量NR间接指定行号

也可以用正则锚定开始结束行

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@rocky86 ~]# seq 10 | awk 'NR>=3 && NR<6'
3
4
5

[root@rocky86 ~]# awk 'NR>=3 && NR<6{print NR,$0}' /etc/passwd
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

#sed 写法
[root@rocky86 ~]# sed -n '3,5p' /etc/passwd
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

#正则锚定
[root@rocky86 ~]# awk '/^bin/,/^adm/' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin

#sed写法
[root@rocky86 ~]# sed -n '/^bin/,/^adm/p' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin

BEGIN/END模式

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

END{}:仅在文本处理完成之后执行一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#只有begin 时,可以没有输入内容
[root@rocky86 ~]# awk 'BEGIN{print "begin"}'
begin

#需要后续输入
[root@rocky86 ~]# awk 'BEGIN{print "begin"}{}'
begin

[root@rocky86 ~]# awk 'BEGIN{print "begin"}{print $0}END{print "end"}' /etc/issue
begin
Welcom to magedu! \d
\S
Kernel \r on an \m
end

范例:

1
2
3
4
5
6
7
8
9
10
[root@rocky8 ~]# awk -F:   'BEGIN{printf "--------------------------------\n%-20s|%10s|\n--------------------------------\n","username","uid"}{printf "%-20s|%10d|\n--------------------------------\n",$1,$3}' /etc/passwd
--------------------------------
username | uid|
--------------------------------
root | 0|
--------------------------------
bin | 1|
--------------------------------
daemon | 2|
--------------------------------

条件判断 if-else

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

语法:

1
2
3
if(condition){statement;…}[else statement]
if(condition1){statement1}else if(condition2){statement2}else if(condition3)
{statement3}...... else {statementN}

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@rocky86 ~]# awk -F: '{if($3>=1000)print $1,$3}' /etc/passwd
nobody 65534
jose 1010
jerry 1011

[root@rocky86 ~]# awk -F: '{if($3<=100){print "<=100",$3}else if ($3<=1000) {print "<=1000",$3} else{print ">=1000",$3}}' /etc/passwd
<=100 0
<=100 1
<=100 2
<=100 3

[root@rocky86 ~]# awk -F: '{if($NF=="/bin/bash") print $1}' /etc/passwd
root
jose
jerry

[root@rocky86 ~]# awk '{if(NF>5) print $0}' /etc/fstab
......

[root@rocky86 ~]# awk -F: '{if($3>=1000) {printf "Common user: %s\n",$1} else {printf "root or Sysuser: %s\n",$1}}' /etc/passwd
root or Sysuser: root
root or Sysuser: bin
root or Sysuser: daemon

范例:

1
2
3
4
5
6
7
8
9
10
11
[root@rocky86 ~]# df -h|awk -F% '/^\/dev\/sd/{print $1}'| awk '$NF>=10{print $1,$5}'
/dev/sda1 26

[root@rocky86 ~]# df | awk -F"[[:space:]]+|%" '/^\/dev\/sd/{if($5>10)print $1,$5}'
/dev/sda1 26

[root@rocky86 ~]# df | awk -F' +|%' '/^\/dev\/sd/{if($5>=10)print $1,$5}'
/dev/sda1 26

[root@rocky86 ~]# df | awk -F"[ %]+" '/^\/dev\/sd/{if($(NF-1)>10){print $(NF-1)" full"}else {print $(NF-1)" OK"}}'
26 full

范例:

1
2
[root@rocky86 ~]# awk 'BEGIN{ test=100;if(test>90){print "very good"}else if(test>60){ print "good"}else{print "no pass"}}'
very good

条件判断 switch-case

awk 中的switch分支语句功能较弱,只能进行等值比较或正则匹配。

各分支结尾需使用break来终止

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch(expression) {
case VALUE1 or /REGEXP1/:
statement1;
break;

case VALUE2 or /REGEXP2/:
statement2;
break;
......;

default:
statementn;
break;
}

分支穿透:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
switch(expression) {
case VALUE1 or /REGEXP1/:
case VALUE2 or /REGEXP2/:
case VALUE3 or /REGEXP3/:
statement1;
break;

case VALUE4 or /REGEXP4/:
statement2;
break;
......;

default:
statementn;
break;
}

范例:

1
2
3
4
5
6
7
[root@rocky86 ~]# awk -F: '{switch($3){case 0:print "root",$3;break; case /^[1-9][0-9]{0,2}$|1000/:print "sys user",$3;break;default:print "common user",$3;}}' /etc/passwd
root 0
sys user 1
sys user 2
sys user 3
sys user 4
sys user 5

循环 while

条件“真”,进入循环;条件“假”,退出循环

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

语法:

1
while (condition) {statement;…}

范例:

1
2
3
4
5
[root@rocky86 ~]# awk -v i=1 -v sum=0 'BEGIN{while(i<=100){sum+=i;i++};print sum}'
5050

[root@rocky86 ~]# awk 'BEGIN{ total=0;i=1;while(i<=100){total+=i;i++};print total}'
5050

循环 do-while

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

语法:

1
do {statement;…}while(condition)

范例:

1
2
3
4
5
6
[root@rocky86 ~]# awk 'BEGIN{ total=0;i=1;do{ total+=i;i++;}while(i<=100);print total}'
5050

#至少执行一次循环
[root@rocky86 ~]# awk 'BEGIN{ total=0;i=101;do{ print total }while(i<=100);}'
0

循环 for

语法:

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

特殊用法:能够遍历数组中的元素

1
for(var in array) {for-body}

范例:

1
2
3
4
5
6
[root@rocky86 ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){sum+=i};print sum}'
5050

#shell实现
[root@rocky86 ~]# for((i=1,sum=0;i<=100;i++));do let sum+=i;done;echo $sum
5050

范例:遍历数组

1
2
3
[root@rocky86 ~]# awk 'BEGIN{ arr[0]=123;arr[1]=456;for(i in arr){ print i"==>"arr[i] };}'
0==>123
1==>456

面试题:文件abc.txt只有一行数字,计算其总和

1
2
3
4
5
6
7
8
9
10
11
[root@rocky86 ~]# cat abc.txt
1 2 3 4 5

[root@rocky86 ~]# cat abc.txt |awk '{for(i=1;i<=NF;i++){sum+=$i};print sum}'
15

[root@rocky86 ~]# cat abc.txt|tr ' ' + |bc
15

[root@rocky86 ~]# sum=0;for i in `cat abc.txt`;do let sum+=i;done;echo $sum
15

性能比较

1
2
3
4
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)

范例: 取出字符串中的数字

1
2
3
4
5
6
7
8
9
10
11
12
[root@rocky86 ~]# echo 'dsFUs34tg*fs5a%8ar%$#@' |awk -F "" '
> {
> for(i=1;i<=NF;i++)
> {
> if ($i ~ /[0-9]/)
> {
> str=(str $i) #连字符,字符串拼接
> }
> }
> print str
> }'
3458

continue 和 break

continue 中断本次循环

break 中断整个循环

格式:

1
2
continue [n]
break [n]

范例:

1
2
3
4
5
6
7
8
9
10
11
[root@rocky86 ~]# awk 'BEGIN{for(i=1;i<=100;i++){if(i==50)continue;sum+=i};print sum}'
5000

[root@rocky86 ~]# awk 'BEGIN{for(i=1;i<=100;i++){if(i==50)break;sum+=i};print sum}'
1225

[root@rocky86 ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i%2==0)continue;sum+=i}print sum}'
2500

[root@rocky86 ~]# awk 'BEGIN{sum=0;for(i=1;i<=100;i++){if(i==50)break;sum+=i}print sum}'
1225

next

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

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@rocky86 ~]# awk -F: '{if($3%2!=0){next;} print $1,$3}' /etc/passwd
root 0
daemon 2
lp 4
shutdown 6
mail 8


[root@rocky86 ~]# seq 10 |awk '{if($0%2!=0){next};{print $0}}'
2
4
6
8
10

数组

awk的数组为关联数组

格式

1
array_name[index-expression]

index-expression

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

范例:

1
2
[root@rocky86 ~]# awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";print weekdays["mon"]}'
Monday

范例:判断数组索引是否存在

1
2
3
4
5
6
7
8
[root@rocky86 ~]# awk 'BEGIN{array["i"]="x"; array["j"]="y" ; print "i" in array,"y" in array }'
1 0

[root@rocky86 ~]# awk 'BEGIN{array["i"]="x"; array["j"]="y" ;if ("i" in array ) {print "存在"}else{print "不存在"}}'
存在

[root@rocky86 ~]# awk 'BEGIN{array["i"]="x"; array["j"]="y" ;if ("abc" in array ) {print "存在"}else{print "不存在"}}'
不存在

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

1
for(var in array) {for-body}

范例:遍历数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@rocky86 ~]# awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for(i in weekdays) {print i,weekdays[i]}}'
tue Tuesday
mon Monday

[root@rocky86 ~]# awk 'BEGIN{students[1]="user1";students[2]="user2";students[3]="user3";for(x in students){print x":"students[x]}}'
1:user1
2:user2
3:user3

[root@rocky8 ~]# awk 'BEGIN {
> a["x"] = "welcome"
> a["y"] = "to"
> a["z"] = "Magedu"
> for (i in a) {
> print i,a[i]
> }
> }'
x welcome
y to
z Magedu


[root@rocky86 ~]# awk -F: '{user[$1]=$3}END{for(i in user){print "username: "i,"uid: "user[i]}}' /etc/passwd
username: adm uid: 3
username: rpc uid: 32
username: dnsmasq uid: 985

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@rocky86 ~]#awk '{ip[$1]++}END{for(i in ip){print i,ip[i]}}' /var/log/httpd/access_log
172.20.0.200 1482
172.20.21.121 2
172.20.30.91 29
172.16.102.29 864
172.20.0.76 1565
172.20.9.9 15
172.20.1.125 463
172.20.61.11 2
172.20.73.73 198

[root@rocky86 ~]#awk '{ip[$1]++}END{for(i in ip){print ip[i],i}}' access_log |sort -nr| head -3
4870 172.20.116.228
3429 172.20.116.208
2834 172.20.0.222

[root@rocky86 ~]#awk '{ip[$1]++}END{for(i in ip){print i,ip[i]}}' access_log |sort -k2 -nr|head -3
172.20.116.228 4870
172.20.116.208 3429
172.20.0.222 2834

范例:封掉查看访问日志中连接次数超过1000次的IP

1
[root@rocky86 ~]# awk '{ip[$1]++}END{for(i in ip){if(ip[i]>=1000){system("iptables -A INPUT -s "i" -j REJECT")}}}' /var/log/httpd/access_log

范例:多维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@rocky86 ~]# 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]
> }'
11
12
13
21
22
23

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@rocky8 ~]# cat score.txt 
name sex score
alice f 100
bob m 90
ming m 95
hong f 90

[root@rocky8 ~]# awk 'NR!=1{if($2=="m"){m_sum+=$3;m_num++}else{f_sum+=$3;f_num++}}END{print "男生平均成绩="m_sum/m_num,"女生平均成绩="f_sum/f_num}' score.txt
男生平均成绩=92.5 女生平均成绩=95

[root@rocky8 ~]# awk 'NR!=1{score[$2]+=$3;num[$2]++}END{for(i in score){print i,score[i]/num[i]}}' score.txt
m 92.5
f 95

[root@rocky8 ~]# awk 'NR!=1{score[$2]+=$3;num[$2]++}END{for(i in score) {if(i=="m"){print "男生平均成绩=",score[i]/num[i]}else{print "女生平均成绩=",score[i]/num[i]}}}' score.txt
男生平均成绩= 92.5
女生平均成绩= 95

awk 函数

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

官方文档

1
https://www.gnu.org/software/gawk/manual/gawk.html#Functions

常见内置函数

数值处理

1
2
3
rand()                           #返回0和1之间一个随机数
srand() #配合rand() 函数,生成随机数的种子
int() #返回整数

范例:

1
2
3
4
5
6
7
8
9
10
11
12
[root@rocky86 ~]# awk 'BEGIN{srand();print rand()}'
0.790437

[root@rocky86 ~]# awk 'BEGIN{srand();print rand()}'
0.283736

[root@rocky86 ~]# awk 'BEGIN{srand(); for (i=1;i<=5;i++)print int(rand()*100) }'
35
17
35
95
19

字符串处理

1
2
3
4
5
str1=(str2,str3)                     #连字符,字符串拼接
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,…

范例: 统计用户名的长度

1
2
[root@rocky86 ~]# cut -d: -f1 /etc/passwd | awk '{print length()}'
[root@rocky86 ~]# awk -F: '{print length($1)}' /etc/passwd

范例:

1
2
3
4
5
[root@rocky86 ~]# echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'
2008-08:08 08:08:08

[root@rocky86 ~]# echo "2008:08:08 08:08:08" | awk '{sub(/:/,"-",$1);print $0}'
2008-08:08 08:08:08

范例:

1
2
3
4
5
[root@rocky86 ~]# echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$0)'
2008-08-08 08-08-08

[root@rocky86 ~]# echo "2008:08:08 08:08:08" | awk '{gsub(/:/,"-",$0);print $0}'
2008-08-08 08-08-08

范例:

1
2
3
4
[root@rocky86 ~]# netstat -tn | awk '/^tcp/{split($4,ip,":");count[ip[1]]++}END{for(i in count){print i,count[i]}}'
10.0.0.1 1
10.0.0.6 1
10.0.0.7 673

调用shell命令

1
system('cmd') 

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

1
2
3
4
5
6
7
[root@rocky86 ~]# awk 'BEGIN{system("hostname")}'
rocky86.m51.magedu.com

root@rocky86 ~]# awk 'BEGIN{score=100; system("echo your score is " score) }'
your score is 100

[root@rocky86 ~]# 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")}}}'

时间函数

1
2
systime()                   #当前时间到1970年1月1日的秒数
strftime() #指定时间格式

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@rocky86 ~]# awk 'BEGIN{print systime()}'
1609917829

[root@rocky86 ~]# awk 'BEGIN{print systime();system("date +%s")}'
1661687319
1661687319

[root@rocky86 ~]# awk 'BEGIN{print strftime("%Y-%m-%dT%H:%M",systime()-3600)}'
2021-01-06T14:24

[root@rocky86 ~]# awk 'BEGIN{print strftime("%Y-%m-%d %H:%M:%S");system("date \"+%Y-%m-%d %H:%M:%S\"")}'
2022-08-28 19:51:34
2022-08-28 19:51:34

自定义函数

自定义函数格式:

1
2
3
4
function name ( parameter, parameter, ... ) {
statements
return expression
}

范例:

1
2
3
4
5
6
7
[root@rocky86 ~]# awk 'function test(){print "hello test func"}BEGIN{test()}'
hello test func

[root@rocky86 ~]# awk -F: 'function test(uname,uid){print uname" id is "uid}{test($1,$3)}' /etc/passwd
root id is 0
bin id is 1
daemon id is 2
1
2
3
4
5
6
7
8
9
10
[root@rocky86 ~]# cat func.awk
function max(x,y) {
x>y?var=x:var=y
return var
}
BEGIN{print max(a,b)}


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

awk 脚本

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

范例:

1
2
3
4
5
6
7
[root@rocky86 ~]# cat passwd.awk 
{if($3>=1000)print $1,$3}

[root@rocky86 ~]# awk -F: -f passwd.awk /etc/passwd
nobody 65534
wang 1000
mage 1001

范例:

1
2
3
4
5
6
7
8
9
[root@rocky86 ~]# cat test.awk
#!/bin/awk -f
{if($3>=1000)print $1,$3}

[root@rocky86 ~]#chmod +x test.awk
[root@rocky86 ~]# ./test.awk -F: /etc/passwd
nobody 65534
wang 1000
mage 1001

向awk脚本传递参数

格式:

1
awkfile  var=value  var2=value2... Inputfile

注意:

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

范例:

1
2
3
4
5
6
7
8
[root@rocky86 ~]# awk -v x=100 'BEGIN{print x}{print x+100}' /etc/hosts
100
200
200

[root@rocky86 ~]# awk 'BEGIN{print x}{print x+100}' x=200 /etc/hosts
300
300

范例:

1
2
3
4
5
6
7
8
9
10
11
12
[root@rocky86 ~]# cat test2.awk 
#!/bin/awk -f
{if($3 >=min && $3<=max)print $1,$3}

[root@rocky86 ~]# chmod +x test2.awk
[root@rocky86 ~]# ./test2.awk -F: min=100 max=200 /etc/passwd
systemd-resolve 193
rtkit 172
pulse 171
qemu 107
usbmuxd 113
abrt 173

练习

1、文件host_list.log 如下格式,请提取”.magedu.com”前面的主机名部分并写入到回到该文件中

1
2
3
4
5
6
7
1 www.magedu.com
2 blog.magedu.com
3 study.magedu.com
4 linux.magedu.com
5 python.magedu.com
......
999 study.magedu.com

参考答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@rocky86 ~]# awk -F'[ .]' '{print $2}' host_list.log >> host_list.log 
[root@rocky86 ~]# cat host_list.log
1 www.magedu.com
1 www.magedu.com
2 blog.magedu.com
3 study.magedu.com
4 linux.magedu.com
5 python.magedu.com
......
999 study.magedu.com
www
blog
study
linux
python

2、统计/etc/fstab文件中每个文件系统类型出现的次数

参考答案

1
2
3
4
5
6
7
[root@rocky86 ~]# awk -F' +' '/^UUID/{fs[$3]++}END{for(i in fs){print i,fs[i]}}' /etc/fstab
swap 1
ext4 3

[root@rocky86 ~]# awk -F' +' '/^UUID/{print $3}' /etc/fstab |uniq -c
3 ext4
1 swap

3、统计/etc/fstab文件中每个单词出现的次数

参考答案

1
[root@rocky86 ~]# awk -F"[^[:alpha:]]" '{for(i=1;i<=NF;i++)word[$i]++}END{for (a in word)if(a !="") print a,word[a]}'   /etc/fstab

4、提取出字符串Yd$C@M05MB%9&Bdh7dq+YVixp3vpw中的所有数字

参考答案

1
2
3
4
5
6
7
[root@rocky86 ~]# echo 'Yd$C@M05MB%9&Bdh7dq+YVixp3vpw' | awk '{gsub(/[^0-9]/,"");print $0}'
05973

[root@rocky86 ~]# echo 'Yd$C@M05MB%9&Bdh7dq+YVixp3vpw' |awk -F "" '{for(i=1;i<=NF;i++){if ($i ~ /[[:digit:]]/) {str=$i; str1=(str1 str)}};print str1}'

[root@rocky86 ~]# echo 'Yd$C@M05MB%9&Bdh7dq+YVixp3vpw' | awk -F'[^0-9]' '{for(i=1;i<=NF;i++){printf "%s",$i}}'
05973

5、文件random.txt记录共5000个随机的整数,存储的格式100,50,35,89…请取出其中最大和最小的整数

参考答案

1
2
3
4
[root@rocky86 ~]# str="";for((i=1;i<=5000;i++));do if [ $i -ne 5000 ];then str+="$RANDOM,";else str+=$RANDOM;fi;done;echo $str > random.txt


[root@rocky86 ~]# awk -F, '{max=$1;min=$1;for(i=1;i<=NF;i++){if($i>max){max=$i}else{if($i<min){min=$i}}}}END{print "最大值:"max,"最小值:"min}' random.txt

6、解决Dos攻击生产案例:监控当某个IP并发连接数超过100时,即调用防火墙命令封掉对应的IP,监 控频率每隔5分钟。防火墙命令为: iptables -A INPUT -s IP -j REJECT

参考答案

1
[root@rocky86 ~]# ss -nt | awk -F " +|:" 'NR!=1{ip[$(NF-2)]++}END{for(i in ip){if(ip[i]>100){system("iptables -A INPUT -s "i" -j REJECT")}}}'

7、将以下文件内容中FQDN取出并根据其进行计数从高到低排序

1
2
3
4
5
6
7
http://mail.magedu.com/index.html
http://www.magedu.com/test.html
http://study.magedu.com/index.html
http://blog.magedu.com/index.html
http://www.magedu.com/images/logo.jpg
http://blog.magedu.com/20080102.html
http://www.magedu.com/images/magedu.jpg

参考答案:

1
2
3
4
5
[root@centos8 ~]#awk -F"/" '{url[$3]++}END{for(i in url){print url[i],i}}' url.log |sort -nr
3 www.magedu.com
2 blog.magedu.com
1 study.magedu.com
1 mail.magedu.com

8、将以下文本文件awktest.txt中 以inode列为标记,对inode列相同的counts列进行累加,并且统计出 同一inode中,beginnumber列中 的最小值和endnumber列中的最大值

1
2
3
4
5
6
7
8
9
inode|beginnumber|endnumber|counts|
106|3363120000|3363129999|10000|
106|3368560000|3368579999|20000|
310|3337000000|3337000100|101|
310|3342950000|3342959999|10000|
310|3362120960|3362120961|2|
311|3313460102|3313469999|9898|
311|3313470000|3313499999|30000|
311|3362120962|3362120963|2|

输出的结果格式为:

1
2
3
106|3363120000|3368579999|30000|
310|3337000000|3362120961|10103|
311|3313460102|3362120963|39900|

参考答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@centos8 ~]#cat awktest.txt
inode|beginnumber|endnumber|counts|
106|3363120000|3363129999|10000|
106|3368560000|3368579999|20000|
310|3337000000|3337000100|101|
310|3342950000|3342959999|10000|
310|3362120960|3362120961|2|
311|3313460102|3313469999|9898|
311|3313470000|3313499999|30000|
311|3362120962|3362120963|2|

[root@rocky86 ~]# awk -F '|' '!/^inode/{sum[$1]+=$4;if(!begin[$1])begin[$1]=$2;else if(begin[$1]>$2)begin[$1]=$2;if(!end[$1])end[$1]=$3;else if(end[$1]<$3)end[$1]=$3}END{for(i in sum)print i"|"begin[i]"|"end[i]"|"sum[i]}' awktest.txt

[root@rocky86 ~]# awk -F'|' -v OFS='|' '/^[0-9]/{inode[$1]++; if(!bn[$1]){bn[$1]=$2}else if(bn[$1]>$2) {bn[$1]=$2}; if(en[$1]<$3)en[$1]=$3;cnt[$1]+=$(NF-1)} END{for(i in inode)print i,bn[i],en[i],cnt[i]}' awktest.txt