Linux awk 命令入门教程

printf 格式化输出

awk 的格式化输出,和 C 语言的 printf 没什么区别。让我们来看一下以下的格式化输出示例:

1
2
3
4
5
6
7
8
9
10
输入:echo 123.4567 | awk '{printf "%.3f\n", $1}'
输出:123.457

# 不满8字符向左填充空格,并且只保留一位小数
输入:echo 123.4567 | awk '{printf "%8.1f\n", $1}'
输出: 123.5

# 第一个元素不满20字符向右填充空格,并且保留七位小数;第二个元素以整形输出
输入:echo 123.4567 55.2 | awk '{printf "%-20.7f %d\n" , $1 , $2}'
输出:123.4567000 55

格式控制的字符列表说明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
`c'    This prints a number as an ASCII character. Thus, `printf "%c", 65' outputs the letter `A'. 
The output for a string value is the first character of the string.

`d' This prints a decimal integer.

`i' This also prints a decimal integer.

`e' This prints a number in scientific (exponential) notation.
For example, printf "%4.3e", 1950 prints `1.950e+03',
with a total of four significant figures of which three follow the decimal point.
The `4.3' are modifiers, discussed below.

`f' This prints a number in floating point notation.

`g' This prints a number in either scientific notation or floating point notation, whichever uses fewer characters.

`o' This prints an unsigned octal integer.

`s' This prints a string.

`x' This prints an unsigned hexadecimal integer.

`X' This prints an unsigned hexadecimal integer.
However, for the values 10 through 15, it uses the letters `A' through `F' instead of `a' through `f'.

