Shell文本处理三剑客之awk

awk 是一个文本处理工具,通常用于处理数据并生成结果报告。其命名源于三位创始人姓氏首字母:Alfred Aho、Peter Weinberger、Brian Kernighan。

语法:

awk [options] 'BEGIN{} pattern {commands} END{}' file stdout | awk [options] 'BEGIN{} pattern {commands} END{}'

说明:

options 选项 BEGIN{} 正式处理数据之前执行 pattern 匹配模式 {commands;...} 处理命令,可能多行 END{} 处理完所有匹配数据后执行

<!-- more -->

内置变量

变量名 说明 $0 整行内容 $1-$n 当前行的第 1 - n 个字段(列) NF Number Field,当前行字段个数(多少列) NR Number Row,当前行的行号,从 1 开始计数 FNR File Number Row,多文件处理时,每个文件行号单独计数,都是从 0 开始 FS Field Separator,输入字段分隔符(默认空格或 tab 键) RS Row Separator,输入行分隔符(默认回车换行) OFS Output Field Separator,输出字段分隔符(默认空格) ORS Output Row Separator,输出行分隔符(默认回车换行) FILENAME 当前输入的文件名字 ARGC 命令行参数个数 ARGV 命令行参数数组

示例:

# 以 : 分隔,输出第 1 列 ➜ awk 'BEGIN{FS=":"} {print $1}' /etc/passwd # 以 -- 分隔成行,以 : 分隔成列,输出第 1、2 列 ➜ awk 'BEGIN{FS=":";RS="--"} {print $1,$2}' /etc/passwd # 以 : 分隔列,输出最后一列,因为 NF 变量是总列数 ➜ awk 'BEGIN{FS=":"} {print $NF}' /etc/passwd

格式化输出(printf)

格式符 说明 修饰符 说明 %s 字符串 - 左对齐 %d 十进制 + 右对齐 %f 浮点数 # 八进制前面加 0,十六进制前面加 0x %x 十六进制 %o 八进制 %e 科学计数法 %c 单个字符的 ASCII 码

示例:

# printf "%+20s %-20s\n",$1,$7 # - 左对齐;+ 右对齐 # 20 列宽,不足则补空 # s 打印字符串 # .3f 打印保留 3 位数的浮点数 ➜ awk 'BEGIN{FS=":";OFS="-"}{printf "%+20s %20.3f %-20s\n",$1,$3,$7}' /etc/passwd root 0.000 /bin/bash bin 1.000 /sbin/nologin daemon 2.000 /sbin/nologin adm 3.000 /sbin/nologin lp 4.000 /sbin/nologin

模式匹配(pattern)

RegExp/patern/ 关系运算<><=>===!=~ 正则匹配、 !~ 非正则匹配、 && 与、 || 或、 !

示例:

# 打印以 root 开头的行 ➜ awk 'BEGIN{FS=":"} /^root/ {print $0}' /etc/passwd root:x:0:0:root:/root:/bin/bash # 打印第 3 列大于 1000 的行 ➜ awk 'BEGIN{FS=":"} $3>1000 {print $0}' /etc/passwd nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin # 打印第 7 列是 /sbin/nologin 的行 ➜ awk 'BEGIN{FS=":"} $7=="/sbin/nologin" {print $0}' /etc/passwd bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin # 打印第 7 列以 nologin 结尾的行 ➜ awk 'BEGIN{FS=":"} $7~/.*nologin$/ {print $0}' /etc/passwd bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin # 打印第 3 列大于 500,并且第 7 列以 nologin 结尾的行 ➜ awk 'BEGIN{FS=":"} $3>500 && $7~/.*nologin$/ {print $0}' /etc/passwd chrony:x:997:995::/var/lib/chrony:/sbin/nologin dockerroot:x:996:993:Docker User:/var/lib/docker:/sbin/nologin

计算表达式

示例:

# 数学计算 ➜ awk 'BEGIN{x=10;y=2; print x+y}' 12 ➜ awk 'BEGIN{x=10;y=2; print x*y}' 20 ➜ awk 'BEGIN{x=10;y=2; print x^y}' 100 ➜ awk 'BEGIN{x=10;y=2; print x**y}' 100 ➜ awk 'BEGIN{x=10;y=x++; print x,y}' 11 10 ➜ awk 'BEGIN{x=10;y=++x; print x,y}' 11 11 # 打印空行行号、统计空行数量 ➜ awk 'BEGIN{idx=0;} /^$/ {idx++; print NR} END{print idx;}' /etc/passwd

流程控制语句

语法:

# 条件判断 if(condition1) { # do something } else if(condition2) { # do something } else { # do something } # 循环 while(condition) { #do something } do # do something while(condition) for(i=0;i<10;i++) { # do something }

示例:

