# awk

# 定义

awk 是一个文本处理工具,通常用于处理数据并生成结果报告

awk 的命名方式是它的三个创始人的首字母组成的

# 语法格式

语法格式 含义
BEGIN{} 表示在正式处理文本之前执行的命令
pattern 匹配模式
{command} 处理命令,可能多行,通过分号隔开。可执行变成命令比如 for 循环
END{} 处理文本之后的执行命令

# 第一种

awk 'BEGIN{}pattern{command}END{}'file_name

# 第二种

standard output | BEGIN{}pattern{command}END{}

# 第三种(引用外部 awk)

  • 在 vscode 中可以安装 awk 高亮工具使得-f 的时候单独使用.awk 文件代码高亮

执行 xxx.awk 文件

awk -f xxx.awk

或者 执行 xxx.awk 中的语句处理 aaa.txt

awk -f xxx.awk aaa.txt

# 常用选项

  • 推荐-v 引入变量的时候都用双引号包起来
选项 解释
-v 参数传递(不同与 sed,awk 是不能够直接引用变量的)
-f 指定执行脚本文件
-F 指定分隔符
-V 查看 awk 版本号(Linux)
--version 查看 awk 版本号(Macos)
var1=1
var2=2

awk -v num1=$var1 -v num2=$var2 'BEGIN{print num1,num2}'

1 2

var1=1
var2="hello world"

# 注意如果外部变量是字符串并且中间有空格那么-v的时候要用双引号引起来
awk -v num1=$var1 -v num2="$var2" 'BEGIN{print num1,num2}'

# 直接执行string.awk中的awk命令
awk -f string.awk

# 指定string.awk里面的awk命令对执行awk-string.txt
awk -f string.awk awk-string.txt

# 使用F指定分隔符
awk -F ":" '{print $1}' test1.txt
awk -F : '{print $1}' test1.txt
awk -F: '{print $1}' test1.txt
# 等价于
awk 'BEGIN{FS=":"}{print $1}' test1.txt

# 内置变量

  • ⚠️macos 与 Linux 中有很多表现不一致的地方,以实际操作表现为准。可能性太多,无法完全记录

  • 小技巧: print 本身是带换行的

# 内置变量对照表

  • {print $1,$3}是指输出第一个和第三个而不是 1-3

  • $NF通常表示每一行最大的字段,NF代表每一行的个数,那么$NF 表示第 NF 个

内置变量 含义
$0 整行内容
$1-$n 当前行的第 1-n 个字段
NF(Number Field) 当前行的字段个数,也就是有多少列
NR (Number Row) 当前行的行号,从 1 开始计数
FNR 多文件处理时,每个文件的行号单独计数
FS(Field separator) 输入字段分隔符。不指定默认以空格或者 tab 键划分
RS 输入行分隔符。默认回车换行\n
OFS(output FS) 输出字段分隔符。默认为空格
ORS 输入行分隔符。默认回车换行
FILENAME 当前输入的问文件名字
ARGC 命令行参数个数
ARGV 命令行参数数组

test1.txt

123:777
456:888
789 asd
# 输出所有内容
awk '{print $0}' test1.txt

# 以:为分隔符,输出每一行的第一个字段
awk 'BEGIN{FS=":"}{print $1}' test1.txt

123
456
789 asd

# 输出各行字段个数
awk '{print NF}' test1.txt

# 输出第NF个分割的的变量的值
awk '{print $NF}' test1.txt

# 输出两个文件的行号(累加)
awk '{print NR}' test1.txt test2.txt

# 输出两根文件的行号(不累加)
awk '{print FNR}' test1.txt test2.txt

test1.txt

  • ⚠️ macos 下面操作完成后可能会多出末尾空行或者间隔空行,可以使用 grep 等方式去除
1|2|3--4|5|6--7|8|9
# 以--为换行表示分割文本内容,并忽略空行(可能会造成空行)
awk 'BEGIN{RS="--"}{print $0}' test1.txt | grep -v "^$"