`%' This isn't really a format-control letter, but it does have a meaning when used after a `%': the sequence `%%' outputs one `%'.
It does not consume an argument.

BEGIN/END

在读取第一个输入记录之前,BEGIN 规则仅执行一次。同样,在读取所有输入之后,END 规则仅执行一次。例如:

1
2
3
4
5
6
7
8
9
10
# awk -F":" 'BEGIN{print "Show all users:"} {print $1} END{print "====> Successful!"}' /etc/passwd
Show all users:
root
daemon
ftp
network
nobody
dnsmasq
stubby
====> Successful!

内置变量

FS 变量

FS 变量用于指定输入字段的分隔符。

awk 默认以空白字符(空格或 tab)来解析从输入读取的每一行,并设置变量 $1,$2 等,FS 变量用于设置字段分隔符,它可以设置为任何单个字符或正则表达式。我们可以通过以下两种方式来使用 FS 变量:

  1. 命令行使用 -F 选项;
  2. 直接设置 FS 变量;
1
2
3
4
5
6
7
Syntax:

awk -F'FS' 'commands' inputfilename

(or)

awk 'BEGIN{FS="FS";}'

这是一个 awk FS 示例,以 “:” 作为分隔符处理 /etc/passwd 文件。为了易读,这里使用了 awk 脚本的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cat passwd.awk
# 处理前
BEGIN{
FS=":";
print "Name\tUserID\tGroupID\tHomeDirectory";
}
# 处理中
{
print $1"\t"$3"\t"$4"\t"$6;
}
# 处理后
END {
print NR,"Records Processed";
}
1
2
3
4
5
6
7
8
9
10
# awk -f passwd.awk /etc/passwd
Name UserID GroupID Home
root 0 0 /root
daemon 1 1 /var
ftp 55 55 /home/ftp
network 101 101 /var
nobody 65534 65534 /var
dnsmasq 453 453 /var/run/dnsmasq
stubby 410 410 /var/run/stubby
7 Records Processed

OFS 变量

OFS 变量用于指定输出字段的分隔符。

OFS 是 FS 变量的输出等价物,默认值是空格。下面是使用 OFS 缺省值的示例:

1
2
3
4
5
6
7
8
# awk -F':' '{print $3,$4;}' /etc/passwd
0 0
1 1
55 55
101 101
65534 65534
453 453
410 410

print 语句中的 “,” 表示两个参数使用空格连接起来,也就是 OFS 的默认值。OFS 的值将被插入到输出字段间,示例如下:

1
2
3
4
5
6
7
8
# awk -F':' 'BEGIN{OFS="=";} {print $3,$4;}' /etc/passwd
0=0
1=1
55=55
101=101
65534=65534
453=453
410=410

RS 变量

RS 变量用于指定输入字段的记录分隔符。

RS 定义了行,awk 缺省情况是以换行符为分隔符一行一行读取的,设置 RS 变量可以让 awk 按照 RS 的分隔符读取。

让我们把软件包信息存到一个文件,每个软件信息以一个空行作为分割,每个字段以换行符分隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# cat opkg.info 
Package: iwinfo
Version: 2018-07-24-94b1366d-2
Depends: libc, libiwinfo
Status: install user installed
Architecture: mips_24kc
Installed-Time: 1553831708

Package: luci-mod-rpc
Version: git-18.196.56128-9112198-1
Depends: libc, luci-lib-json
Status: install user installed
Architecture: all
Installed-Time: 1553831708

Package: ubus
Version: 2018-01-16-5bae22eb-1
Depends: libc, libubus, libblobmsg-json, ubusd
Status: install ok installed
Architecture: mips_24kc
Installed-Time: 1553831711
Auto-Installed: yes

以下是使用 RS 变量打印软件包名称的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
# cat opkg.awk 
BEGIN {
RS="";
FS=" ";
}
{
print $2;
}

# awk -f opkg.awk opkg.info
iwinfo
luci-mod-rpc
ubus

在 opkg.awk 脚本中,RS 被赋值为空,它将匹配到空行,因此把每个软件包详细信息作为单个记录读取,并且记录中的每一行都是一个字段,而分隔字段使用 FS 变量,为了提取软件包名称,这里使用了空格(” “)作为 FS 的分隔符。

ORS 变量

ORS 变量用于指定输出字段的记录分隔符。

ORS 是 RS 变量的输出等价物,表示输出中的每条记录都将用这个分隔符打印。下面是使用 ORS 的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# awk 'BEGIN{ORS="=>"; RS=""} {print;}' opkg.info 
Package: iwinfo
Version: 2018-07-24-94b1366d-2
Depends: libc, libiwinfo
Status: install user installed
Architecture: mips_24kc
Installed-Time: 1553831708
=>Package: luci-mod-rpc
Version: git-18.196.56128-9112198-1
Depends: libc, luci-lib-json
Status: install user installed
Architecture: all
Installed-Time: 1553831708=>Package: ubus
Version: 2018-01-16-5bae22eb-1
Depends: libc, libubus, libblobmsg-json, ubusd
Status: install ok installed
Architecture: mips_24kc
Installed-Time: 1553831711
Auto-Installed: yes=>

上面的 awk 表达式中,以每个软件包信息作为一条记录,并且记录的输出用”=>”分隔。

NR 变量

NR 变量表示当前的记录数。

注意,不一定等于行数,如果设置了 RS 或 ORS,不再是按行读取,而是按 RS 或 ORS 分隔符读取,可以是单行,也可以是多行。

NR 表示已经读出的记录数或者行号,从 1 开始,如果有多个文件话,这个值也是不断累加中。

在下面的 NR 示例中,NR 变量记录当前的记录数,等同于行号,在 END 部分中,NR 告诉我们文件中的记录总数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# awk '{print "Processing Record - ",NR;}END {print NR, "Packages Records are processed";}' opkg.info 
Processing Record - 1
Processing Record - 2
Processing Record - 3
Processing Record - 4
Processing Record - 5
Processing Record - 6
Processing Record - 7
Processing Record - 8
Processing Record - 9
Processing Record - 10
Processing Record - 11
Processing Record - 12
Processing Record - 13
Processing Record - 14
Processing Record - 15
Processing Record - 16
Processing Record - 17
Processing Record - 18
Processing Record - 19
Processing Record - 20
Processing Record - 21
Processing Record - 22
Processing Record - 23
Processing Record - 24
Processing Record - 25
25 Packages Records are processed

NF 变量

NF 变量表示当前记录的字段总数,也就是列数。NF 对于验证记录中是否存在所有字段非常有用。

让我们来看一个学生成绩文件,其中有学生缺少一项成绩,如下所示。

1
2
3
4
5
6
cat student.txt 
Jones 78 84 77
Gondrol 56 58 45
RinRao 38 37
Edwin 78 67 45
Dayan 30 47

以下的 awk 脚本,打印记录(行)号和该记录中的字段数,很容易发现哪些学生的成绩缺少了。

1
2
3
4
5
6
# awk '{print NR,"->",NF}' student.txt 
1 -> 4
2 -> 4
3 -> 3
4 -> 4
5 -> 3

FILENAME 变量

FILENAME 变量表示当前输入的文件名。

以下的示例,在处理完后把当前的输入文件名打印出来:

1
2
# awk 'END{print "Current input file:", FILENAME}' student.txt 
Current input file: student.txt

FNR 变量

FNR 变量表示相对于当前输入文件的记录数。

与 NR 不同的是,这个值会是各个文件自己的行号,而 NR 对于多个文件,NR 的值是不断累加的。

我们处理单个文件的情况较多,多文件处理相对用的少。

Expressions as Patterns

任何 awk 表达式都是合法的 awk 模式,如果表达式的值非零(对于整数)或者非空(对于字符串),则模式匹配。处理每条记录时,都会执行一次表达式。

Comparison expression

awk 支持比较表达式,比较运算符:==, !=, >, <, >=, <=。

以下示例通过比较表达式提取本机所有监听端口:

1
2
3
4
5
6
# netstat -nap | awk '$6 == "LISTEN" {print;}'
tcp 0 0 0.0.0.0:139 0.0.0.0:* LISTEN 1833/smbd
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1652/lighttpd
tcp 0 0 0.0.0.0:83 0.0.0.0:* LISTEN 1652/lighttpd
tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN 2858/dnsmasq
tcp 0 0 192.168.17.211:53 0.0.0.0:* LISTEN 2858/dnsmasq

Pattern match

awk 跟 grep 一样,支持模式匹配。模式匹配格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var ~ /regex/ <=> /regex/
var !~ /regex/ <=> !/regex/
```

