awk是Unix-like系统常用的文本处理工具。
其功能可总结为:将输入文本切成表格,通过行筛选、字段重组、跨行上下文、逻辑判断等灵活组合,实现定制化输出。
简单示例 通过简单示例,建立初步印象。
输入文件:coord_list
115.631449,33.110324;115.638022,33.1160350.0,0.0;0.0,0.0115.150937,40.549179;115.157529,40.554885110.029599,27.578549;110.036011,27.5848270.0,0.0;0.0,0.0
命令:
awk -F'[,;]' '$1!=0 {print $3"|"$4}' coord_list
其中
参数: -F'[,;]' 指定正则做为字段分隔符(单个逗号或分号);条件: $1!=0 第一个字段必须不等于0;动作: {print $3"|"$4} 输出第三和第四个字段,中间以竖线分隔。
输出:
115.638022|33.116035115.157529|40.554885110.036011|27.584827
命令结构 参数部分常用的有
-F 指定字段切分的分隔符,默认为空格。可以指定单个字符,比如 -F, 或 -F: 或 -F't' ;如果指定多个字符,则视为正则表达式,比如 -F' |t' 或 -F's+' 脚本部分
完整形态的脚本模式:
'BEGIN{前置动作} 行操作 END{后置动作}'
其中
BEGIN{前置动作}:可选,在整个文件处理开始前执行,一般会 修改配置、定义全局变量、打印表头等行操作(for each line):可选,每行数据执行一次,具体模式见下文END{后置动作}:可选,在整个文件处理结束后执行,一般会 处理Buffer中的残留内容
行操作的具体模式有
'条件':对满足条件的行,执行隐含动作:直接打印当前行(等价于 '条件 {print $0}' )'{动作}':对所有行,执行动作'条件 {动作}':对满足条件的行,执行动作'条件1 {动作1} 条件2 {动作2}':对满足条件1的行,执行动作1;对满足条件2的行,执行动作2 脚本使用案例 案例1:包含完整命令结构的简单示例【对前5个数字求平均】
输入文件:top10
美国,72958690印度,39799202巴西,24134946法国,16800913英国,15953685俄罗斯,11241109土耳其,11014152意大利,10001344西班牙,9280890德国,8808107
命令:
awk -F, 'BEGIN{sum=0;cnt=0} NR<=5 {sum+=$2;cnt++} END{print sum/cnt/10000"万"}' top10
其中
NR<=5:筛选前5行。NR为awk内置变量,含义为当前行号(Number of Records)
输出:
3392.95万
案例2:使用全局变量的简单示例【构造进程pid关系链】输入文件:ps(来自 ps -ef 输出结果,已脱敏)
UID PID PPID C STIME TTY TIME CMDroot 1 0 0 Jan17 ? 00:36:47 /usr/bin/python /usr/lib/systemd/syste+root 444 1 0 Jan17 ? 00:00:08 /usr/sbin/sshd -Droot 448 1 0 Jan17 ? 00:00:05 /usr/sbin/crond -nroot 486 1 0 Jan17 ? 01:19:11 /home/staragent/bin/staragentdroot 880 0 0 Jan17 ? 00:33:10 /home/a/xxxxxxxxxx/xxxxxxxxxx -conf /h+root 924 880 0 Jan17 ? 00:00:00 [nslookup]
命令:
awk 'NR>1 {s=(m[$3]==null?$3:m[$3])"-"$2; m[$2]=s; print s}' ps
其中
NR>1:过滤掉表头行。NR为awk内置变量,含义为当前行号(Number of Records)m:awk的变量是免定义的,会根据用法自动推断类型并初始化。这里将m作为Map使用,初始化为空Map
输出:
0-10-1-4440-1-4480-1-4860-8800-880-9240-1-24910-1-2491-24930-44300-168030-1-631480-1-444-855700-1-444-85570-855720-1-444-85570-85572-855730-1-444-85570-85572-85573-901690-1-2006420-1-2006540-1-200963
案例3:跨行合并与还原的案例【Java栈整体筛选】 思路:将多行合并为一行,筛选后还原为多行
输入文件:stk(来自 jstack 输出结果,截取部分内容)
2022-01-25 11:18:57Full thread dump OpenJDK 64-Bit Server VM (25.242-b24 mixed mode):"process reaper" #38398 daemon prio=10 os_prio=0 tid=0x00002b4b4e019800 nid=0x256b6 waiting on condition [0x00002b4b2af7c000] java.lang.Thread.State: TIMED_WAITING (parking) at sun.misc.Unsafe.park0(Native Method) - parking to wait for <0x00000000f820d960> (a java.util.concurrent.SynchronousQueue$TransferStack) at sun.misc.Unsafe.park(Unsafe.java:1038) at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:216) at ..."Attach Listener" #479 daemon prio=9 os_prio=0 tid=0x00002b4b49613000 nid=0x1430f waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE..."Surrogate Locker Thread (Concurrent GC)" #4 daemon prio=9 os_prio=0 tid=0x00002b4b2b04a000 nid=0xf6c7 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00002b4b2b049000 nid=0xf6c6 in Object.wait() [0x00002b4b426ff000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144) - locked <0x00000000f8038ed8> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:287)"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00002b4b2b048000 nid=0xf6c5 in Object.wait() [0x00002b4b421d4000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:502) at java.lang.ref.Reference.tryHandlePending(Reference.java:191) - locked <0x00000000f8038f08> (a java.lang.ref.Reference$Lock) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)"main" #1 prio=5 os_prio=0 tid=0x00002b4b2b047000 nid=0xf6b8 in Object.wait() [0x00002b4b283b7000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000000f803ca80> (a io.netty.channel.AbstractChannel$CloseFuture) at java.lang.Object.wait(Object.java:502) at io.netty.util.concurrent.DefaultPromise.await(DefaultPromise.java:254) - locked <0x00000000f803ca80> (a io.netty.channel.AbstractChannel$CloseFuture) at io.netty.channel.DefaultChannelPromise.await(DefaultChannelPromise.java:129) at ......
命令:
awk '{ if(match($0,"^\s")==1) {s=s"@@"$0} else { if(s!="") {print s} s=$0 } } END{if(s!="") {print s}}' stk | grep 'java.lang.ref.Reference' | awk 'BEGIN{RS="n|@@"} {print $0}'
其中
前一句awk:将Java栈压缩为一行(以@@分隔,以便还原)
if(match($0,"^\s")==1):判断当前行是否以空白字符开头。如果true,则认为是跟随行,需要合并到上一行后面s:awk的变量是免定义的,会根据用法自动推断类型并初始化。这里将s作为String使用,初始化为空串s=s"@@"$0:将当前行拼接在变量s后面,以@@分隔 grep语句:在压缩的Java栈中筛选"java.lang.ref.Reference"后一句awk:将压缩的Java栈还原为多行
RS:是awk的内置变量,即行分隔符(Record Separator),默认为换行符nBEGIN{RS="n|@@"}:将行分隔符修改为正则表达式n|@@。即保留默认n的基础上,追认@@为换行符
输出:内容包含 java.lang.ref.Reference 的两个线程栈
"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00002b4b2b049000 nid=0xf6c6 in Object.wait() [0x00002b4b426ff000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144) - locked <0x00000000f8038ed8> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:287)"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00002b4b2b048000 nid=0xf6c5 in Object.wait() [0x00002b4b421d4000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:502) at java.lang.ref.Reference.tryHandlePending(Reference.java:191) - locked <0x00000000f8038f08> (a java.lang.ref.Reference$Lock) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
案例4:多文件关联的案例【维度文件(小)与数据文件(大)】 思路:将维度加载为内存字典,提供给数据文件查询
输入文件1:dict(行政区划代码)
110000,北京市110101,东城区110102,西城区110105,朝阳区110106,丰台区110107,石景山区110108,海淀区110109,门头沟区110111,房山区110112,通州区...
输入文件2:data(包含行政区划代码的地理数据,已脱敏)
341204,〇营,115.630000,33.110000420107,〇〇〇〇专卖店,114.380000,30.630000130705,〇〇〇〇道,115.150000,40.540000340521,〇〇〇〇(〇〇村店),118.820000,31.380000330523,〇〇〇〇〇〇(〇〇路店),119.660000,30.790000370781,〇〇〇衣(〇〇站),118.530000,36.690000513433,〇〇〇〇饭店,102.210000,28.550000652926,〇〇〇〇销售中心,81.860000,41.790000431202,〇〇〇〇特价处理,110.020000,27.570000110115,停车场(〇〇〇〇〇〇〇〇),116.430000,39.760000...
命令:
awk -F',' '{if(FILENAME=="dict"){m[$1]=$2}else{print m[$1]","$0}}' dict data
其中
FILENAME:是awk的内置变量,即当前行所在的文件名。注意,通过管道输入的数据不存在文件名
输出:附带行政区划名称的地理数据
颍泉区,341204,〇营,115.630000,33.110000青山区,420107,〇〇〇〇专卖店,114.380000,30.630000宣化区,130705,〇〇〇〇道,115.150000,40.540000当涂县,340521,〇〇〇〇(〇〇村店),118.820000,31.380000安吉县,330523,〇〇〇〇〇〇(〇〇路店),119.660000,30.790000青州市,370781,〇〇〇衣(〇〇站),118.530000,36.690000冕宁县,513433,〇〇〇〇饭店,102.210000,28.550000拜城县,652926,〇〇〇〇销售中心,81.860000,41.790000鹤城区,431202,〇〇〇〇特价处理,110.020000,27.570000大兴区,110115,停车场(〇〇〇〇〇〇〇〇),116.430000,39.760000...
如果要深入了解提供几个方向,可自行学习:
awk命令参数awk支持的运算符awk内置函数(比如print/match等)awk内置变量(比如NR/NF/FILENAME等)