1|2|3
4|5|6
7|8|9

# 以-为行分隔符并以|为字符分割输出第一个
awk 'BEGIN{RS="--";FS="|"}{print $1}' test1.txt | grep -v "^$"

1
4
7

# 输入同上,输出以^为行分隔符
awk 'BEGIN{RS="--";FS="|";ORS="^"}{print $1}' test1.txt | grep -v "^$"

test1.txt

ljc:cw--cw:ljc
zxp--pz--ll
# print $1,$2和print $1 $2 是有区别的,主要体现在打印上面
awk 'BEGIN{RS="--";FS=":"}{print $1,$2}' test1.txt | grep -v "^$"

ljc cw

cw ljc
zxp

pz

ll

#
awk 'BEGIN{RS="--";FS=":"}{print $1$2}' test1.txt | grep -v "^$"

ljccw
cwljc
zxp
pz
ll

text3.txt

Hadoop|Spark|Flume--Java|Python|Scala|Go--Allen|Mike|Meggie
  • ⚠️Macos 与 Linux 有不同表现,行与行之间默认会多一个换行符,参照以下例子的第一个区别,后续展示 Macos,Linux 不再展示

  • {print $1,$3}如果不用逗号隔开的话,设置 OFS 是没有效果的,会按照默认空格分割

awk 'BEGIN{RS="--"}{print $0}' test3.txt | grep -v "^$"

Hadoop|Spark|Flume
Java|Python|Scala|Go
Allen|Mike|Meggie

awk 'BEGIN{RS="--";FS="|"}{print $3}' test3.txt | grep -v "^$"

Flume
Scala
Meggie

awk 'BEGIN{RS="--";FS="|";ORS="&"}{print $0}' test3.txt | grep -v "^$"

Hadoop|Spark|Flume&&Java|Python|Scala|Go&&Allen|Mike|Meggie& # Macos

awk 'BEGIN{RS="--";FS="|";ORS="&"}{print $0}' test3.txt

Hadoop|Spark|Flume&Java|Python|Scala|Go&Allen|Mike|Meggie # Linux

# {print $1 $3}
awk 'BEGIN{RS="--";FS="|";ORS="&"}{print $1 $3}' test3.txt | grep -v "^$"

HadoopFlume&&JavaScala&&AllenMeggie&

# {print $1,$3}
awk 'BEGIN{RS="--";FS="|";ORS="&"}{print $1,$3}' test3.txt | grep -v "^$"

Hadoop Flume& &Java Scala& &Allen Meggie&

awk 'BEGIN{RS="--";FS="|";ORS="&";OFS="\*"}{print $1,$3}' test3.txt | grep -v "^$"

Hadoop*Flume&*&Java*Scala&*&Allen*Meggie&

# 获取处理文件的文件名
awk '{print FILENAME}' test3.txt

test3.txt

awk '{print FILENAME}' test3.txt test1.txt # 如果没有限定条件,那么输出是按照匹配行的个数来输出的,所以输出可能多次文件名

test3.txt
test1.txt
test1.txt

# 输出命令行参数个数 awk和test3.txt共2个
awk '{print ARGC}' test3.txt

2

# 格式化输出 printf

  • ⚠️ 中文有可能使 printf 达不到预期效果

# 语法

{printf "格式符+修饰符", 变量}

# printf 的格式说明符

格式符 含义
%(n)s 打印字符串 (n 为占位字符,如%20s)
%d 打印十进制数
%f 打印一个浮点数(默认 6 位小数)
%x 打印十六进制数
%o 打印八进制数
%e 打印数字的科学计数
%c 打印单个字符的 ASCll 码

# printf 的修饰符

修饰符 含义
- 左对齐
+ 右对齐
# 显示八进制在前面加 0,显示十六进制在前面加 0x
  • Linux 和 Macos 在表现上有区别,例如 Linux 下使用修饰符+-必须有位数如%+15s