把上面的条件表达式改成模式匹配,其等价于:

```
# netstat -nap | awk '$6 ~ /LISTEN/ {print;}'
tcp 0 0 0.0.0.0:139 0.0.0.0:* LISTEN 1833/smbd
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1652/lighttpd
tcp 0 0 0.0.0.0:83 0.0.0.0:* LISTEN 1652/lighttpd
tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN 2858/dnsmasq
tcp 0 0 192.168.17.211:53 0.0.0.0:* LISTEN 2858/dnsmasq

(or)

# netstat -nap | awk '/LISTEN/ {print;}'
tcp 0 0 0.0.0.0:139 0.0.0.0:* LISTEN 1833/smbd
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1652/lighttpd
tcp 0 0 0.0.0.0:83 0.0.0.0:* LISTEN 1652/lighttpd
tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN 2858/dnsmasq
tcp 0 0 192.168.17.211:53 0.0.0.0:* LISTEN 2858/dnsmasq

Example

opkg.info 文件记录了软件包信息,其中自动安装的软件包会包含 “Auto-Installed: yes” 字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# cat opkg.info 
Package: iwinfo
Version: 2018-07-24-94b1366d-2
Depends: libc, libiwinfo
Status: install user installed
Architecture: mips_24kc
Installed-Time: 1553831708

Package: luci-mod-rpc
Version: git-18.196.56128-9112198-1
Depends: libc, luci-lib-json
Status: install user installed
Architecture: all
Installed-Time: 1553831708

Package: ubus
Version: 2018-01-16-5bae22eb-1
Depends: libc, libubus, libblobmsg-json, ubusd
Status: install ok installed
Architecture: mips_24kc
Installed-Time: 1553831711
Auto-Installed: yes

下面的示例使用了模式匹配的取反规则来打印所有非自动安装的软件包名称:

1
2
# awk 'BEGIN{ORS=" ";RS=""};!/Auto-Installed/{print $2}' opkg.info 
iwinfo luci-mod-rpc

See also

https://www.gnu.org/software/gawk/manual/html_node/Expression-Patterns.html

评论