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 匹配整个单词  

需要注意的地方

  1. egrep = grep -E 除了 < \> \b 以外其他正则都可以去掉
  2. 如果有要匹配的内容中,有( ; )这种的话最好加"" 比如 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
12-27 21:12