printf.txt

ljc:cw:111
cw:ljc:222
cyx:fxl:333
awk '{printf "%s", $0}' printf.txt

ljc:cwcw:ljccyx:fxl

awk '{printf "%s\n", $0}' printf.txt

ljc:cw
cw:ljc
cyx:fxl

awk 'BEGIN{FS=":"}{printf "%s%s\n", $1,$2}' printf.txt

ljccw
cwljc
cyxfxl

# "%s %s\n"变量之间放空格
awk 'BEGIN{FS=":"}{printf "%s %s\n", $1,$2}' printf.txt

ljc cw
cw ljc
cyx fxl

# 使用占位字符(默认右对齐)
awk 'BEGIN{FS=":"}{printf "%20s %20s\n", $1,$2}' printf.txt

  ljc                   cw
   cw                  ljc
  cyx                  fxl

# 使用左右对其方式
awk 'BEGIN{FS=":"}{printf "%-20s %20s\n", $1,$2}' printf.txt

ljc                     cw
cw                     ljc
cyx                    fxl

# 十进制整数
awk 'BEGIN{FS=":"}{printf "%d\n", $3}' printf.txt

111
222
333

# 显示小数
awk 'BEGIN{FS=":"}{printf "%.2f\n", $3}' printf.txt

111.00
222.00
333.00

# 匹配模式

语法格式 含义
RegExp 按正则表达式匹配
运算符 按关系运算匹配
关系运算符匹配 含义
< 小于
> 大于
<= 小于等于
>= 大于等于
== 等于
!= 不等于
~ 匹配正则表达式
!~ 不匹配正则表达式
布尔运算符匹配 含义
||
&&
!

pattern.txt

zhang,24,90,85,88,94
wang,22,70,80,75,85
li,31,80,90,60,55
# 匹配含有90的行
awk '/90/{print $0}' pattern.txt

# 匹配第4个元素大于80的并打印出来
awk 'BEGIN{FS=","}$4>80{printf "%-20s %20d\n", $0,$4}' pattern.txt

zhang,24,90,85,88,94                   85
li,31,80,90,60,55                      90

# 匹配$1正则表达式满足/^zhang/的行
awk 'BEGIN{FS=","}$1~/^zhang/{print $0}' pattern.txt

zhang,24,90,85,88,94

# 匹配$1正则表达式不满足/^zhang/的行
awk 'BEGIN{FS=","}$1!~/^zhang/{print $0}' pattern.txt

wang,22,70,80,75,85
li,31,80,90,60,55

# 匹配$2大于30的或者$1以满足/^wang/的行
awk 'BEGIN{FS=","}$2>30 || $1~/^wang/{print $0}' pattern.txt

wang,22,70,80,75,85
li,31,80,90,60,55

# 表达式

# 动作表达式中的算数运算符

  • awk 默认支持浮点数
运算符 含义
+-*/% 加减乘除余
^或** 乘方
++x 在返回 x 变量之前,x 变量加 1
x++ 在返回 x 变量之后,x 变量加 1
--x 在返回 x 变量之前,x 变量减 1
x-- 在返回 x 变量之后,x 变量减 1
awk 'BEGIN{a=1;b=2;print a,b}'

1 2

awk 'BEGIN{a=1;b=2;printf "%.2f", a/b}' | grep -v "^$"

0.50

# 显示空行的个数
grep -wc "^$" test1.txt
# 等价
awk '/^$/{sum++}END{print sum}' pattern.txt

# 动作中的条件及循环语句

# 条件语法

if (条件表达式) {
  动作1
} else if(条件表达式) {
  动作2
} else {
  动作3
}
awk '{if($3>=90) print $0,$3}' student.txt

awk '{if($3>=80 && $3<90) print $0}' student.txt

# 循环语法 while

while (条件表达式){
  动作·
}

# 循环语法 do while

do
  动作
while (条件表达式)

# 循环语法 for

for (i=0;i<n;i++){
  动作
}

