跳至内容

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

基本用法示例

  1. 从文件中读取 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%
    
  2. 指定分隔符

    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/
    
  3. 变量赋值

    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
    
  4. 将 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"
    

    稍后会介绍这些变量的含义。现在进行回顾,跳转至变量

  5. 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
    
  6. --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
    
  7. 通过正则表达式匹配行(记录)

    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
    ...
    
  8. 逻辑运算(逻辑与、逻辑或、非)

    逻辑与:&& 逻辑或:|| 非:!

    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
    ...
    
  9. 通过字符串定位连续的行并打印

    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"
  1. 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
    
  2. 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"
    
  3. 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
    
  4. 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
    
  5. 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
    
  6. 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
    
  7. 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
    
  8. 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)
    
  9. 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
  1. 感叹号

    打印奇数行

    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}”。

  2. 反转

    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
    ...
    
  3. 数学中的基本运算

    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
    ...
    
  4. 管道符

    你可以在 awk 程序中使用 bash 命令,例如:

    Shell > echo -e "6\n3\n9\n8" | awk '{print $0 | "sort"}'
    3
    6
    8
    9
    

    信息

    请注意!你必须使用双引号来包含命令。

  5. 正则表达式

    这里,我们涵盖了正则表达式的基本示例。你可以对行记录使用正则表达式。

    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
    ...
    

流程控制

  1. 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"}
    }'
    
  2. 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
    
  3. 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
    
  4. break 语句和 continue 语句

    两者之间的比较如下:

    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
    
  5. 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]]]) 格式化输出时间。将时间戳转换为字符串
  1. 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。遇到以数字开头的字符串时,会将其截断。

  2. sqrt 函数

    Shell > awk 'BEGIN{print sqrt(9)}'
    3
    
  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
    
  4. 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
    

    信息

    排序规则

    • 数字的优先级高于字符串
    • 遇到负数时,先比较从左数第一个数字,如果相同,则比较第二个数字,以此类推
    • 遇到正数时,会按升序排列
    • 字符串按升序字典序排列
  5. 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
    
  6. 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
    
  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
    
  8. match 函数

    Shell > echo -e "1592abc144qszd\n144bc\nbn"
    1592abc144qszd
    144bc
    bn
    
    Shell > echo -e "1592abc144qszd\n144bc\nbn" | awk '{print match($1,144)}'
    8
    1
    0
    
  9. 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
    
  10. 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
    
  11. 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
    
  12. 处理时间日期的函数

    什么是 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 停止处理当前输入记录,并执行后续操作
print 打印结果
printf 参见本文档中该命令的部分
system(cmd-line) 执行命令并返回状态码。0 表示命令执行成功;非 0 表示执行失败
print ... >> file 输出重定向
print ... | command 打印输出,并将其作为命令的输入
  1. 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
    
  2. 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
    
  3. command | getline [var]

    Shell > awk 'BEGIN{ "date +%Y%m%d" | getline datenow ; print datenow}'
    20240107
    

    技巧

    使用双引号包含 Shell 命令。

  4. 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{}" 中使用。

  5. 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
    
  6. 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
    

    技巧

    ">" 表示以覆盖的方式写入文件。如果你想以追加的方式写入文件,请使用 ">>"。再次提醒,你应该使用双引号来包含文件路径。

  7. 管道符

  8. 自定义函数

    语法 - function NAME(parameter list) { function body }。例如:

    Shell > awk 'function mysum(a,b) {return a+b} BEGIN{print mysum(1,6)}'
    7
    

结语

如果你具备专业的编程语言技能,awk 会相对容易上手。但是,对于大多数编程语言基础薄弱的系统管理员(包括作者在内),awk 可能会非常复杂。有关未涵盖的信息,请参考 此处

再次感谢阅读。

作者:李天赐