深入浅出认识awk

awk是一个强大的文本处理工具,常用于Unix/Linux系统。它能按行处理文本,通过字段分割、条件判断和自定义输出实现数据筛选与转换。本文介绍了awk的基本用法,包括设置字段分隔符、条件判断和动作执行,并通过多个实战案例展示了awk在统计计算、进程分析、日志处理和多文件操作等场景的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

awk是Unix-like系统常用的文本处理工具。

其功能可总结为:将输入文本切成表格,通过行筛选、字段重组、跨行上下文、逻辑判断等灵活组合,实现定制化输出

简单示例

通过简单示例,建立初步印象。
输入文件:coord_list

115.631449,33.110324;115.638022,33.116035
0.0,0.0;0.0,0.0
115.150937,40.549179;115.157529,40.554885
110.029599,27.578549;110.036011,27.584827
0.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.116035
115.157529|40.554885
110.036011|27.584827

命令结构

awk命令结构

参数部分

常用的有

  • -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 CMD
root          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 -D
root        448      1  0 Jan17 ?        00:00:05 /usr/sbin/crond -n
root        486      1  0 Jan17 ?        01:19:11 /home/staragent/bin/staragentd
root        880      0  0 Jan17 ?        00:33:10 /home/a/xxxxxxxxxx/xxxxxxxxxx -conf /h+
root        924    880  0 Jan17 ?        00:00:00 [nslookup] <defunct>
root       2491      1  0 Jan17 ?        00:00:00 /usr/local/ilogtail/ilogtail
root       2493   2491  0 Jan17 ?        00:43:06 /usr/local/ilogtail/ilogtail
root       4430      0  0 Jan17 ?        00:00:00 nginx: master process /opt/xxxxxx/xxxx+
root      16803      0  0 11:41 ?        00:00:03 /usr/bin/xxxxxxxxxx --verbose=2 -f /us+
root      63148      1  0 Jan19 ?        00:15:10 /home/staragent/plugins/xxxxxxxxxxxx/j+
root      85570    444  0 17:26 ?        00:00:00 sshd: yizishou [priv]
yizishou  85572  85570  0 17:26 ?        00:00:00 sshd: yizishou@pts/0
yizishou  85573  85572  0 17:26 pts/0    00:00:00 -bash
yizishou  90169  85573  0 17:50 pts/0    00:00:00 ps -ef
root     200642      1  0 Jan18 ?        00:00:35 /usr/sbin/syslog-ng -F -p /var/run/sys+
root     200654      1  0 Jan18 ?        00:02:43 /usr/lib/systemd/systemd-journald
root     200963      1  1 Jan19 ?        01:36:22 /home/xxxxxxxxx/plugins/xxxxxxxxx/xxxx+

命令:

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-1
0-1-444
0-1-448
0-1-486
0-880
0-880-924
0-1-2491
0-1-2491-2493
0-4430
0-16803
0-1-63148
0-1-444-85570
0-1-444-85570-85572
0-1-444-85570-85572-85573
0-1-444-85570-85572-85573-90169
0-1-200642
0-1-200654
0-1-200963
案例3:跨行合并与还原的案例【Java栈整体筛选】

思路:将多行合并为一行,筛选后还原为多行
输入文件:stk(来自 jstack 输出结果,截取部分内容)

2022-01-25 11:18:57
Full 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),默认为换行符\n
    • BEGIN{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.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 -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等)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值