# 字符串函数

默认可以使用的关于字符串处理的函数

函数名 解释 函数返回值
length(str) 计算字符串长度 整数长度值
index(str1,str2) 在 str1 中查询到的 str2 的位置 返回值为位置索引,从 1 计数
tolower(str) 小写转换 转换后的小写字符串
toupper(str) 大写转换 转换后的大写字符串
split(str,arr,fs) 按 fs 分隔字符串,并保存到 arr 中,fs 默认是空格或者 tab 切割后的字串的个数
substr(str,m,n) 截取子串,从 m 个字符开始,截取 n 位。n 若不指定,则默认截取到字符串尾 截取后的字串
match(str,RE) 在 str 中按 /RE/ 查找,返回位置 返回索引位置
sub(RE,RepStr,str) 在 str 中按 RE 查找符合的字串并替换成 RepStr,只替换第一个 替换的个数
gsub(RE,RepStr,str) 在 str 中按 RE 查找符合的字串并替换成 RepStr,替换所有 替换的个数

awk-string.txt

root:x:0:0:root:/root:/bin/bash
awk -f string.awk awk-string.txt

BEGIN{
			FS=":"
		}

		{
			i=1
			while(i<=NF)
			{
				if(i==NF)
					printf "%d",length($i)
				else
					printf "%d:",length($i)
				i++
			}
			print ""
		}

4:1:1:1:4:5:9

搜索字符串"I have a dream"中出现"ea"子串的位置

awk 'BEGIN{str="I hava a dream";location=index(str,"ea");print location}'
awk 'BEGIN{str="I hava a dream";location=match(str,"ea");print location}'

将字符串"Hadoop Kafka Spark Storm HDFS YARN Zookeeper",按照空格为分隔符,分隔每部分保存到数组 array 中

awk 'BEGIN{str="Hadoop Kafka Spark Storm HDFS YARN Zookeeper";split(str,arr);for(a in arr) print arr[a]}'

搜索字符串"Tranction 2345 Start:Select * from master"第一个数字出现的位置

awk 'BEGIN{str="Tranction 2345 Start:Select * from master";location=match(str,/[0-9]/);print location}'

截取字符串"transaction start"的子串,截取条件从第 4 个字符开始,截取 5 位

awk 'BEGIN{str="transaction start";print substr(str,4,5)}'
awk 'BEGIN{str="transaction start";print substr(str,4)}'

替换字符串"Tranction 243 Start,Event ID:9002"中第一个匹配到的数字串为$符号

awk 'BEGIN{str="Tranction 243 Start,Event ID:9002";count=sub(/[0-9]+/,"$",str);print count,str}'
awk 'BEGIN{str="Tranction 243 Start,Event ID:9002";count=gsub(/[0-9]+/,"$",str);print count,str}'

# 数组的用法

  • 数组下标从 1 开始(和 shell 不同)

  • 数组下标不仅可以是数字,也可以是字符串,使用字符串作为下标时候,遍历使用 for (a in array)

  • 使用字符串下标的时候必须用双引号扩起来

BEGIN{
    arr["var1"]="word1"
    arr["var2"]="word2"
    for(a in arr) {
        print arr[a]
    }
}

# 数组的定义

str="Allen Jerry Mike Tracy Jordan Kobe Garnet"
split(str,array)

# 或
arr["var1"]="word1"
arr["var2"]="word2"

# 遍历语法 1

for (a in array)
			print array[a]

awk -f array.awk

BEGIN{
    str="Allen Jerry Mike Tracy Jordan Kobe Garnet"
    split(str,array)
    for(a in array) {
        print array[a]
    }
}

# 遍历语法 2

for (i=1;i<=length(array);i++)
			print array[i]

awk -f array.awk

BEGIN{
    str="Allen Jerry Mike Tracy Jordan Kobe Garnet"
    split(str,array)
    for(i=1;i<=length(array);i++) {
        print array[i]
    }
}