awk是由Alfred Aho 、Peter Weinberger 和 Brian Kernighan这三个人创造的,awk由这个三个人的姓氏的首个字母组成。awk其实可以看成是一门独立的编程语言,他支持条件判断、数组、循环等功能。awk共有两个版本(gawk与nawk版本),在linux中我们最常使用的是gawk,同时awk、grep、sed被称为Linux中的三剑客
[root@CodeSheep ~]# ll /usr/bin/awk
lrwxrwxrwx. 1 root root 4 Feb 15 2019 /usr/bin/awk -> gawk
关于"三剑客"的特长
- grep 更适合单纯的查找和匹配文本
- sed 更适合编辑匹配的文本
- awk 更适合格式化文本,对文本进行较复杂格式处理
awk基础
语法
awk [options] '{pattern + action}' {filenames}
其中的action 我们最常用的就是print以及prinf,对于action来说,每次经过一行,都会当前行执行一边action
比如
awk '{print "1",NR}' /etc/passwd
你会发现有多少行,他就输出了多少个1 每行一个
awk的工作流程
首先awk并不是列操作,而是行操作,同样的他也是一行一行的处理的,其中$0表示当前行,比如在正常情况下我们输出全文是用cat来查看的,那么用awk的操作是这样的,另外awk的接受标准输入和文件
cat /etc/passwd
awk '{print $0}' /etc/passwd
# 如上的内容是一样的
awk中的分隔符
为了处理好每一行中的每一个字段,awk引入了分隔符的概念,分隔符有三种表现的形式,
一种是不输入任何不指定任何,则默认为空格
一种是自定义的分隔符 使用 -F 指定 比如 -F:
另外一种是使用 内置变量指定 -v FS='#'
自定义分隔符
既然默认的分隔符是空格,那么当然还有自定义的分隔符咯。我们使用-F选项来指定我们的分隔符
# 使用#分隔符
[root@CodeSheep ~]# cat demo3.text
123#123#dsdshj#dlsj#
[root@CodeSheep ~]# awk -F# '{print $1}' demo3.text
123
[root@CodeSheep ~]# cat demo3.text
123#123#dsdshj#dlsj#
[root@CodeSheep ~]# awk -v FS='#' '{print $1}' demo3.text
123
其实在awk中不仅仅有输入的分割符,还有输出的分隔符,默认也为空格
[root@CodeSheep ~]# awk -F# '{print $1,$2}' demo3.text
123 123
OFS(output field separator)可以指定输出的分隔符,用法与FS相同
[root@CodeSheep ~]# awk -v FS='#' -v OFS='++++' '{print $1,$2}' demo3.text
123++++123
awk的内建变量
awk支持内建变量和自定义变量
内建变量
- $0 当前行
- $1~$n 第n个字段
- $NF 最后一个字段
- NF 当前行被分割字段总数
- NR 当前行行号
- FNR 各文件分别计数的行号
- FS 输入分隔符
- OFS 输出分隔符
- RS 输入换行符
- ORS 输出换行符
- FILENAME 当前文件名
- ARGC 命令行参数的个数
- ARGV 数组,保存的命令行所给定的各参数
# 给每一行添加行号,输出当前行号,以及当前行的内容
[root@CodeSheep ~]# df | awk '{print NR,$0}'
1 Filesystem 1K-blocks Used Available Use% Mounted on
2 /dev/vda1 41147472 6848400 32395580 18% /
3 devtmpfs 930656 0 930656 0% /dev
4 tmpfs 941116 0 941116 0% /dev/shm
5 tmpfs 941116 452 940664 1% /run
6 tmpfs 941116 0 941116 0% /sys/fs/cgroup
7 tmpfs 188224 0 188224 0% /run/user/0
# 多个文件使用NR时,会根据的文件顺序累加序号
# FNR则会分开标序
# ARGV,ARGC
[root@CodeSheep ~]# awk 'BEGIN{print ARGV[0],ARGV[1],ARGV[2],ARGC}' demo1.txt demo2.text
awk demo1.txt demo2.text 3
# 其中ARGV作为数组,第一个参数是awk
自定义变量名
自定义变量的两种形式,一种是在action外面 使用选项指定变量,比如 -v name="hzj"
另外一种则是在action前的pattern中定义,比如 {name="hzj"; action->print name}
[root@CodeSheep ~]# awk -v name="hzj" 'BEGIN{print name}'
hzj
[root@CodeSheep ~]# awk 'BEGIN{name="hzj";print name}'
hzj
[root@CodeSheep ~]# name=hzj
[root@CodeSheep ~]# awk -v name=$name 'BEGIN{print name}'
hzj
awk 参数
options
-v name="xx" -v外部定义变量
pattern+action
抛开Pattern 我们先来使用{action}
# 输出文本内容
[root@CodeSheep ~]# df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/vda1 41147472 6848500 32395480 18% /
devtmpfs 930656 0 930656 0% /dev
tmpfs 941116 0 941116 0% /dev/shm
tmpfs 941116 452 940664 1% /run
tmpfs 941116 0 941116 0% /sys/fs/cgroup
tmpfs 188224 0 188224 0% /run/user/0
[root@CodeSheep ~]# df | awk '{print $1}'
Filesystem
/dev/vda1
devtmpfs
tmpfs
tmpfs
tmpfs
tmpfs
awk是逐行处理的,也就是说当awk处理一个文本的时候,他会一行一行的处理内容。其中awk默认以 换行符 为标记识别每一行。另外对于每一行的处理中 awk会按照用户指定的 分隔符 去分隔当前行,如果没有指定,则默认使用空格作为分隔符,当出现多个空格的时候,awk会自动将连续的空格理解成为一个分隔符。
其中 我们将被awk处理后的每一行都使用了特定的变量标记
$0表示当前处理的整行
$1表示第一个字段
$2表示第二个字段
$NF表示最后一个字段
NF表示当前行被分割后字段总数
因此 假设一行文本被空格分为8段,则$NF表示$8 NF=8 则$7=$(NF-1)
# 输出多行
[root@CodeSheep ~]# df | awk '{print $1 $2 $3}'
Filesystem1K-blocksUsed
/dev/vda1411474726848536
devtmpfs9306560
tmpfs9411160
tmpfs941116452
tmpfs9411160
tmpfs1882240
[root@CodeSheep ~]# df | awk '{print $1, $2 ,$3}'
Filesystem 1K-blocks Used
/dev/vda1 41147472 6848536
devtmpfs 930656 0
tmpfs 941116 0
tmpfs 941116 452
tmpfs 941116 0
tmpfs 188224 0
# 自己添加字段
[root@CodeSheep ~]# df | awk '{print $1 ,"this print test"}'
Filesystem this print test
/dev/vda1 this print test
devtmpfs this print test
tmpfs this print test
tmpfs this print test
tmpfs this print test
tmpfs this print test
[root@CodeSheep ~]# df | awk '{print "$1="$1 , "testfield:""this print test"}'
$1=Filesystem testfield:this print test
$1=/dev/vda1 testfield:this print test
$1=devtmpfs testfield:this print test
$1=tmpfs testfield:this print test
$1=tmpfs testfield:this print test
$1=tmpfs testfield:this print test
$1=tmpfs testfield:this print test
#看上面的案例我们可以发现,当$1被双引号包裹的时候,他就是一个字符串,不再是变量
awk中的Pattern
其实action的主要操作就是print输出,简单来用就是输出内容,更复杂的就是对输出的内容进行格式化的操作。而Pattern所存在的两种模式,愈加加强了awk的能力。
awk中的逻辑
为了更好的格式化,awk中也带有逻辑参数,其中包括开始BEGIN和结尾END,他们之间用{}分隔,比如
awk -v test="test" 'BEGIN{print "1",NR}{print test}END{print "input end" }' /etc/passwd
你会发现他输出了很多个test,首先begin进入,然后输出1和行号0 紧接着不断的进入行输出test 在最后一行输入时 执行input end
特殊模式下的BEGIN与END
- BEGIN 模式指定了处理文本之前需要执行的操作
- END 模式指定了处理完所有行之后所需要执行的操作
[root@CodeSheep ~]# df | awk 'BEGIN{print "$1","$2"}'
$1 $2
[root@CodeSheep ~]# df | awk 'BEGIN{print "$1","$2"} {print $1,$2}'
$1 $2
Filesystem 1K-blocks
/dev/vda1 41147472
devtmpfs 930656
tmpfs 941116
tmpfs 941116
tmpfs 941116
tmpfs 188224
[root@CodeSheep ~]# df | awk 'BEGIN{print "$1","$2"} {print $1,$2} END{print "end for file"}'
$1 $2
Filesystem 1K-blocks
/dev/vda1 41147472
devtmpfs 930656
tmpfs 941116
tmpfs 941116
tmpfs 941116
tmpfs 188224
end for file
在BEGIN的模式下,首先awk会执行BEGIN中的action 之后才做去执行其他的action,同理,在执行完所有的action之后,awk回去执行END模式下面的action。需要注意的是两个特殊的模式BEGIN以及END都需要大写
于是 awk的格式化能力表露无疑了,提取字段再加上BEGIN与END的两种特殊模式,一张完整的表不就出来了嘛
关系运算符
< 小于 x < y
<= 小于等于 x <= y
== 等于 x==y
!= 不等于 x!=y
> = 大于等于 x>=y
> 大于 x>y
~ 正则匹配 x ~ /正则/
!~ 正则不匹配 x !~ /正则/
awk中的正则模式
既然上面的关系运算符中出现了正则,那我们就来讲一讲正则模式
需要注意的是 当使用{x,y}这种次数匹配的正则表达式时,需要配合--posix选项或者--re-interval选项
# 匹配/etc/passwd 中以 tss开头的行
[root@CodeSheep ~]# cat /etc/passwd | awk '/tss/{print $0}'
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
# 匹配/etc/passwd 中以 /bin/bash结尾的行
[root@CodeSheep ~]# cat /etc/passwd | awk '/\/bin\/bash$/{print $0}'
root:x:0:0:root:/root:/bin/bash
# 匹配/etc/passwd 中以/bin/bash结尾的行 到以 tss开头的行
[root@CodeSheep ~]# cat /etc/passwd | awk '/\/bin\/bash$/,/^tss/{print $0}'
root:x:0:0:root:/root:/bin/bash
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
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
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
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin
ntp:x:38:38::/etc/ntp:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
nscd:x:28:28:NSCD Daemon:/:/sbin/nologin
mysql:x:27:27:MySQL Server:/var/lib/mysql:/bin/false
dockerroot:x:997:994:Docker User:/var/lib/docker:/sbin/nologin
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
# 匹配行号大于2 且小于6的行
[root@CodeSheep ~]# cat /etc/passwd | awk 'NR>2 && NR<=6{print $0}'
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
sync:x:5:0:sync:/sbin:/bin/sync
awk中的语法逻辑
在awk中,同样可以使用if for while等在编程语法中出现的逻辑判断
if(){语句1;语句2;}
# 输出df第一行
[root@CodeSheep ~]# df | awk 'NR==1{print $0}'
Filesystem 1K-blocks Used Available Use% Mounted on
[root@CodeSheep ~]# df | awk '{if(NR==1){print $0}}'
Filesystem 1K-blocks Used Available Use% Mounted on
if(){语句1;语句2;}else{语句3;语句4;}
if(){语句1;语句2;}else if(){语句3;语句4;}else{语句5;语句6;}
for(初始化;表达式;更新){语句}
for(变量 in 数组){语句}
while(表达式){语句}
do{语句}while(条件)
# 循环输出1-10
[root@CodeSheep ~]# awk 'BEGIN{for(i=1;i<=10;i++){print i}}'
1
2
...
[root@CodeSheep ~]# awk 'BEGIN{i=1;while(i<=10){print i;i++}}'
1
2
3
...
# 使用continue和break
[root@CodeSheep ~]# awk 'BEGIN{for(i=1;i<=10;i++){if(i==3){continue};print i}}'
1
2
4
...
[root@CodeSheep ~]# awk 'BEGIN{for(i=1;i<=10;i++){if(i==3){break};print i}}'
1
2
# 使用exit退出当前脚本
[root@CodeSheep ~]# awk 'BEGIN{for(i=1;i<=10;i++){if(i==3){break};print i}}'
1
2
[root@CodeSheep ~]# df | awk 'BEGIN{print "filed"}{exit}{print $1}END{print "end file"}'
filed
end file
# 并不是直接结束,而是跳到了end
# 使用next 直接跳到下一行
df | awk 'BEGIN{print "输出第一行的下一行"}{if(NR==1){next};print $0}'
[root@CodeSheep ~]# df | awk 'BEGIN{print "输出第一行的下一行"}{if(NR==1){next};print $0;{exit}}'
输出第一行的下一行
/dev/vda1 41147472 6851112 32392868 18% /
sed基础
sed [参数][定址commands][inputfile]
参数:
--version 查看版本
--help 查看帮助
-n 关闭默认输出,默认自动打印所有行
-e 多点编辑,允许多个脚本指令被执行
-r 支持正则
-i 修改原文件内容
-f 支持使用脚本文件
命令:
/p 打印变动行
s/ 替换
y/ 替换
/n 替换第几个匹配的内容
/g 匹配所有行
/d 删除当前行
i 从当前行前插入
a 从当前行后插入
c 替换匹配行
使用案例
输出
输出passwd文件的1~3行
sed -n '1,3p' /etc/passwd
输出passwd文件的root~daemon行
sed -n '/^root/,/daemon/p'
输出passwd文件中uid是0或1的行
sed -n '/x:[0:1]:/p' /etc/passwd
删除
删除第一行
sed '1d' /etc/passwd
删除第一行和第三行
sed -e '1d' -e '3d' /etc/passwd
sed '1d;3d' /etc/passwd
保留第一行 删除其他行
sed '1!d'
替换
匹配第一个并修改
sed 's/root/ROOT' /etc/passwd
匹配第二个并修改
sed 's/root/ROOT' /etc/passwd
匹配所有并修改
sed 's/root/ROOT/g' /etc/passwd
匹配修改成功后,打印变动行
sed -n 's/root/ROOT/pg' /etc/passwd
修改后另存为其他文件
sed -n 's/root/ROOT/pg;w passwd.md' /etc/passwd
sed -n 's/root/ROOT/pgw passwd.md' /etc/passwd
在文件的每行前面添加#注释
sed -n 's/^/#/p' /etc/passwd
删除文件的第1个字符
sed 's/^.//1' /etc/passwd
删除文件的第二个字符
sed 's/.//2' /etc/passwd
在每一行的上面添加hello
sed 'i hello' /etc/passwd
在第一行的上面添加hello
sed '1a hello' /etc/passwd
将a~z替换成A~Z
sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' /etc/passwd
替换匹配行
sed -n '1c hello' /etc/passwd
输出1行
sed -n '1q' /etc/passwd
打印奇数行
sed -n 'p;n' /etc/passwd
打印偶数行
sed -n 'n;p' /etc/passwd
打印匹配字符串的下一行
sed -n '/^root/{n;p}' /etc/passwd
grep基础
grep (global regular Expression Print) 全局正则表达式输出
grep (选项) pattern flie
-A <显示行数> 除了显示符合匹配行,也显示匹配行后的n行
-B <显示行数> 除了显示符合匹配行,也显示匹配行前的n行
-C <显示行数> A+B 显示前后n行
-c 统计匹配行数的总数,仅显示
-e 实现多个选项件的逻辑关系 or
-E 扩展的正则表达式
-f FILE:从FILE获取PATTERN匹配
-i --ignore-case 忽略字符大小写的差别
-n 显示匹配的行号
-o 仅显示匹配到的字符串
-v 反向匹配,显示不被pattern匹配到的行
-w 匹配整个单词
需要注意的地方
- egrep = grep -E 除了 < \> \b 以外其他正则都可以去掉
- 如果有要匹配的内容中,有( ; )这种的话最好加"" 比如 grep ";" test.txt
正则表达式
匹配内容
.匹配全部字符,不匹配空行
[]匹配指定范围内的任意单个字符
[^] 取反
[:alnum:] [0-9a-zA-Z]
[:alpha:] [a-zA-Z]
[:upper:] 或 [A-Z]
[:lower:] 或 [a-z]
[:blank:] 匹配空白字符
[:punct:] 标点符号
匹配次数
*匹配任意次 0-n次 ,贪婪模式(尽可能多匹配)
.*匹配任意次 1-n次
\?匹配 0-1次
\+至少匹配1次
char\{n\} 匹配char n次
char\{n,m\} 匹配char n-m次
char\{,n\} 匹配char 最多n次
char\{n,\} 匹配char 至少n次
位置确定
^ 行首
$ 行尾
^$ 空白行
划组匹配
在正则表达示中使用之前匹配到的内容,即划组匹配
# 分组形式 \(\)
# 读取方式 \1 \2 \3