awk
命令¶
1977 年,一个用于处理文本的编程语言级别的工具,名为 'awk',诞生于贝尔实验室。该名称来源于三位著名人物姓氏的首字母
- Alfred Aho
- Peter Weinberger
- Brian Kernighan
与 shell(bash, csh, zsh, 和 ksh)类似,awk
随着历史的发展也出现了各种变体
awk
:诞生于 1977 年的贝尔实验室。nawk
(new awk):创建于 1985 年,是awk
的更新和增强版本。它在 Unix System V Release 3.1 (1987) 中被广泛使用。oawk
指的是awk
的旧版本。gawk
(GNU awk):由 Paul Rubin 于 1986 年编写。GNU 项目诞生于 1984 年。mawk
:由 Mike Brennan 于 1996 年编写,是awk
编程语言的解释器。jawk
:在 JAVA 中实现的awk
在 GNU/Linux 操作系统中,通常的 awk
指的是 gawk
。然而,一些发行版,如 Ubuntu 或 Debian,使用 mawk
作为其默认的 awk
。
在所有较新的 Rocky Linux 版本中,awk
指的是 gawk
。
Shell > whereis awk
awk: /usr/bin/awk /usr/libexec/awk /usr/share/awk /usr/share/man/man1/awk.1.gz
Shell > ls -l /usr/bin/awk
lrwxrwxrwx. 1 root root 4 4月 16 2022 /usr/bin/awk -> gawk
Shell > rpm -qf /usr/bin/awk
gawk-4.2.1-4.el8.x86_64
有关未涵盖的信息,请参阅 gawk 手册。
虽然 awk
是一个用于处理文本的工具,但它也具备一些编程语言的特性
- 变量
- 流程控制(循环)
- 数据类型
- 逻辑运算
- 函数
- 数组
- ...
awk
的工作原理:类似于关系数据库,它支持处理字段(列)和记录(行)。默认情况下,awk
将文件的每一行视为一个记录,并将这些记录存储在内存中进行逐行处理,每一行的部分内容被视为记录中的一个字段。默认情况下,分隔不同字段的分隔符是空格和制表符,数字代表行记录中的不同字段。要引用多个字段,请用逗号或制表符分隔。
一个简单易懂的例子:
Shell > df -hT
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|Filesystem | Type | Size | Used | Avail | Use% | Mounted | on |←← 1 (first line)
|devtmpfs | devtmpfs | 1.8G | 0 | 1.8G | 0% | /dev | |←← 2
|tmpfs | tmpfs | 1.8G | 0 | 1.8G | 0% | /dev/shm | |←← 3
|tmpfs | tmpfs | 1.8G | 8.9M | 1.8G | 1% | /run | |←← 4
|tmpfs | tmpfs | 1.8G | 0 | 1.8G | 0% | /sys/fs/cgroup | |←← 5
|/dev/nvme0n1p2 | ext4 | 47G | 2.6G | 42G | 6% | / | |←← 6
|/dev/nvme0n1p1 | xfs | 1014M | 182M | 833M | 18% | /boot | |←← 7
|tmpfs | tmpfs | 364M | 0 | 364M | 0% | /run/user/0 | |←← 8 (end line)
Shell > df -hT | awk '{print $1,$2}'
Filesystem Type
devtmpfs devtmpfs
tmpfs tmpfs
tmpfs tmpfs
tmpfs tmpfs
/dev/nvme0n1p2 ext4
/dev/nvme0n1p1 xfs
tmpfs tmpfs
# $0: Reference the entire text content.
Shell > df -hT | awk '{print $0}'
Filesystem Type Size Used Avail Use% Mounted on
devtmpfs devtmpfs 1.8G 0 1.8G 0% /dev
tmpfs tmpfs 1.8G 0 1.8G 0% /dev/shm
tmpfs tmpfs 1.8G 8.9M 1.8G 1% /run
tmpfs tmpfs 1.8G 0 1.8G 0% /sys/fs/cgroup
/dev/nvme0n1p2 ext4 47G 2.6G 42G 6% /
/dev/nvme0n1p1 xfs 1014M 182M 833M 18% /boot
tmpfs tmpfs 364M 0 364M 0% /run/user/0
Awk 使用说明¶
awk
的用法是 - awk option 'pattern {action}' FileName
pattern:在文本中查找特定内容 action:操作指令 { }:根据特定模式对一些指令进行分组
option | 描述 |
---|---|
-f program-file --file program-file | 从文件中读取 awk 程序源代码 |
-F FS | 指定分隔字段的分隔符。这里的 'FS' 是 awk 中的内置变量,默认值为空格或制表符 |
-v var=value | 变量赋值 |
--posix | 开启兼容模式 |
--dump-variables=[file] | 将 awk 中的全局变量写入文件。如果未指定文件,则默认为 awkvars.out |
--profile=[file] | 将性能分析数据写入指定文件。如果未指定文件,则默认为 awkprof.out |
pattern | 描述 |
---|---|
BEGIN{ } | 在读取所有行记录之前执行的操作 |
END{ } | 在读取完所有行记录之后执行的操作 |
/regular expression/ | 匹配每个输入行记录的正则表达式 |
pattern && pattern | 逻辑与操作 |
pattern || pattern | 逻辑或操作 |
!pattern | 逻辑非操作 |
pattern1,pattern2 | 指定模式范围,匹配该范围内的所有行记录 |
awk
功能强大,涉及知识点较多,部分内容后续再讲解。
printf
命令¶
在正式学习 awk
之前,初学者需要了解 printf
命令。
printf
:格式化并打印数据。其用法是 -printf FORMAT [ARGUMENT]...
FORMAT:用于控制输出内容。支持以下常用的转义序列:
- \a - 警报 (BEL)
- \b - 退格
- \f - 换页
- \n - 换行
- \r - 回车
- \t - 水平制表符
- \v - 垂直制表符
- %Ns - 输出字符串。N 表示字符串的长度,例如:
%s %s %s
- %Ni - 输出整数。N 表示输出整数的长度,例如:
%i %i
- %m.nf - 输出浮点数。m 表示输出的总位数,n 表示小数点后的位数。例如:
%8.5f
ARGUMENT:如果是文件,则需要进行一些预处理才能正确输出。
Shell > cat /tmp/printf.txt
ID Name Age Class
1 Frank 20 3
2 Jack 25 5
3 Django 16 6
4 Tom 19 7
# Example of incorrect syntax:
Shell > printf '%s %s $s\n' /tmp/printf.txt
/tmp/printf.txt
# Change the format of the text
Shell > printf '%s' $(cat /tmp/printf.txt)
IDNameAgeClass1Frank2032Jack2553Django1664Tom197
# Change the format of the text
Shell > printf '%s\t%s\t%s\n' $(cat /tmp/printf.txt)
ID Name Age
Class 1 Frank
20 3 2
Jack 25 5
3 Django 16
6 4 Tom
19 7
Shell > printf "%s\t%s\t%s\t%s\n" a b c d 1 2 3 4
a b c d
1 2 3 4
RockyLinux 操作系统中不存在 print 命令。你只能在 awk
中使用 print,它与 printf 的区别在于它会在每行末尾自动添加一个换行符。例如:
Shell > awk '{printf $1 "\t" $2"\n"}' /tmp/printf.txt
ID Name
1 Frank
2 Jack
3 Django
4 Tom
Shell > awk '{print $1 "\t" $2}' /tmp/printf.txt
ID Name
1 Frank
2 Jack
3 Django
4 Tom
基本用法示例¶
-
从文件中读取
awk
程序源代码Shell > vim /tmp/read-print.awk #!/bin/awk {print $6} Shell > df -hT | awk -f /tmp/read-print.awk Use% 0% 0% 1% 0% 6% 18% 0%
-
指定分隔符
Shell > awk -F ":" '{print $1}' /etc/passwd root bin daemon adm lp sync ... Shell > tail -n 5 /etc/services | awk -F "\/" '{print $2}' awk: warning: escape sequence `\/' treated as plain `/' axio-disc 35100 pmwebapi 44323 cloudcheck-ping 45514 cloudcheck 45514 spremotetablet 46998
也可以使用单词作为分隔符。括号表示这是一个整体分隔符,"|" 表示或。
Shell > tail -n 5 /etc/services | awk -F "(tcp)|(udp)" '{print $1}' axio-disc 35100/ pmwebapi 44323/ cloudcheck-ping 45514/ cloudcheck 45514/ spremotetablet 46998/
-
变量赋值
Shell > tail -n 5 /etc/services | awk -v a=123 'BEGIN{print a}{print $1}' 123 axio-disc pmwebapi cloudcheck-ping cloudcheck spremotetablet
将 bash 中的用户自定义变量的值赋给 awk 的变量。
Shell > ab=123 Shell > echo ${ab} 123 Shell > tail -n 5 /etc/services | awk -v a=${ab} 'BEGIN{print a}{print $1}' 123 axio-disc pmwebapi cloudcheck-ping cloudcheck spremotetablet
-
将 awk 的全局变量写入文件
Shell > seq 1 6 | awk --dump-variables '{print $0}' 1 2 3 4 5 6 Shell > cat /root/awkvars.out ARGC: 1 ARGIND: 0 ARGV: array, 1 elements BINMODE: 0 CONVFMT: "%.6g" ENVIRON: array, 27 elements ERRNO: "" FIELDWIDTHS: "" FILENAME: "-" FNR: 6 FPAT: "[^[:space:]]+" FS: " " FUNCTAB: array, 41 elements IGNORECASE: 0 LINT: 0 NF: 1 NR: 6 OFMT: "%.6g" OFS: " " ORS: "\n" PREC: 53 PROCINFO: array, 20 elements RLENGTH: 0 ROUNDMODE: "N" RS: "\n" RSTART: 0 RT: "\n" SUBSEP: "\034" SYMTAB: array, 28 elements TEXTDOMAIN: "messages"
稍后会介绍这些变量的含义。现在进行回顾,跳转至变量。
-
BEGIN{ } 和 END{ }
Shell > head -n 5 /etc/passwd | awk 'BEGIN{print "UserName:PasswordIdentification:UID:InitGID"}{print $0}END{print "one\ntwo"}' UserName:PasswordIdentification:UID:InitGID 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 one two
-
--profile 选项
Shell > df -hT | awk --profile 'BEGIN{print "start line"}{print $0}END{print "end line"}' start line Filesystem Type Size Used Avail Use% Mounted on devtmpfs devtmpfs 1.8G 0 1.8G 0% /dev tmpfs tmpfs 1.8G 0 1.8G 0% /dev/shm tmpfs tmpfs 1.8G 8.9M 1.8G 1% /run tmpfs tmpfs 1.8G 0 1.8G 0% /sys/fs/cgroup /dev/nvme0n1p2 ext4 47G 2.7G 42G 6% / /dev/nvme0n1p1 xfs 1014M 181M 834M 18% /boot tmpfs tmpfs 363M 0 363M 0% /run/user/0 end line Shell > cat /root/awkprof.out # gawk profile, created Fri Dec 8 15:12:56 2023 # BEGIN rule(s) BEGIN { 1 print "start line" } # Rule(s) 8 { 8 print $0 } # END rule(s) END { 1 print "end line" }
修改 awkprof.out 文件。
Shell > vim /root/awkprof.out BEGIN { print "start line" } { print $0 } END { print "end line" } Shell > df -hT | awk -f /root/awkprof.out start line Filesystem Type Size Used Avail Use% Mounted on devtmpfs devtmpfs 1.8G 0 1.8G 0% /dev tmpfs tmpfs 1.8G 0 1.8G 0% /dev/shm tmpfs tmpfs 1.8G 8.9M 1.8G 1% /run tmpfs tmpfs 1.8G 0 1.8G 0% /sys/fs/cgroup /dev/nvme0n1p2 ext4 47G 2.7G 42G 6% / /dev/nvme0n1p1 xfs 1014M 181M 834M 18% /boot tmpfs tmpfs 363M 0 363M 0% /run/user/0 end line
-
Shell > cat /etc/services | awk '/[^0-9a-zA-Z]1[1-9]{2}\/tcp/ {print $0}' sunrpc 111/tcp portmapper rpcbind # RPC 4.0 portmapper TCP auth 113/tcp authentication tap ident sftp 115/tcp uucp-path 117/tcp nntp 119/tcp readnews untp # USENET News Transfer Protocol ntp 123/tcp netbios-ns 137/tcp # NETBIOS Name Service netbios-dgm 138/tcp # NETBIOS Datagram Service netbios-ssn 139/tcp # NETBIOS session service ...
-
逻辑运算(逻辑与、逻辑或、非)
逻辑与:&& 逻辑或:|| 非:!
Shell > cat /etc/services | awk '/[^0-9a-zA-Z]1[1-9]{2}\/tcp/ && /175/ {print $0}' vmnet 175/tcp # VMNET
Shell > cat /etc/services | awk '/[^0-9a-zA-Z]9[1-9]{2}\/tcp/ || /91{2}\/tcp/ {print $0}' telnets 992/tcp imaps 993/tcp # IMAP over SSL pop3s 995/tcp # POP-3 over SSL mtp 1911/tcp # rndc 953/tcp # rndc control sockets (BIND 9) xact-backup 911/tcp # xact-backup apex-mesh 912/tcp # APEX relay-relay service apex-edge 913/tcp # APEX endpoint-relay service ftps-data 989/tcp # ftp protocol, data, over TLS/SSL nas 991/tcp # Netnews Administration System vsinet 996/tcp # vsinet maitrd 997/tcp # busboy 998/tcp # garcon 999/tcp # #puprouter 999/tcp # blockade 2911/tcp # Blockade prnstatus 3911/tcp # Printer Status Port cpdlc 5911/tcp # Controller Pilot Data Link Communication manyone-xml 8911/tcp # manyone-xml sype-transport 9911/tcp # SYPECom Transport Protocol
Shell > cat /etc/services | awk '!/(tcp)|(udp)/ {print $0}' discard 9/sctp # Discard discard 9/dccp # Discard SC:DISC ftp-data 20/sctp # FTP ftp 21/sctp # FTP ssh 22/sctp # SSH exp1 1021/sctp # RFC3692-style Experiment 1 (*) [RFC4727] exp1 1021/dccp # RFC3692-style Experiment 1 (*) [RFC4727] exp2 1022/sctp # RFC3692-style Experiment 2 (*) [RFC4727] exp2 1022/dccp # RFC3692-style Experiment 2 (*) [RFC4727] ltp-deepspace 1113/dccp # Licklider Transmission Protocol cisco-ipsla 1167/sctp # Cisco IP SLAs Control Protocol rcip-itu 2225/sctp # Resource Connection Initiation Protocol m2ua 2904/sctp # M2UA m3ua 2905/sctp # M3UA megaco-h248 2944/sctp # Megaco-H.248 text ...
-
通过字符串定位连续的行并打印
Shell > cat /etc/services | awk '/^ntp/,/^netbios/ {print $0}' ntp 123/tcp ntp 123/udp # Network Time Protocol netbios-ns 137/tcp # NETBIOS Name Service
信息
起始范围:遇到第一个匹配项时开始匹配。结束范围:遇到第一个匹配项时停止匹配。
内置变量¶
变量名 | 描述 |
---|---|
FS | 输入字段的分隔符。默认为空格或制表符 |
OFS | 输出字段的分隔符。默认为空格 |
RS | 输入行记录的分隔符。默认为换行符 (\n) |
ORS | 输出行记录的分隔符。默认为换行符 (\n) |
NF | 统计当前行记录的字段数 |
NR | 统计行记录数。每处理完一行文本,该变量的值会 +1 |
FNR | 统计行记录数。处理第二个文件时,NR 变量会继续累加,而 FNR 变量会重新计数 |
ARGC | 命令行参数的数量 |
ARGV | 命令行参数的数组,下标从 0 开始,ARGV[0] 代表 awk |
ARGIND | 当前正在处理的文件的索引值。第一个文件是 1,第二个文件是 2,以此类推 |
ENVIRON | 当前系统的环境变量 |
FILENAME | 输出当前正在处理的文件名 |
IGNORECASE | 忽略大小写 |
SUBSEP | 数组中下标的分隔符,默认为 "\034" |
-
FS 和 OFS
Shell > cat /etc/passwd | awk 'BEGIN{FS=":"}{print $1}' root bin daemon adm lp sync
你也可以使用 -v 选项来为变量赋值。
Shell > cat /etc/passwd | awk -v FS=":" '{print $1}' root bin daemon adm lp sync
当使用逗号引用多个字段时,默认的输出分隔符是空格。但是,你可以单独指定输出分隔符。
Shell > cat /etc/passwd | awk 'BEGIN{FS=":"}{print $1,$2}' root x bin x daemon x adm x lp x
Shell > cat /etc/passwd | awk 'BEGIN{FS=":";OFS="\t"}{print $1,$2}' # or Shell > cat /etc/passwd | awk -v FS=":" -v OFS="\t" '{print $1,$2}' root x bin x daemon x adm x lp x
-
RS 和 ORS
默认情况下,
awk
使用换行符来区分每一行记录Shell > echo -e "https://example.com/books/index.html\ntitle//tcp" https://example.com/books/index.html title//tcp Shell > echo -e "https://example.com/books/index.html\ntitle//tcp" | awk 'BEGIN{RS="\/\/";ORS="%%"}{print $0}' awk: cmd. line:1: warning: escape sequence `\/' treated as plain `/' https:%%example.com/books/index.html title%%tcp %% ← Why? Because "print"
-
NF
统计当前文本中每行的字段数
Shell > head -n 5 /etc/passwd | awk -F ":" 'BEGIN{RS="\n";ORS="\n"} {print NF}' 7 7 7 7 7
打印第五个字段
Shell > head -n 5 /etc/passwd | awk -F ":" 'BEGIN{RS="\n";ORS="\n"} {print $(NF-2)}' root bin daemon adm lp
打印最后一个字段
Shell > head -n 5 /etc/passwd | awk -F ":" 'BEGIN{RS="\n";ORS="\n"} {print $NF}' /bin/bash /sbin/nologin /sbin/nologin /sbin/nologin /sbin/nologin
排除最后两个字段
Shell > head -n 5 /etc/passwd | awk -F ":" 'BEGIN{RS="\n";ORS="\n"} {$NF=" ";$(NF-1)=" ";print $0}' root x 0 0 root bin x 1 1 bin daemon x 2 2 daemon adm x 3 4 adm lp x 4 7 lp
排除第一个字段
Shell > head -n 5 /etc/passwd | awk -F ":" 'BEGIN{RS="\n";ORS="\n"} {$1=" ";print $0}' | sed -r 's/(^ )//g' x 0 0 root /root /bin/bash x 1 1 bin /bin /sbin/nologin x 2 2 daemon /sbin /sbin/nologin x 3 4 adm /var/adm /sbin/nologin x 4 7 lp /var/spool/lpd /sbin/nologin
-
NR 和 FNR
Shell > tail -n 5 /etc/services | awk '{print NR,$0}' 1 axio-disc 35100/udp # Axiomatic discovery protocol 2 pmwebapi 44323/tcp # Performance Co-Pilot client HTTP API 3 cloudcheck-ping 45514/udp # ASSIA CloudCheck WiFi Management keepalive 4 cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System 5 spremotetablet 46998/tcp # Capture handwritten signatures
打印文件内容的总行数
Shell > cat /etc/services | awk 'END{print NR}' 11473
打印第 200 行的内容
Shell > cat /etc/services | awk 'NR==200' microsoft-ds 445/tcp
打印第 200 行的第二个字段
Shell > cat /etc/services | awk 'BEGIN{RS="\n";ORS="\n"} NR==200 {print $2}' 445/tcp
打印特定范围内的内容
Shell > cat /etc/services | awk 'BEGIN{RS="\n";ORS="\n"} NR<=10 {print NR,$0}' 1 # /etc/services: 2 # $Id: services,v 1.49 2017/08/18 12:43:23 ovasik Exp $ 3 # 4 # Network services, Internet style 5 # IANA services version: last updated 2016-07-08 6 # 7 # Note that it is presently the policy of IANA to assign a single well-known 8 # port number for both TCP and UDP; hence, most entries here have two entries 9 # even if the protocol doesn't support UDP operations. 10 # Updated from RFC 1700, ``Assigned Numbers'' (October 1994). Not all ports
NR 与 FNR 的比较
Shell > head -n 3 /etc/services > /tmp/a.txt Shell > cat /tmp/a.txt # /etc/services: # $Id: services,v 1.49 2017/08/18 12:43:23 ovasik Exp $ # Shell > cat /etc/resolv.conf # Generated by NetworkManager nameserver 8.8.8.8 nameserver 114.114.114.114 Shell > awk '{print NR,$0}' /tmp/a.txt /etc/resolv.conf 1 # /etc/services: 2 # $Id: services,v 1.49 2017/08/18 12:43:23 ovasik Exp $ 3 # 4 # Generated by NetworkManager 5 nameserver 8.8.8.8 6 nameserver 114.114.114.114 Shell > awk '{print FNR,$0}' /tmp/a.txt /etc/resolv.conf 1 # /etc/services: 2 # $Id: services,v 1.49 2017/08/18 12:43:23 ovasik Exp $ 3 # 1 # Generated by NetworkManager 2 nameserver 8.8.8.8 3 nameserver 114.114.114.114
-
ARGC 和 ARGV
Shell > awk 'BEGIN{print ARGC}' log dump long 4 Shell > awk 'BEGIN{print ARGV[0]}' log dump long awk Shell > awk 'BEGIN{print ARGV[1]}' log dump long log Shell > awk 'BEGIN{print ARGV[2]}' log dump long dump
-
ARGIND
这个变量主要用于确定
awk
程序正在处理哪个文件。Shell > awk '{print ARGIND,$0}' /etc/hostname /etc/resolv.conf 1 Master 2 # Generated by NetworkManager 2 nameserver 8.8.8.8 2 nameserver 114.114.114.114
-
ENVIRON
你可以在
awk
程序中引用操作系统或用户自定义的变量。Shell > echo ${SSH_CLIENT} 192.168.100.2 6969 22 Shell > awk 'BEGIN{print ENVIRON["SSH_CLIENT"]}' 192.168.100.2 6969 22 Shell > export a=123 Shell > env | grep -w a a=123 Shell > awk 'BEGIN{print ENVIRON["a"]}' 123 Shell > unset a
-
FILENAME
Shell > awk 'BEGIN{RS="\n";ORS="\n"} NR=FNR {print ARGIND,FILENAME"---"$0}' /etc/hostname /etc/resolv.conf /etc/rocky-release 1 /etc/hostname---Master 2 /etc/resolv.conf---# Generated by NetworkManager 2 /etc/resolv.conf---nameserver 8.8.8.8 2 /etc/resolv.conf---nameserver 114.114.114.114 3 /etc/rocky-release---Rocky Linux release 8.9 (Green Obsidian)
-
IGNORECASE
如果你想在
awk
中使用正则表达式并且忽略大小写,这个变量很有用。Shell > awk 'BEGIN{IGNORECASE=1;RS="\n";ORS="\n"} /^(SSH)|^(ftp)/ {print $0}' /etc/services ftp-data 20/tcp ftp-data 20/udp ftp 21/tcp ftp 21/udp fsp fspd ssh 22/tcp # The Secure Shell (SSH) Protocol ssh 22/udp # The Secure Shell (SSH) Protocol ftp-data 20/sctp # FTP ftp 21/sctp # FTP ssh 22/sctp # SSH ftp-agent 574/tcp # FTP Software Agent System ftp-agent 574/udp # FTP Software Agent System sshell 614/tcp # SSLshell sshell 614/udp # SSLshell ftps-data 989/tcp # ftp protocol, data, over TLS/SSL ftps-data 989/udp # ftp protocol, data, over TLS/SSL ftps 990/tcp # ftp protocol, control, over TLS/SSL ftps 990/udp # ftp protocol, control, over TLS/SSL ssh-mgmt 17235/tcp # SSH Tectia Manager ssh-mgmt 17235/udp # SSH Tectia Manager
Shell > awk 'BEGIN{IGNORECASE=1;RS="\n";ORS="\n"} /^(SMTP)\s/,/^(TFTP)\s/ {print $0}' /etc/services smtp 25/tcp mail smtp 25/udp mail time 37/tcp timserver time 37/udp timserver rlp 39/tcp resource # resource location rlp 39/udp resource # resource location nameserver 42/tcp name # IEN 116 nameserver 42/udp name # IEN 116 nicname 43/tcp whois nicname 43/udp whois tacacs 49/tcp # Login Host Protocol (TACACS) tacacs 49/udp # Login Host Protocol (TACACS) re-mail-ck 50/tcp # Remote Mail Checking Protocol re-mail-ck 50/udp # Remote Mail Checking Protocol domain 53/tcp # name-domain server domain 53/udp whois++ 63/tcp whoispp whois++ 63/udp whoispp bootps 67/tcp # BOOTP server bootps 67/udp bootpc 68/tcp dhcpc # BOOTP client bootpc 68/udp dhcpc tftp 69/tcp
运算符¶
运算符 | 描述 |
---|---|
(...) | 分组 |
$n | 字段引用 |
++ | 递增 |
-- | 递减 |
+ | 数学加号 |
- | 数学减号 |
! | 取反 |
* | 数学乘号 |
/ | 数学除号 |
% | 取模运算 |
in | 数组中的元素 |
&& | 逻辑与运算 |
|| | 逻辑或运算 |
?: | 条件表达式的缩写 |
~ | 正则表达式的另一种表示 |
!~ | 反向正则表达式 |
注意
在 awk
程序中,以下表达式将被判断为 **false**
- 数字为 0;
- 空字符串;
- 未定义值。
Shell > awk 'BEGIN{n=0;if(n) print "Ture";else print "False"}'
False
Shell > awk 'BEGIN{s="";if(s) print "True";else print "False"}'
False
Shell > awk 'BEGIN{if(t) print "True";else print "Flase"}'
False
-
感叹号
打印奇数行
Shell > seq 1 10 | awk 'i=!i {print $0}' 1 3 5 7 9
问题
为什么? **读第一行**:因为 “i” 没有被赋值,所以 “i=!i” 表示 TRUE。**读第二行**:此时,“i=!i” 表示 FALSE。以此类推,最终打印的行数为奇数。
打印偶数行
Shell > seq 1 10 | awk '!(i=!i)' # or Shell > seq 1 10 | awk '!(i=!i) {print $0}' 2 4 6 8 10
注意
正如你所见,有时可以省略“action”部分,默认等同于 “{print $0}”。
-
反转
Shell > cat /etc/services | awk '!/(tcp)|(udp)|(^#)|(^$)/ {print $0}' http 80/sctp # HyperText Transfer Protocol bgp 179/sctp https 443/sctp # http protocol over TLS/SSL h323hostcall 1720/sctp # H.323 Call Control nfs 2049/sctp nfsd shilp # Network File System rtmp 1/ddp # Routing Table Maintenance Protocol nbp 2/ddp # Name Binding Protocol echo 4/ddp # AppleTalk Echo Protocol zip 6/ddp # Zone Information Protocol discard 9/sctp # Discard discard 9/dccp # Discard SC:DISC ...
-
数学中的基本运算
Shell > echo -e "36\n40\n50" | awk '{print $0+1}' 37 41 Shell > echo -e "30\t5\t8\n11\t20\t34" 30 5 8 11 20 34 Shell > echo -e "30\t5\t8\n11\t20\t34" | awk '{print $2*2+1}' 11 41
也可以用在“pattern”中
Shell > cat -n /etc/services | awk '/^[1-9]*/ && $1%2==0 {print $0}' ... 24 tcpmux 1/udp # TCP port service multiplexer 26 rje 5/udp # Remote Job Entry 28 echo 7/udp 30 discard 9/udp sink null 32 systat 11/udp users 34 daytime 13/udp 36 qotd 17/udp quote ... Shell > cat -n /etc/services | awk '/^[1-9]*/ && $1%2!=0 {print $0}' ... 23 tcpmux 1/tcp # TCP port service multiplexer 25 rje 5/tcp # Remote Job Entry 27 echo 7/tcp 29 discard 9/tcp sink null 31 systat 11/tcp users ...
-
管道符
你可以在 awk 程序中使用 bash 命令,例如:
Shell > echo -e "6\n3\n9\n8" | awk '{print $0 | "sort"}' 3 6 8 9
信息
请注意!你必须使用双引号来包含命令。
-
正则表达式
这里,我们涵盖了正则表达式的基本示例。你可以对行记录使用正则表达式。
Shell > cat /etc/services | awk '/[^0-9a-zA-Z]1[1-9]{2}\/tcp/ {print $0}' # Be equivalent to: Shell > cat /etc/services | awk '$0~/[^0-9a-zA-Z]1[1-9]{2}\/tcp/ {print $0}'
如果文件文本量很大,还可以对字段使用正则表达式,这有助于提高处理效率。用法示例如下:
Shell > cat /etc/services | awk '$0~/^(ssh)/ && $2~/tcp/ {print $0}' ssh 22/tcp # The Secure Shell (SSH) Protocol sshell 614/tcp # SSLshell ssh-mgmt 17235/tcp # SSH Tectia Manager Shell > cat /etc/services | grep -v -E "(^#)|(^$)" | awk '$2!~/(tcp)|(udp)/ {print $0}' http 80/sctp # HyperText Transfer Protocol bgp 179/sctp https 443/sctp # http protocol over TLS/SSL h323hostcall 1720/sctp # H.323 Call Control nfs 2049/sctp nfsd shilp # Network File System rtmp 1/ddp # Routing Table Maintenance Protocol nbp 2/ddp # Name Binding Protocol ...
流程控制¶
-
if 语句
基本语法格式为 -
if (condition) statement [ else statement ]
if 语句单分支使用的示例
Shell > cat /etc/services | awk '{if(NR==110) print $0}' pop3 110/udp pop-3
条件判定为正则表达式
Shell > cat /etc/services | awk '{if(/^(ftp)\s|^(ssh)\s/) print $0}' ftp 21/tcp ftp 21/udp fsp fspd ssh 22/tcp # The Secure Shell (SSH) Protocol ssh 22/udp # The Secure Shell (SSH) Protocol ftp 21/sctp # FTP ssh 22/sctp # SSH
双分支
Shell > seq 1 10 | awk '{if($0==10) print $0 ; else print "False"}' False False False False False False False False False 10
多分支
Shell > cat /etc/services | awk '{ \ if($1~/netbios/) {print $0} else if($2~/175/) {print "175"} else if($2~/137/) {print "137"} else {print "no"} }'
-
while 语句
基本语法格式为 -
while (condition) statement
遍历并打印所有行记录的字段。
Shell > tail -n 2 /etc/services cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System spremotetablet 46998/tcp # Capture handwritten signatures Shell > tail -n 2 /etc/services | awk '{ \ i=1; while(i<=NF){print $i;i++} }' cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System spremotetablet 46998/tcp # Capture handwritten signatures
-
for 语句
基本语法格式为 -
for (expr1; expr2; expr3) statement
遍历并打印所有行记录的字段。
Shell > tail -n 2 /etc/services | awk '{ \ for(i=1;i<=NF;i++) print $i }'
以反序打印每个行记录的字段。
Shell > tail -n 2 /etc/services | awk '{ \ for(i=NF;i>=1;i--) print $i }' System Management WiFi CloudCheck ASSIA # 45514/tcp cloudcheck signatures handwritten Capture # 46998/tcp spremotetablet
以反序打印每一行记录。
Shell > tail -n 2 /etc/services | awk '{ \ for(i=NF;i>=1;i--) {printf $i" "}; print "" }' System Management WiFi CloudCheck ASSIA # 45514/tcp cloudcheck signatures handwritten Capture # 46998/tcp spremotetablet
-
两者之间的比较如下:
Shell > awk 'BEGIN{ \ for(i=1;i<=10;i++) { if(i==3) {break}; print i } }' 1 2
Shell > awk 'BEGIN{ \ for(i=1;i<=10;i++) { if(i==3) {continue}; print i } }' 1 2 4 5 6 7 8 9 10
-
exit 语句
你可以指定一个 [0,255] 范围内的返回值
基本语法格式为 -
exit [expression]
Shell > seq 1 10 | awk '{ if($0~/5/) exit "135" }' Shell > echo $? 135
数组¶
array:具有相同数据类型并按一定顺序排列的数据集合。数组中的每个数据被称为元素。
像大多数编程语言一样,awk
也支持数组,数组分为 **索引数组(以数字为下标)** 和 **关联数组(以字符串为下标)**。
awk
有很多函数,与数组相关的函数有:
-
length(Array_Name) - 获取数组的长度。
-
自定义数组
格式 -
Array_Name[Index]=Value
Shell > awk 'BEGIN{a1[0]="test0" ; a1[1]="s1"; print a1[0]}' test0
获取数组长度
Shell > awk 'BEGIN{name[-1]="jimcat8" ; name[3]="jack" ; print length(name)}' 2
将所有的 GNU/Linux 用户存储到数组中
Shell > cat /etc/passwd | awk -F ":" '{username[NR]=$1}END{print username[2]}' bin Shell > cat /etc/passwd | awk -F ":" '{username[NR]=$1}END{print username[1]}' root
信息
awk
数组的数字下标可以是正整数、负整数、字符串或 0,所以awk
数组的数字下标没有初始值的概念。这和bash
中的数组是不一样的。Shell > arr1=(2 10 30 string1) Shell > echo "${arr1[0]}" 2 Shell > unset arr1
-
删除数组
格式 -
delete Array_Name
-
删除数组中的一个元素
格式 -
delete Array_Name[Index]
-
遍历数组
可以使用 **for** 语句,适用于数组下标未知的情况
Shell > head -n 5 /etc/passwd | awk -F ":" ' \ { username[NR]=$1 } END { for(i in username) print username[i],i } ' root 1 bin 2 daemon 3 adm 4 lp 5
如果数组的下标是规则的,可以使用这种形式的 **for** 语句
Shell > cat /etc/passwd | awk -F ":" ' \ { username[NR]=$1 } END{ for(i=1;i<=NR;i++) print username[i],i } ' root 1 bin 2 daemon 3 adm 4 lp 5 sync 6 shutdown 7 halt 8 ...
-
使用 "++" 作为数组的下标
Shell > tail -n 5 /etc/group | awk -F ":" '\ { a[x++]=$1 } END{ for(i in a) print a[i],i } ' slocate 0 unbound 1 docker 2 cgred 3 redis 4
-
使用字段作为数组的下标
Shell > tail -n 5 /etc/group | awk -F ":" '\ { a[$1]=$3 } END{ for(i in a) print a[i],i } ' 991 docker 21 slocate 989 redis 992 unbound 990 cgred
-
统计相同字段出现的次数
统计相同的 IPv4 地址出现的次数。基本思路
- 先用
grep
命令筛选出所有的 IPv4 地址 - 再交给
awk
程序处理
Shell > cat /var/log/secure | egrep -o "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" | awk ' \ { a[$1]++ } END{ for(v in a) print a[v],v } ' 4 0.0.0.0 4 192.168.100.2
信息
a[$1]++
等同于a[$1]+=1
统计单词出现的次数,忽略大小写。基本思路
- 将所有字段拆分到多个行记录中
- 再交给
awk
程序处理
Shell > cat /etc/services | awk -F " " '{for(i=1;i<=NF;i++) print $i}' Shell > cat /etc/services | awk -F " " '{for(i=1;i<=NF;i++) print $i}' | awk '\ BEGIN{IGNORECASE=1;OFS="\t"} /^netbios$/ || /^ftp$/ {a[$1]++} END{for(v in a) print a[v],v} ' 3 NETBIOS 18 FTP 7 ftp Shell > cat /etc/services | awk -F " " '{ for(i=1;i<=NF;i++) print $i }' | awk '\ BEGIN{IGNORECASE=1;OFS="\t"} /^netbios$/ || /^ftp$/ {a[$1]++} END{for(v in a) \ if(a[v]>=5) print a[v],v} ' 18 FTP 7 ftp
可以先筛选特定行记录,再进行统计,例如:
Shell > ss -tulnp | awk -F " " '/tcp/ {a[$2]++} END{for(i in a) print a[i],i}' 2 LISTEN
- 先用
-
根据特定字段的出现次数打印行
Shell > tail /etc/services aigairserver 21221/tcp # Services for Air Server ka-kdp 31016/udp # Kollective Agent Kollective Delivery ka-sddp 31016/tcp # Kollective Agent Secure Distributed Delivery edi_service 34567/udp # dhanalakshmi.org EDI Service axio-disc 35100/tcp # Axiomatic discovery protocol axio-disc 35100/udp # Axiomatic discovery protocol pmwebapi 44323/tcp # Performance Co-Pilot client HTTP API cloudcheck-ping 45514/udp # ASSIA CloudCheck WiFi Management keepalive cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System spremotetablet 46998/tcp # Capture handwritten signatures Shell > tail /etc/services | awk 'a[$1]++ {print $0}' axio-disc 35100/udp # Axiomatic discovery protocol
反转
Shell > tail /etc/services | awk '!a[$1]++ {print $0}' aigairserver 21221/tcp # Services for Air Server ka-kdp 31016/udp # Kollective Agent Kollective Delivery ka-sddp 31016/tcp # Kollective Agent Secure Distributed Delivery edi_service 34567/udp # dhanalakshmi.org EDI Service axio-disc 35100/tcp # Axiomatic discovery protocol pmwebapi 44323/tcp # Performance Co-Pilot client HTTP API cloudcheck-ping 45514/udp # ASSIA CloudCheck WiFi Management keepalive cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System spremotetablet 46998/tcp # Capture handwritten signatures
-
多维数组
awk
程序不支持多维数组,但可以通过模拟实现对多维数组的支持。默认情况下,"\034" 是多维数组下标的分隔符。使用多维数组时请注意以下几点区别
Shell > awk 'BEGIN{ a["1,0"]=100 ; a[2,0]=200 ; a["3","0"]=300 ; for(i in a) print a[i],i }' 200 20 300 30 100 1,0
重新定义分隔符
Shell > awk 'BEGIN{ SUBSEP="----" ; a["1,0"]=100 ; a[2,0]=200 ; a["3","0"]=300 ; for(i in a) print a[i],i }' 300 3----0 200 2----0 100 1,0
重新排序:
Shell > awk 'BEGIN{ SUBSEP="----" ; a["1,0"]=100 ; a[2,0]=200 ; a["3","0"]=300 ; for(i in a) print a[i],i | "sort" }' 100 1,0 200 2----0 300 3----0
统计字段出现的次数
Shell > cat c.txt A 192.168.1.1 HTTP B 192.168.1.2 HTTP B 192.168.1.2 MYSQL C 192.168.1.1 MYSQL C 192.168.1.1 MQ D 192.168.1.4 NGINX Shell > cat c.txt | awk 'BEGIN{SUBSEP="----"} {a[$1,$2]++} END{for(i in a) print a[i],i}' 1 A----192.168.1.1 2 B----192.168.1.2 2 C----192.168.1.1 1 D----192.168.1.4
内置函数¶
函数名 | 描述 |
---|---|
int(expr) | 截断为整数 |
sqrt(expr) | 平方根 |
rand() | 返回一个范围在 (0,1) 的随机数 N。结果并非每次运行都是随机数,而是保持不变。 |
srand([expr]) | 使用 “expr” 生成随机数。如果 “expr” 未指定,则默认使用当前时间作为种子,如果有种子,则使用生成的随机数。 |
asort(a,b) | 将数组 “a” 的元素进行排序(按字典序)并存入新数组 “b”,数组 “b” 的下标从 1 开始。此函数返回数组的元素个数。 |
asorti(a,b) | 对数组 “a” 的下标进行排序,并将排序后的下标作为元素存入新数组 “b”,数组 “b” 的下标从 1 开始。 |
sub(r,s[,t]) | 使用 “r” 正则表达式匹配输入记录,并将匹配结果替换为 “s”。“t” 是可选的,表示替换某个字段。函数返回替换次数 - 0 或 1。类似于 sed s// |
gsub(r,s[,t]) | 全局替换。“t” 是可选的,表示替换某个字段。如果忽略 “t”,则表示全局替换。类似于 sed s///g |
gensub(r,s,h[,t]) | “r” 正则表达式匹配输入记录,并将匹配结果替换为 “s”。“t” 是可选的,表示替换某个字段。“h” 表示替换指定索引位置 |
index(s,t) | 返回字符串 “t” 在字符串 “s” 中的索引位置(字符串下标从 1 开始)。如果函数返回 0,则表示不存在 |
length([s]) | 返回 “s” 的长度 |
match(s,r[,a]) | 测试字符串 “s” 是否包含字符串 “r”。如果包含,返回 “r” 在其中的索引位置(字符串下标从 1 开始)。如果不包含,返回 0 |
split(s,a[,r[,seps]]) | 根据分隔符 “seps” 将字符串 “s” 分割到数组 “a” 中。数组下标从 1 开始。 |
substr(s,i[,n]) | 截取字符串。“s” 表示要处理的字符串;“i” 表示字符串的索引位置;“n” 是长度。如果不指定 “n”,则表示截取剩余所有部分 |
tolower(str) | 将所有字符串转换为小写 |
toupper(str) | 将所有字符串转换为大写 |
systime() | 当前时间戳 |
strftime([format[,timestamp[,utc-flag]]]) | 格式化输出时间。将时间戳转换为字符串 |
-
int 函数
Shell > echo -e "qwer123\n123\nabc\n123abc123\n100.55\n-155.27" qwer123 123 abc 123abc123 100.55 -155.27 Shell > echo -e "qwer123\n123\nabc\n123abc123\n100.55\n-155.27" | awk '{print int($1)}' 0 123 0 123 100 -155
正如你所见,int 函数只对数字有效,遇到字符串时,会将其转换为 0。遇到以数字开头的字符串时,会将其截断。
-
sqrt 函数
Shell > awk 'BEGIN{print sqrt(9)}' 3
-
rand 函数和 srand 函数
rand 函数的使用示例如下:
Shell > awk 'BEGIN{print rand()}' 0.924046 Shell > awk 'BEGIN{print rand()}' 0.924046 Shell > awk 'BEGIN{print rand()}' 0.924046
srand 函数的使用示例如下:
Shell > awk 'BEGIN{srand() ; print rand()}' 0.975495 Shell > awk 'BEGIN{srand() ; print rand()}' 0.99187 Shell > awk 'BEGIN{srand() ; print rand()}' 0.069002
生成一个范围在 (0,100) 的整数
Shell > awk 'BEGIN{srand() ; print int(rand()*100)}' 56 Shell > awk 'BEGIN{srand() ; print int(rand()*100)}' 33 Shell > awk 'BEGIN{srand() ; print int(rand()*100)}' 42
-
asort 函数和 asorti 函数
Shell > cat /etc/passwd | awk -F ":" '{a[NR]=$1} END{anu=asort(a,b) ; for(i=1;i<=anu;i++) print i,b[i]}' 1 adm 2 bin 3 chrony 4 daemon 5 dbus 6 ftp 7 games 8 halt 9 lp 10 mail 11 nobody 12 operator 13 polkitd 14 redis 15 root 16 shutdown 17 sshd 18 sssd 19 sync 20 systemd-coredump 21 systemd-resolve 22 tss 23 unbound Shell > awk 'BEGIN{a[1]=1000 ; a[2]=200 ; a[3]=30 ; a[4]="admin" ; a[5]="Admin" ; \ a[6]="12string" ; a[7]=-1 ; a[8]=-10 ; a[9]=-20 ; a[10]=-21 ;nu=asort(a,b) ; for(i=1;i<=nu;i++) print i,b[i]}' 1 -21 2 -20 3 -10 4 -1 5 30 6 200 7 1000 8 12string 9 Admin 10 admin
信息
排序规则
- 数字的优先级高于字符串,并按升序排列。
- 字符串按升序字典序排列
如果你使用的是 asorti 函数,示例如下:
Shell > awk 'BEGIN{ a[-11]=1000 ; a[-2]=200 ; a[-10]=30 ; a[-21]="admin" ; a[41]="Admin" ; \ a[30]="12string" ; a["root"]="rootstr" ; a["Root"]="r1" ; nu=asorti(a,b) ; for(i in b) print i,b[i] }' 1 -10 2 -11 3 -2 4 -21 5 30 6 41 7 Root 8 root
信息
排序规则
- 数字的优先级高于字符串
- 遇到负数时,先比较从左数第一个数字,如果相同,则比较第二个数字,以此类推
- 遇到正数时,会按升序排列
- 字符串按升序字典序排列
-
sub 函数和 gsub 函数
Shell > cat /etc/services | awk '/netbios/ {sub(/tcp/,"test") ; print $0 }' netbios-ns 137/test # NETBIOS Name Service netbios-ns 137/udp netbios-dgm 138/test # NETBIOS Datagram Service netbios-dgm 138/udp netbios-ssn 139/test # NETBIOS session service netbios-ssn 139/udp Shell > cat /etc/services | awk '/^ftp/ && /21\/tcp/ {print $0}' ftp 21/tcp ↑ ↑ Shell > cat /etc/services | awk 'BEGIN{OFS="\t"} /^ftp/ && /21\/tcp/ {gsub(/p/,"P",$2) ; print $0}' ftp 21/tcP ↑ Shell > cat /etc/services | awk 'BEGIN{OFS="\t"} /^ftp/ && /21\/tcp/ {gsub(/p/,"P") ; print $0}' ftP 21/tcP ↑ ↑
就像
sed
命令一样,你也可以使用 "&" 符号引用已匹配的字符串。Shell > vim /tmp/tmp-file1.txt A 192.168.1.1 HTTP B 192.168.1.2 HTTP B 192.168.1.2 MYSQL C 192.168.1.1 MYSQL C 192.168.1.1 MQ D 192.168.1.4 NGINX # Add a line of text before the second line Shell > cat /tmp/tmp-file1.txt | awk 'NR==2 {gsub(/.*/,"add a line\n&")} {print $0}' A 192.168.1.1 HTTP add a line B 192.168.1.2 HTTP B 192.168.1.2 MYSQL C 192.168.1.1 MYSQL C 192.168.1.1 MQ D 192.168.1.4 NGINX # Add a string after the IP address in the second line Shell > cat /tmp/tmp-file1.txt | awk 'NR==2 {gsub(/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/,"&\tSTRING")} {print $0}' A 192.168.1.1 HTTP B 192.168.1.2 STRING HTTP B 192.168.1.2 MYSQL C 192.168.1.1 MYSQL C 192.168.1.1 MQ D 192.168.1.4 NGINX
-
index 函数
Shell > tail -n 5 /etc/services axio-disc 35100/udp # Axiomatic discovery protocol pmwebapi 44323/tcp # Performance Co-Pilot client HTTP API cloudcheck-ping 45514/udp # ASSIA CloudCheck WiFi Management keepalive cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System spremotetablet 46998/tcp # Capture handwritten signatures Shell > tail -n 5 /etc/services | awk '{print index($2,"tcp")}' 0 7 0 7 7
-
length 函数
# The length of the output field Shell > tail -n 5 /etc/services | awk '{print length($1)}' 9 8 15 10 14 # The length of the output array Shell > cat /etc/passwd | awk -F ":" 'a[NR]=$1 END{print length(a)}' 22
-
match 函数
Shell > echo -e "1592abc144qszd\n144bc\nbn" 1592abc144qszd 144bc bn Shell > echo -e "1592abc144qszd\n144bc\nbn" | awk '{print match($1,144)}' 8 1 0
-
split 函数
Shell > echo "365%tmp%dir%number" | awk '{split($1,a1,"%") ; for(i in a1) print i,a1[i]}' 1 365 2 tmp 3 dir 4 number
-
substr 函数
Shell > head -n 5 /etc/passwd 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 # I need this part of the content - "emon:/sbin:/sbin/nologin" Shell > head -n 5 /etc/passwd | awk '/daemon/ {print substr($0,16)}' emon:/sbin:/sbin/nologin Shell > tail -n 5 /etc/services axio-disc 35100/udp # Axiomatic discovery protocol pmwebapi 44323/tcp # Performance Co-Pilot client HTTP API cloudcheck-ping 45514/udp # ASSIA CloudCheck WiFi Management keepalive cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System spremotetablet 46998/tcp # Capture handwritten signatures # I need this part of the content - "tablet" Shell > tail -n 5 /etc/services | awk '/^sp/ {print substr($1,9)}' tablet
-
tolower 函数和 toupper 函数
Shell > echo -e "AbcD123\nqweR" | awk '{print tolower($0)}' abcd123 qwer Shell > tail -n 5 /etc/services | awk '{print toupper($0)}' AXIO-DISC 35100/UDP # AXIOMATIC DISCOVERY PROTOCOL PMWEBAPI 44323/TCP # PERFORMANCE CO-PILOT CLIENT HTTP API CLOUDCHECK-PING 45514/UDP # ASSIA CLOUDCHECK WIFI MANAGEMENT KEEPALIVE CLOUDCHECK 45514/TCP # ASSIA CLOUDCHECK WIFI MANAGEMENT SYSTEM SPREMOTETABLET 46998/TCP # CAPTURE HANDWRITTEN SIGNATURES
-
处理时间日期的函数
什么是 UNIX 时间戳? 根据 GNU/Linux 的发展历史,UNIX V1 诞生于 1971 年,同年 11 月 3 日,《UNIX Programmer's Manual》出版,其中定义 1970-01-01 为 UNIX 的起始基准日期。
时间戳与自然日期时间(天)的转换
Shell > echo "$(( $(date --date="2024/01/06" +%s)/86400 + 1 ))" 19728 Shell > date -d "1970-01-01 19728days" Sat Jan 6 00:00:00 CST 2024
时间戳与自然日期时间(秒)的转换
Shell > echo "$(date --date="2024/01/06 17:12:00" +%s)" 1704532320 Shell > echo "$(date --date='@1704532320')" Sat Jan 6 17:12:00 CST 2024
在
awk
程序中,自然日期时间与 UNIX 时间戳的转换Shell > awk 'BEGIN{print systime()}' 1704532597 Shell > echo "1704532597" | awk '{print strftime("%Y-%m-%d %H:%M:%S",$0)}' 2024-01-06 17:16:37
I/O 语句¶
语句 | 描述 |
---|---|
getline | 读取下一个匹配的行记录,并赋值给 "$0"。返回值是 1:表示已读取相关的行记录。返回值是 0:表示已读取到最后一行。返回值是负数:表示遇到错误。 |
getline var | 读取下一个匹配的行记录,并赋值给变量 “var” |
command | getline [var] | 将结果赋值给 "$0" 或变量 "var" |
next | 停止处理当前输入记录,并执行后续操作 |
打印结果 | |
printf | 参见本文档中该命令的部分 |
system(cmd-line) | 执行命令并返回状态码。0 表示命令执行成功;非 0 表示执行失败 |
print ... >> file | 输出重定向 |
print ... | command | 打印输出,并将其作为命令的输入 |
-
getline
Shell > seq 1 10 | awk '/3/ || /6/ {getline ; print $0}' 4 7 Shell > seq 1 10 | awk '/3/ || /6/ {print $0 ; getline ; print $0}' 3 4 6 7
利用前面学到的函数和 "&" 符号,我们可以
Shell > tail -n 5 /etc/services | awk '/45514\/tcp/ {getline ; gsub(/.*/ , "&\tSTRING1") ; print $0}' spremotetablet 46998/tcp # Capture handwritten signatures STRING1 Shell > tail -n 5 /etc/services | awk '/45514\/tcp/ {print $0 ; getline; gsub(/.*/,"&\tSTRING2") } {print $0}' axio-disc 35100/udp # Axiomatic discovery protocol pmwebapi 44323/tcp # Performance Co-Pilot client HTTP API cloudcheck-ping 45514/udp # ASSIA CloudCheck WiFi Management keepalive cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System spremotetablet 46998/tcp # Capture handwritten signatures STRING2
打印偶数行和奇数行
Shell > tail -n 10 /etc/services | cat -n | awk '{ if( (getline) <= 1) print $0}' 2 ka-kdp 31016/udp # Kollective Agent Kollective Delivery 4 edi_service 34567/udp # dhanalakshmi.org EDI Service 6 axio-disc 35100/udp # Axiomatic discovery protocol 8 cloudcheck-ping 45514/udp # ASSIA CloudCheck WiFi Management keepalive 10 spremotetablet 46998/tcp # Capture handwritten signatures Shell > tail -n 10 /etc/services | cat -n | awk '{if(NR==1) print $0} { if(NR%2==0) {if(getline > 0) print $0} }' 1 aigairserver 21221/tcp # Services for Air Server 3 ka-sddp 31016/tcp # Kollective Agent Secure Distributed Delivery 5 axio-disc 35100/tcp # Axiomatic discovery protocol 7 pmwebapi 44323/tcp # Performance Co-Pilot client HTTP API 9 cloudcheck 45514/tcp # ASSIA CloudCheck WiFi Management System
-
getline var
将 b 文件的每一行添加到 c 文件的每一行后面
Shell > cat /tmp/b.txt b1 b2 b3 b4 b5 b6 Shell > cat /tmp/c.txt A 192.168.1.1 HTTP B 192.168.1.2 HTTP B 192.168.1.2 MYSQL C 192.168.1.1 MYSQL C 192.168.1.1 MQ D 192.168.1.4 NGINX Shell > awk '{getline var1 <"/tmp/b.txt" ; print $0 , var1}' /tmp/c.txt A 192.168.1.1 HTTP b1 B 192.168.1.2 HTTP b2 B 192.168.1.2 MYSQL b3 C 192.168.1.1 MYSQL b4 C 192.168.1.1 MQ b5 D 192.168.1.4 NGINX b6
将 c 文件的指定字段替换为 b 文件的内容行
Shell > awk '{ getline var2 < "/tmp/b.txt" ; gsub($2 , var2 , $2) ; print $0 }' /tmp/c.txt A b1 HTTP B b2 HTTP B b3 MYSQL C b4 MYSQL C b5 MQ D b6 NGINX
-
command | getline [var]
Shell > awk 'BEGIN{ "date +%Y%m%d" | getline datenow ; print datenow}' 20240107
技巧
使用双引号包含 Shell 命令。
-
next
前面介绍了 break 语句和 continue 语句,前者用于终止循环,后者用于跳出当前循环。见 这里。对于 next,当条件满足时,它将停止匹配条件的输入记录,并继续执行后续操作。
Shell > seq 1 5 | awk '{if(NR==3) {next} print $0}' 1 2 4 5 # equivalent to Shell > seq 1 5 | awk '{if($1!=3) print $0}'
跳过符合条件的行记录
Shell > cat /etc/passwd | awk -F ":" 'NR>5 {next} {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 # equivalent to Shell > cat /etc/passwd | awk -F ":" 'NR>=1 && NR<=5 {print $0}'
技巧
"next" 不能在 "BEGIN{}" 和 "END{}" 中使用。
-
system 函数
你可以使用该函数调用 Shell 中的命令,例如:
Shell > awk 'BEGIN{ system("echo nginx http") }' nginx http
技巧
请注意,在使用 system 函数时要加上双引号。如果不加,
awk
程序会将其视为awk
程序的变量。Shell > awk 'BEGIN{ cmd1="date +%Y" ; system(cmd1)}' 2024
如果 Shell 命令本身包含双引号怎么办? 使用转义字符 - "\",例如:
Shell > egrep "^root|^nobody" /etc/passwd Shell > awk 'BEGIN{ system("egrep \"^root|^nobody\" /etc/passwd") }' root:x:0:0:root:/root:/bin/bash nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
另一个示例
Shell > awk 'BEGIN{ if ( system("xmind &> /dev/null") == 0 ) print "True"; else print "False" }' False
-
将
awk
程序的输出写入文件Shell > head -n 5 /etc/passwd | awk -F ":" 'BEGIN{OFS="\t"} {print $1,$2 > "/tmp/user.txt"}' Shell > cat /tmp/user.txt root x bin x daemon x adm x lp x
技巧
">" 表示以覆盖的方式写入文件。如果你想以追加的方式写入文件,请使用 ">>"。再次提醒,你应该使用双引号来包含文件路径。
-
管道符
-
自定义函数
语法 -
function NAME(parameter list) { function body }
。例如:Shell > awk 'function mysum(a,b) {return a+b} BEGIN{print mysum(1,6)}' 7
结语¶
如果你具备专业的编程语言技能,awk
会相对容易上手。但是,对于大多数编程语言基础薄弱的系统管理员(包括作者在内),awk
可能会非常复杂。有关未涵盖的信息,请参考 此处。
再次感谢阅读。
作者:李天赐