awk 命令
awk 简述
awk [选项] '程序' file ...
|
设置字段分隔字符,缺省为「空白字符」或制表字符 (Tab 键)。 |
|
将变量 var 设置成 value。 |
- 记录和字段
-
AWK 可以处理文本文件和串流 (管道),输入数据分为「记录」和「字段」。awk 一次对一条「记录」进行操作,直到结尾 (EOF)。
「记录」由「记录分隔字符RS
(Record Separator)」来分隔,缺省的RS
是换行字符,表示文本数据中的每一行都是一条记录,可以使用变量设置不同的RS
。
一行记录可由「字段分隔字符FS
(Field Separator)」分隔出不同字段,缺省的FS
是空格或制表字符 (Tab 键),可采用命令行选项-F
或变量设置不同的FS
。每个字段可由字段变量 ($
) 取得 $1 $2 $3 依此类推,$0 为全部内容,最后一个字段也可以使用变量$NF
。FS
(字段分隔字符) 设置为:
,将/etc/passwd
文件标示出字段如下:$grep ubuntu /etc/passwd ubuntu:x:1000:1000::/home/ubuntu:/bin/bash $1 :$2 :$4 :$6 :$7($NF) :$3 :$5 $0----------------------------------------
awk 程序 (program)
- 「程序 (program)」是由一系列模式动作对组写成:
-
'pattern {action}'
其中
pattern
(模式) 表示在数据中找出符合的「记录(行)」,可为逻辑判断式或正则表达式 (Regex),当省略pattern
时,表示匹配所有记录(行)。
而{action}
是在匹配记录时所运行的动作,由大括号{}
中的语句 (statement) 组成,{action}
也可省略,省略时缺省动作是打印该行的全部内容 (print $0
)。
语句 (statement) 可以解读为「指令」。一个动作 (action) 有多个指令 (语句) 时,要写在下一行或以分号 (;
) 分开,如下:awk -F: ' { print $1 print $6 }' /etc/passwd
awk -F: '{print $1; print $6}' /etc/passwd
另外在「控制语句」 (如if
) 中,如果有多个指令也需要由大括号{}
组成复合语句,如下:awk -F: ' { if ($3 >= 1000) { print $1 print $3 } }' /etc/passwd
awk 模式 (pattern)
- 模式 (pattern) 判断式
-
逻辑判断语法
x == y
相等
x != y
不相等
x > y
大于
x >= y
大于或等于
x < y
小于
x <= y
小于或等于
正则表达式语法
/Regex/
匹配 Regex 表达式,表达式必须以定界字符
/
包围。!/Regex/
否定匹配 Regex 表达式。
x ~ y
x 匹配 y 正则表达式。
x !~ y
x 不匹配 y 正则表达式。
AND OR 语法,适用逻辑判断式及正则表达式。
&&
AND 条件
||
OR 条件
以逻辑判断式,列出UID
大于或等于 1000 的帐户awk -F: '($3 >= 1000) {print $1, $6}' /etc/passwd
列出用户名及家目录为第 1 个字段及第 6 个字段
{print $1, $6}
,字段变量之间的逗号 (,
) 是打印字段分隔字符OFS
(Output field separator) 缺省为空白字符。如果是{print $1 $6}
并不会把字段分隔,可采用" "
字符串,在字符串中加入空格,如{print $1 " " $6}
。以控制语句,列出UID
大于或等于 1000 的帐户awk -F: '{if ($3 >= 1000) print $1 " " $6}' /etc/passwd
以正则表达式 (Regex) 匹配模式 (pattern),过滤具有家目录的帐户awk -F: '/\/home/ {print $1, $6}' /etc/passwd
将记录中整行的内容匹配模式
/\/home/
,正则表达式必须以定界字符/
包围,而字符/
必须以转义字符\
转译成\/
,内容匹配/home
才会打印。匹配 (~
) 两个运算,$6 ~ /\/home/
为字段 6 匹配/home
,本例的结果跟上面是一样,只是上面是不区分字段。awk -F: '$6 ~ /\/home/ {print $1, $6}' /etc/passwd
- 以模式指定记录范围
-
pattern1, pattern2
采用逗号 (
,
) 区分两个位置;位置范围由第一个位置pattern1
至 (,
) 第二个位置pattern2
, 第二个开始匹配是在第一个位置或之后,记录范围始终跨越至少一个记录 (行)。打印记录内容 2 至 3 的记录seq 5 | awk '$0==2, $0==3' seq 5 | awk '/2/, /3/'
可混合「逻辑判断式」及「正则表达式」seq 5 | awk '$0==2, /3/'
打印记录内容 3 至 2,由于第二个位置不合乎条件,将打印第一个位置记录 (3
) 至最后记录 (5
)seq 5 | awk '/3/, /2/'
打印记录内容 2 或 3 或 Yes 的记录echo Yes | awk '/2/ || /3/ || /Yes/'
在范围模式中,逗号 (,
) 是所有操作符中最低的优先权 (即;最后判断)。因此,范围模式与另一个运算结合起来:echo Yes | awk '/2/,/3/ || /Yes/'
程序的意图是(/1/,/2/) || /Yes/
,但 awk 将其解读为/1/, (/2/ || /Yes/)
,这无法改变或解决;范围模式不能与其他模式结合:$echo Yes | awk '(/1/,/2/) || /Yes/' awk: cmd. line:1: (/1/,/2/) || /Yes/ awk: cmd. line:1: ^ syntax error
采用「控制语句」来处理:echo Yes | awk '{if ( ($0>=2 && $0<=3) || ($0=="Yes") ) print}'
- 多条模式 (pattern)
-
在程序中可以有多条模式,合乎模式的以不同动作来处理。
awk -F: ' $1=="root" { print "The superuser is on line ", NR } $1=="www-data" { print "The user for the web server is on line ", NR } ' /etc/passwd
- BEGIN 和 END 特殊模式 (pattern)
-
BEGIN { 尚未读取前先运行的动作 } BEGINFILE { 文件尚未读取的动作 } { 读取时运行的动作 } ENDFILE { 文件读取后运行的动作 } END { 读取完成后运行的动作 }
当程序 (program) 只有
BEGIN
时,并不需要读取文件或管道输入。
在运行BEGIN
时并未打开及读取文件,BEGINFILE
为打开文件,尚未读取,再运行读取时运行的动作
,文件读取完成后运行ENDFILE
,所有文件处理完毕后最后运行END
。当传入多个文件时,处理文件按顺序 (先打开左边的文件) 把多个文件当成接续的 (一个) 文件,其行号
NR
为接续。awk ' BEGIN { print "BEGIN FILENAME is", FILENAME } BEGINFILE { print "BEGINFILE", FILENAME, NR } { if (LastFileName != FILENAME) { LastFileName = FILENAME print "Read file", FILENAME, NR } } ENDFILE { print "ENDFILE", FILENAME, NR } END { print "END FILENAME is", FILENAME } ' /etc/passwd /etc/group
BEGIN FILENAME is (1) BEGINFILE /etc/passwd 0 (2) Read file /etc/passwd 1 (3) ENDFILE /etc/passwd 27 (4) BEGINFILE /etc/group 27 Read file /etc/group 28 ENDFILE /etc/group 81 END FILENAME is /etc/group (5)
1 BEGIN
未打开及读取文件2 BEGINFILE
打开文件,尚未读取文件。3 运行「读取时的动作」读取文件。 4 ENDFILE
文件读取完成。5 END
所有文件处理完毕。
awk 变量
- 内置变量
-
ARGC
命令行输入参数数量
ARGV
命令行输入参数内容 (数组)
NR
(目前) 记录位置 (行数)。
NF
(目前) 记录中字段数。
FILENAME
文件名称。
FS
(输入的) 字段分隔字符 (Field Separator),缺省为「空白字符」或制表字符 (Tab 键)。
RS
(输入的) 记录分隔字符 (Record Separator),缺省为换行字符。
OFS
输出字段分隔字符 (Output Field Separator),默认值为空白字符。
ORS
输出记录分隔字符 (Output Record Separator),默认值为换行字符。
OFMT
输出数字格式 (Format for numeric output),默认值为
%.6g
。IGNORECASE
匹配或排序时不区分大小写,默认值为 1。
RSTART
字符串函数 match 专用,回传匹配的 (开始) 位置。
RLENGTH
字符串函数 match 专用,回传匹配的 (内容) 长度。
SUBSEP
(多主键) 数组分隔字符。
FPAT
匹配
FPAT
正规表示法,以匹配内容创建字段,缺省为[^[:space:]]+
(不为空格的多个字符)。
在程序中同时设置变量FS
、FPAT
,是以最后设置哪个变量来解析字段,未设置变量缺省是以FS
来分隔。CSV
是以逗号,
来区分字段,但字符串中也会有逗号的情况如1,"2,b",3
,这时只能用FPAT = "[^,]+|\"[^\"]+\""
来处理。上述的正规表示法的意思是-
匹配不为逗号
[^,]
多个 (+
) 字符 -
或
|
-
前后为双引号
\"
中间不为双引号[^\"]
多个 (+
) 字符
打印具有家目录的帐户及行数$awk -F: '/\/home/ {print $1,NR}' /etc/passwd syslog 21 ubuntu 24
以BEGIN
在读取前先设置FS
(输入) 字段分隔字符为:
,OFS
输出字段分隔字符为,
:awk 'BEGIN {FS=":"; OFS=","} /\/home/ {print $1,NR}' /etc/passwd
以命令行选项-v
设置内置变量awk -vFS=: -vOFS=, '/\/home/ {print $1,NR}' /etc/passwd
设置OFMT
输出数字格式示例awk 'BEGIN{OFMT="%.0f"; print 2.5, 3.5, 4.5, 5.5}'
2 4 4 6
awk 的浮点进位为银行进位法 (Banker’s Rounding) 可简记为「四舍六入五成双」,五成双,尾数「5」的前一位,若双数则为双数,若为单数则进位。
如格式化字符串为%.0f
,参数输入2.5, 3.5, 4.5, 5.5
则输出2, 4, 4, 6
。 -
- 自定变量
-
awk 自定变量可为任意类型,如数字、浮点、字符串或数组。变量名称不可跟函数名称相同,如
index = 1
(index 刚好为函数),由于变量区分大小写,可以改名为Index = 1
。变量内容指定为 16 进制数值,则内容值前置
0x
,如myVar = 0x10
(10 进制为 16),x
或数值a
至f
不区分为大小写可混用。
8 进制则为前置0
如myVar = 010
(10 进制为 8)。
num=123
awk -v n=$num 'BEGIN {print n}'
awk 控制语句
{action}
控制语句语法if (conditional)
then-body-statement
else if (conditional)
else-if-then-body-statement
else
else-body-statement
initialization-statement
while (conditional) {
body-statement
increment-statement
break
continue
}
for (initialization; condition; increment)
body-statement
break
continue
for (variable in array)
body-statement
break
continue
switch (expression) {
case value or regular_expression:
case-body-statement
break
default:
default-body-statement
break
}
next
exit [return_code]
nextfile
- next
-
处理下一笔记录,若不为
EOF
重启循环,回到「读取时运行的动作」程序起始位置 (不会运行next
后面的程序)。
awk 函数
print
内的逗号 ,
是打印 OFS
字段分隔字符,数字输出格式为 OFMT
(缺省为 %.6g
),运行 print
后会加印 ORS
输出记录分隔字符 (缺省为换行字符)。可设置内置变量 OFS
、OFMT
及 ORS
改变缺省行为。print $1 $2
并不会把字段分隔,可采用 " "
字符串,在字符串中加入空格,如 print $1 " " $2
。
print
无参数时为 print $0
,另外在 awk 程序 (program) 的 pattern {action}
,当省略 {action}
时缺省动作也是 print $0
。
printf
printf format [, argument-list]
format (格式) 为字符串,argument-list 是对应于 format 的参数表表。
% [parameter] [flags] [width] [.precision] type
parameter (参数) |
|
N$ |
打印第 N 个参数,如 |
flags (修饰字符) |
|
|
左对齐,不足宽度时在右边填满空格,如 |
|
用于数字;在正数前面增加一个 |
space |
用于数字;在正数前面增加一个空格 (space),主要是用于跟负数对齐。如 |
|
用于数字;不足宽度时在左边填满 |
|
用于数字;加上千位分隔符号;支持千位分隔表示的 由于
' (单引号) 需要转译,以 16 进制 \x27 避开。以下列示处理 ' (单引号) 的方式:
|
|
类型 (type) 为 |
width (宽度) |
|
「宽度」指定输出的最少字符数,通常用于固定宽度的输出。 「宽度」可以省略,或者当作为另一个参数传递时由星号 |
|
precision (精度) |
|
「精度」通常指定输出的最多字符数,在不同 type (类型) 其义意不同:
「精度」可以省略,或者当作为另一个参数传递时由星号 |
|
type (类型) |
|
|
字符,参数为数字如 |
|
整数,无条件舍去法。 |
|
以科学记号显示浮点数,小写 |
|
浮点数值格式化成定点 (fixed-point)。 |
|
通用浮点型,自动显示成「浮点 (定点) 类型」或「科学记号类型」,整数字数已经超过由「精度」指定的有效位数,则会显示成科学记号。小写 |
|
8 进制。 |
|
字符串。 |
|
16 进制, |
|
输出一个 |
|
Alert (警报) 哔声 (beep), Ctrl+g,ASCII code 7 (BEL). |
|
倒退键 Ctrl+h,ASCII code 8 (BS). |
|
制表字符,Tab、Ctrl+i,ASCII code 9 (HT). |
|
换行字符,Ctrl+j,ASCII code 10 (LF). |
|
垂直制表字符,Ctrl+k,ASCII code 11 (VT). |
|
换页字符,Ctrl+l,ASCII code 12 (FF). |
|
确认键,Ctrl+m,ASCII code 13 (CR). |
|
8 进制值 nnn,其中 nnn 为 |
|
16 进制数值 hh( |
|
输出一个 |
|
在字符串中输出 |
数学函数
|
x 除以 y 的余数。 |
|
x 的 y 次方 |
|
正弦 |
|
余弦 |
|
反正切 |
|
指数 |
|
对数 |
|
开根号 |
|
整数值 (无条件舍去小数) |
|
初始化乱数 |
|
回传乱数 n,0 <= n < 1。 |
比特操作函数
|
|
回传比特运算 |
|
|
|
回传 val 比特运算的补数 (complement),将 |
|
|
|
回传 val 以比特 (bit) 左移 count 位,(左边) 高位抛弃,(右边) 低位补 0。 |
|
|
|
回传比特运算 |
|
|
|
回传 val 以比特 (bit) 右移 count 位,(左边) 高位补 0,(右边) 低位抛弃。 |
|
|
|
回传比特运算 |
字符串函数
|
|
将 source 的「内容值」排序。排序后的数组主键由整数 1 开始,数组内容为「内容值」。如果有指定 dest 则排序结果会保存在 dest (source 不变)。内置变量 |
|
|
|
将 source 的「主键值」排序,排序后的数组主键由整数 1 开始,数组内容为「主键值」。如果有指定 dest 则排序结果会保存在 dest (source 不变)。内置变量 |
|
|
|
将输入的 source 匹配 regexp 替换为 replacement 回传替换后的文本。
未指定 source 参数其来源为字段 $0,how 设置为数字 n 表示第 n 次开始替换,若为 |
|
|
|
回传字符串 find 在 in 的位置 (不支持正规表示法)。 |
|
|
|
回传字符串长度,如果没有 string 参数则回传 |
|
|
|
取得 string 匹配 regexp 的位置和长度,将起始位置填入内置变量 array 为正规表示法中分组功能的回调
|
|
|
|
字符串 string 匹配 fieldpat 模式,将合乎模式匹配的文本创建数组 array,如省略 fieldpat 缺省为内置变量 |
|
|
|
字符串 string 分割后 (的前后) 创建数组 array,如省略 fieldsep 缺省为内置变量 |
|
|
|
回传(不打印)printf (使用相同参数) 输出的字符串。 |
|
|
|
将 str 转成数字 (含小数),若 str 前置 |
|
|
|
将 target 匹配 regexp 替换一次成为 replacement,替换后的文本取代 target。没有指定 target 参数时缺省为字段变量 |
|
|
|
gsub 跟 sub 类似,sub 只替换一次,gsub 为全部替换。 |
|
|
|
回传字符串 string 起始位置 start 长度 length,没有指定 length 则至 string 的结尾。 |
|
|
|
转成小写 |
|
|
|
转成大写 |
输入输出函数
|
|
输入的文件或管道,在经过读取后,其读取的位置会增加,如果不关闭输入,则后续的读取是接续上次的位置。 在双向管道 (two-way pipe) 或称为 coprocess (协助) 可以指定 how 参数, 若 |
|
|
|
将输出文件或管道的缓冲区排清 (flush) 确定已写入。 |
|
|
|
运行操作系统的命令 command 回传命令的状态码。 |
|
|
|
读取下一笔记录保存至 target 并回传读取状态,没有指定 target 时将保存至 运行
getline 错误时并不会出错,而是回传状态:
采用
|
getent hosts 8.8.8.8
8.8.8.8 dns.google
getline
取得 IP 位置的名称echo -e '8.8.8.8\n8.8.4.4' | awk '
{
"getent hosts " $0 | getline Hosts;
split(Hosts, Hosts_Arr, " ");
print $0, Hosts_Arr[2]
}'
8.8.8.8 dns.google 8.8.4.4 dns.google
awk -F: '
{
User = $1
UID = $3
while ( (getline < "/etc/group") > 0 )
{
if (User == $1) {
print $1, UID, $3
break
}
}
close("/etc/group") (1)
}' /etc/passwd
1 | 关闭文件后,第二次才能从头开始读取。 |
注:本例是为了说明 close
,并未优化,在 BEGIN{}
时先将 /etc/group
全部读取至数组再以数组查表,比较理想。
- print printf 重定向输出
-
print
及printf
的管道输出字符其运作跟管道一样。print > "filename" print >> "filename" print | "command" print |& "command_coprocess"
- 将输出文件错误视为非严重错误
-
当
PROCINFO["NONFATAL"]
或者PROCINFO["filename", "NONFATAL"]
存在时 (不管项目设置值,设为 0 也一样),则print
重定向至文件的 I/O 错误将变为非严重错误 (注:awk 5.0 才会作用)。awk ' BEGIN { PROCINFO["/no/such/file", "NONFATAL"] = 0 (1) ERRNO = 0 print "hi" > "/no/such/file" if (ERRNO) { print("Output failed:", ERRNO) > "/dev/stderr" exit 1 } }'
1 设置为 0 是在解释该项目存在时即有作用,实际上不需要设置;或者设置为 1。 Output failed: No such file or directory
- 双向管道
-
将管道的输入及输出;由
print
发送给命令的「管道输入」;由getline
读取命令的「管道输出」。以|&
创建双向管道print |& command command |& getline x
|
跟|&
可能混淆以为行为一样,| command
是由命令直接输出,而|& command
其命令是回传给 awk,命令并不会输出。依据命令的特性,如命令是以逐行来处理,则每发送一笔记录后运行
close(command, "to")
在管道中写入EOF
,避免要求读取所有数据后才会处理的「命令」发生阻塞 (永久等待)。
发送后运行关闭管道的输出close(command, "to")
,在运行getline
之后也需要运行close(command)
,这两个close
是成对的。{ print |& command close(command, "to") (1) if ( (command |& getline x) > 0 ) print x close(command) (2) }
1 close(command, "to")
在管道中写入EOF
。2 在处理后需要运行 close(command)
,这两个close
是成对的。如果命令是读取所有的输入后才处理 (或者是读取全部后才会正确),那么则分段;先发送所有记录后,再发送
EOF
,再以getline
(全部) 读取。{ print |& command (1) } END { close(command, "to") (2) while ( (command |& getline x) > 0) { print x } close(command) (3) }
1 先发送所有记录。 2 发送所有记录后,再发送 EOF
。3 实际上并不需要该指令,但加入该指定比较合乎 close
的用法。将用户帐户转成大写 (逐行处理)awk -F: ' BEGIN { command="awk \x27{ print toupper($0) }\x27" (1) } { print $1 |& command close(command, "to") if ( (command |& getline x) > 0 ) print x close(command) } ' /etc/passwd
1 command
即为awk '{ print toupper($0) }'
,将 ' (单引号) 以 16 进制\x27
避开转译。将用户帐户经过cat -n
加上行号,读取后再打印 (全部数据分段处理)。awk -F: ' BEGIN { command="cat -n" } { print $1 |& command } END { close(command, "to") while ( (command |& getline x) > 0 ) print x close(command) } ' /etc/passwd
awk '
BEGIN {
command = "echo hi"
print "getline return:" (command | getline)
print "close return:" close(command)
print "----------"
command = "sleep 1 && echo hi"
PROCINFO[command, "RETRY"] = 1
PROCINFO[command, "READ_TIMEOUT"] = 200
print "getline return:" (command | getline), "ERRNO:" ERRNO
print "close return:" close(command), "ERRNO:" ERRNO
print "close return:" close(command), "ERRNO:" ERRNO
}'
getline return:1 close return:0 ---------- getline return:-2 ERRNO:Connection timed out (1) close return:269 ERRNO: (2) close return:-1 ERRNO:close of redirection that was never opened
1 | 不一定是 -2,awk 4.1.4 为 -1。 |
2 | 不一定是 269 可能是其他非 0 的数字。 |
awk 关联数组
数组主键值为唯一,数组主键 (及内容值) 可为文本或数字,收集或统计字段的情况。
- 数组类型
-
-
array[x,y]
多主键数组
水果有多种颜色,水果及颜色为同一组数据时,可采用多主键数组。 -
array[x][y]
多维数组
水果有多种颜色,如要各别列出水果、颜色,处理不同的维度时,应采用多维数组。
多主键数组是保存在一维数组,其主键是以SUBSEP
(数组分隔字符) 来分隔,该字符即为 Ctrl+\。awk ' BEGIN { a[1,1] = 11; a[1,2] = 12; a[2,2] = 22; for (i in a) print i } ' | cat -v
2^\2 1^\1 1^\2
多维数组,在程序中可取得一个维度,其数量为该维度的个数。awk ' BEGIN { a[1][1] = 11; a[1][2] = 12; a[2][2] = 22; for (x in a) { print x for (y in a[x]) print "\t" y } } '
1 1 2 2 2
-
cat > sales.txt << EOF
sales,fruit,color,qty,amount
john,apple,red,1,100
john,banana,yellow,2,200
john,kiwi,green,3,300
kevin,lemon,yellow,1,100
marry,banana,yellow,2,200
marry,apple,green,3,300
EOF
awk -F, '
{
if (NR < 2) next
fruitAmount[$2] += $5
salesAmount[$1] += $5
}
END {
print "---------- Fruit amount"
for (fruit in fruitAmount) print fruit, fruitAmount[fruit]
print "---------- Sales amount"
for (sales in salesAmount) print sales, salesAmount[sales]
}
' sales.txt
-
if (NR < 2) next
目前行号 (NR
) 小于 2 时,运行下一行next
。 -
fruitAmount[$2] += $5
水果销售数组fruitAmount
其主键为水果字段$2
,内容为累加销售金额$5
。
写成fruitAmount[$2] = fruitAmount[$2] + $5
也相同。 -
for (fruit in fruitAmount)
由for
取得fruitAmount
的每个主键fruit
(水果字段)。 -
fruitAmount[fruit]
以主键fruit
取得fruitAmount
销售金额内容值。
---------- Fruit amount apple 400 banana 400 lemon 100 kiwi 300 ---------- Sales amount kevin 100 marry 500 john 600
- 预先定义的数组排序
-
数组排序
@[ind|val]_[num|type|str]_[asc|desc]
选项-
@unsorted
无排序。 -
ind
按主键(索引)排序,val
按内容值排序。 -
num
尝试以数字排序 (1a
会以1
排序),无前置数字在前。
str
按字符串 (文本) 顺序排序。
type
按类型排序,数字类型或类数字 (内容全部为数字) 在前,字符串 (文本) 类型在后 (1a
为字符串类型)。 -
asc
为升幂,desc
为降幂。
数组排序组合列表@ind_num_asc
@ind_num_desc
@ind_str_asc
@ind_str_desc
@val_num_asc
@val_num_desc
@val_str_asc
@val_str_desc
@val_type_asc
@val_type_desc
注:没有ind_type
的组合。使用方式PROCINFO["sorted_in"] = "@ind_str_asc" for (i in array) print i, array[i]
str
num
type
排序示例awk ' function sortdemo(format, arr) { PROCINFO["sorted_in"] = format (4) print format for (i in arr) { print typeof(arr[i]), arr[i] (5) } print "-------------" } BEGIN { split("31 5 red 1a", a) (1) a[100] = 400 (2) sortdemo("@val_str_asc", a) (3) sortdemo("@val_num_asc", a) sortdemo("@val_type_asc", a) } '
1 创建数组 a
内容值为31
、5
、red
、1a
。2 数组 a[100]
设置为数字 400。3 将排序方式 @val_str_asc
传递给sortdemo
的 format。4 sortdemo
设置排序方式 format。5 打印每个数组的数据类型及内容 (awk 4.2 才有支持 typeof
函数)。@val_str_asc string 1a strnum 31 number 400 strnum 5 string red ------------- @val_num_asc string red string 1a strnum 5 strnum 31 number 400 ------------- @val_type_asc strnum 5 strnum 31 number 400 string 1a string red -------------
-
awk -F, '
{
if (NR < 2) next
fruitQty[$2,$3] += $4
fruitAmount[$2,$3] += $5
}
END {
PROCINFO["sorted_in"] = "@ind_str_asc"
for (combKey in fruitQty) {
split(combKey, sepKey, SUBSEP)
print sepKey[1], sepKey[2], fruitQty[sepKey[1], sepKey[2]], fruitAmount[sepKey[1], sepKey[2]]
}
}
' sales.txt
-
for (combKey in fruitQty)
由for
取得fruitQty
的每个主键combKey
。 -
split(combKey, sepKey, SUBSEP)
由于 (原始的)fruitQty
为多主键,combKey
也是多主键,运行split
以SUBSEP
(数组分隔字符) 分割出每个主键保存至数组sepKey
中的sepKey[1]
、sepKey[2]
。 -
fruitQty[sepKey[1], sepKey[2]]
以主键sepKey[1]
、sepKey[2]
取得fruitQty
的内容值,fruitAmount
的主键跟fruitQty
相同,以相同的主键fruitAmount[sepKey[1], sepKey[2]]
取得fruitAmount
的内容值。
apple green 3 300 apple red 1 100 banana yellow 4 400 kiwi green 3 300 lemon yellow 1 100
asorti
示例)awk -F, '
{
if (NR < 2) next
fruitQty[$2,$3] += $4
fruitAmount[$2,$3] += $5
}
END {
asorti(fruitQty, sortKey)
for (i=1; i in sortKey; i++) {
split(sortKey[i], sepKey, SUBSEP)
print sepKey[1], sepKey[2], fruitQty[sepKey[1], sepKey[2]], fruitAmount[sepKey[1], sepKey[2]]
}
}
' sales.txt
运行结果跟前述的「水果销售明细结果」相同。
-
asorti(fruitQty, sortKey)
将fruitQty
的主键排序后保存至sortKey
其主键值由整数 1 开始。sortKey
数组如下:1 apple^\green 2 apple^\red 3 banana^\yellow 4 kiwi^\green 5 lemon^\yellow
-
for (i=1; i in sortKey; i++)
由for
取得每个整数主键i
。for
的 condition 为i in sortKey
改为i <= length(sortKey)
也相同。for (i=1; i <= length(sortKey); i++)
-
split(sortKey[i], sepKey, SUBSEP)
sortKey[i]
为 (已排序的) 多主键的内容,运行split
以SUBSEP
(数组分隔字符) 分割出每个主键保存至数组sepKey
中的sepKey[1]
、sepKey[2]
。 -
fruitQty[sepKey[1], sepKey[2]]
以主键sepKey[1]
、sepKey[2]
取得fruitQty
的内容值,fruitAmount
的主键跟fruitQty
相同,以相同的主键fruitAmount[sepKey[1], sepKey[2]]
取得fruitAmount
的内容值。
awk -F, '
{
if (NR < 2) next
fruitSales[$2][$3]["qty"] += $4
fruitSales[$2][$3]["amt"] += $5
}
END {
PROCINFO["sorted_in"] = "@ind_str_asc"
for (fruit in fruitSales) {
subqty = 0; subamt = 0;
for (color in fruitSales[fruit]) {
qty = fruitSales[fruit][color]["qty"]
amt = fruitSales[fruit][color]["amt"]
print fruit, color, qty, amt
subqty += qty; subamt += amt;
}
print "--------------------"
print fruit, "subtotal", subqty, subamt
print ""
}
}
' sales.txt
apple green 3 300 apple red 1 100 -------------------- apple subtotal 4 400 banana yellow 4 400 -------------------- banana subtotal 4 400 kiwi green 3 300 -------------------- kiwi subtotal 3 300 lemon yellow 1 100 -------------------- lemon subtotal 1 100
(
cat << EOF
fruit,color-list
apple,green,red
lemon,green,yellow
banana,yellow
kiwi,green,gold
grape,black,red,purple
EOF
) |
awk -F, '
{
if (NR < 2) next
for(i=2; i<=NF; i++)
array[$i][$1]
}
END {
PROCINFO["sorted_in"] = "@ind_str_asc"
for (color in array) {
print color, length(array[color])
for (fruit in array[color]) {
print "\t" fruit
}
}
}
'
-
for(i=2; i<=NF; i++)
由for
取得 i,该值跳过第 1 字段的水果,由 2 开始至该行的字段数NF
,i 值为颜色字段。 -
array[$i][$1]
收集多维数组array
第 1 维度的主键为颜色字段$i
,第 2 维度的主键为水果$1
。
注:只需收集主键值,不需要内容值。
black 1 grape gold 1 kiwi green 3 apple kiwi lemon purple 1 grape red 2 apple grape yellow 2 banana lemon