# 如果第 3 列小于 10 并且第 7 列是 /sbin/nologin 的行打印 this is if # 如果第 3 列大于 500 的打印 this is else if # 否则打印 this is else ➜ awk 'BEGIN{FS=":"} { if($3<10 && $7="/sbin/nologin") {print "this is if"} else if($3>500) {print "this is else if"} else {print "this is else"}}' /etc/passwd this is if this is if this is else this is else if this is else # 计算 1-10 相加的结果 # 注意:变量不需要提前声明 ➜ awk 'BEGIN{ while(i<10) { sum+=i; i++}; print sum}' 45 ➜ awk 'BEGIN{do { sum+=i; i++; } while(i<10); print sum}' 45 ➜ awk 'BEGIN{ for(i=0;i<10;i++) { sum+=i; }; print sum}' 45

字符串函数

函数名 说明 返回值 length(str) 计算字符串长度 整数长度值 index(str,sub_str) 在 str 中查找 sub_str 的位置 位置索引,从 1 计数 tolower(str) 转小写 转换后的小写字符串 toupper(str) 转大些 转换后的大写字符串 substr(str,start,length) 从 str 第 start 个字符开始,截取 length 位 截取后到子串 split(str,arr,fs) 按 fs 拆分字符串,结果保存到 arr 拆分后子串的个数 match(str,reg) 在 str 中按 reg 查找,返回位置 索引位置 sub(reg,new_sub_str,str) 在 str 中搜索符合 reg 的子串,将其替换为 new_sub_str, 只替换第一个 替换的个数 gsub(reg,new_sub_str,str) 类似 sub,替换所有 替换的个数

示例:

# sub(/oo/,"11",$1) 返回替换的个数;后面的 $1 为替换后的值 ➜ awk 'BEGIN{FS=":"} { print length($1),toupper($1),substr($1,0,2),sub(/oo/,"11",$1),$1}' /etc/passwd 4 ROOT ro 1 r11t 3 BIN bi 0 bin 6 DAEMON da 0 daemon 4 SYNC sy 0 sync # 数组下标从 1 开始 ➜ awk 'BEGIN{str="Shell;Python;C;C++;Java;PHP"; split(str,arr,";"); print arr[2]}' Python ➜ awk 'BEGIN{str="Shell;Python;C;C++;Java;PHP"; split(str,arr,";"); for(i in arr) { print arr[i]; }}' C++ Java PHP Shell Python C

常用选项(options)

-v 参数传递 -f 指定脚本文件 -v 指定分隔符 -V 查看 awk 版本

示例:

# 引入外部变量 ➜ var1=10 ➜ var2="hello awk" ➜ awk -v var1="$var1" -v var2="$var2" 'BEGIN{print var1,var2}' 10 hello awk # 把所有操作抽离到一个独立文件 # 建议:复杂操作优先使用这种方式,更易于程序理解和管理 ➜ touch script.awk BEGIN{ FS=":" } { if($3<10 && $7="/sbin/nologin") { print "this is if" } else if($3>500) { print "this is else if" } else { print "this is else" } } ➜ awk -f script.awk /etc/passwd # -F: 相当于 BEGIN{FS=":"} $ awk -F: '{print $1}' pwd root bin daemon

数组

shell 中的数组操作如下:

操作 示例 输出 定义一个数组 arr=("Python" "PHP" "Java" "Go" "Rust") 某个数组元素(下标从 0 开始) echo ${arr[2]} Java 数组元素个数 echo ${#arr[@]} 5 某个元素的长度 echo ${#arr[0]} 6 修改元素值 arr[2]="JAVA" 删除数组元素 unset arr[1] 打印所有数组元素) echo ${arr[@]} Python JAVA Go Rust 分片访问 echo ${arr[@]:0:2} Python JAVA 数组元素替换(找到的第一个) echo ${arr[@]/A/a} Python JaVA Go Rust 数组元素替换(所有) echo ${arr[@]//A/a} Python JaVa Go Rust 数组遍历 for a in ${arr[*]}; do echo $a; done
而 awk 中数组的使用略有不同,它使用 关联数组提供数组功能,即数组的索引可以是 数字任意字符串

语法示例:

# 定义 # 语法:array_name[index]=value ➜ awk 'BEGIN{arr[0]=0; arr["second"]="2"; print arr[0],arr["second"];}' 0 2 # 数组元素参与计算 ➜ awk 'BEGIN{arr[0]=0; arr["second"]="2"; print arr[0]+3,arr["second"];}' 3 2 # 删除数组元素 # 语法:delete array_name[index] ➜ awk 'BEGIN{arr[0]=0;arr["second"]="2"; delete arr["second"]; print arr["second"];}' # 遍历数组 # 方式一:for ... in 是无序输出 ➜ awk 'BEGIN{str="Python Rust PHP Go"; arrLen=split(str,arr," "); for(i in arr){ print i,arr[i] }}' 4 Go 1 Python 2 Rust 3 PHP # 方式二:for(i=1;i<=len;i++) { ... } 有序输出 ➜ awk 'BEGIN{str="Python Rust PHP Go"; arrLen=split(str,arr," "); for(i=1;i<=arrLen;i++){ print i,arr[i] }}' 1 Rust 2 Go 3 Python 4 PHP