Linux Command - awk vars
Linux 下的 awk 命令中的变量。
FS/OFS/RS#
以下是控制格式相关的一些内置变量:
变量 | 描述 |
---|---|
FIELDWIDTHS | 由空格分隔的一列数字, 定义了每个数据字段确切宽度 |
RS | 输入记录分隔符,默认为换行符 |
FS | 输入字段分隔符,默认为空白字符 |
ORS | 输出记录分隔符,默认为换行符 |
OFS | 输出字段分隔符,默认为空格 |
以下打印 awk 默认的输入记录/字段分割符、输出记录/字段分割符:
$ awk 'BEGIN { printf "RS=\"%s\"\nFS=\"%s\"\nORS=\"%s\"\nOFS=\"%s\"\n", RS, FS, ORS, OFS }'
RS="
"
FS=" "
ORS="
"
OFS=" "
Linux 中 awk 后面的 RS, ORS, FS, OFS 用法
Explanation about awk command using ORS, NR, FS, RS
8 Powerful Awk Built-in Variables – FS, OFS, RS, ORS, NR, NF, FILENAME, FNR
FS#
在 awk-basic 中讲过,可在执行 awk 命令时通过 -F
选项来指定域分割符。
默认 FS
非顶格空格或制表符,也可以自行指定其他字符(串)作为域分割符。
一般在 BEGIN 中指定 FS,实际上 -F
和 FS
支持指定多个字符作为分割点。
例如
-F '[()]'
或FS='[()]'
表示以左括号和右括号作为分割符。
macOS 下通过 networksetup -listnetworkserviceorder
命令可查看网络服务接口:
$ networksetup -listnetworkserviceorder
An asterisk (*) denotes that a network service is disabled.
(1) Wi-Fi
(Hardware Port: Wi-Fi, Device: en0)
(2) Bluetooth PAN
(Hardware Port: Bluetooth PAN, Device: en3)
(3) Thunderbolt Bridge
(Hardware Port: Thunderbolt Bridge, Device: bridge0)
如果提取无线网卡(Wi-Fi)的接口名称(en0)呢?
- 定位起始边界标题行
(1) Wi-Fi
,读取下一行; - 括号内按逗号分割,读取第二部分
Device: en0
; - 对
Device: en0
提取后面的名称部分。
执行过程分析如下:
- 管传第1个awk:界定结构化内容的起始边界行(标题包含序号和接口名);
- 管传第2个awk:指定三个分割符
(
、,
、)
,进行多点切割; - 管传第3个awk:默认按空格分割,提取第二部分的设备名。
# networksetup -listnetworkserviceorder | awk '/\([[:digit:]]+\) Wi-Fi/{getline; print}' | awk -F '[(,)]' '{print $3}' | awk '{print $2}'
$ networksetup -listnetworkserviceorder | awk '/\([[:digit:]]+\) Wi-Fi/{getline; print}' | awk 'BEGIN {FS="[(,)]"} {print $3}' | awk '{print $NF}'
OFS#
变量 FS 和 OFS 定义了 awk 如何处理数据流中的数据字段。
- 变量
FS
用来定义记录处理时的字段分割符; - 变量
OFS
用于定义print
打印字段的分隔符。
默认情况下,awk 将 OFS
设成一个空格,打印各字段以空格分隔。
执行命令 print $1,$2,$3
,会看到输出 field1 field2 field3
。
可以通过 BEGIN 模块在 body 处理前预设 OFS:
FIELDWIDTH#
在一些应用程序中,数据并没有使用字段分隔符,而是被放置在了记录中的特定列。
这种情况下,必须设定 FIELDWIDTHS
变量来匹配数据在记录中的 位置,按照列宽来分割各个域。
一旦设置了 FIELDWIDTH 变量,awk 就会忽略 FS 变量。
RS#
变量 RS 和 ORS 定义了 awk 程序如何分割/分隔数据流中的记录。
默认情况下,awk 将 RS 和 ORS 设为换行符(\n
),即以行作为记录裁决单位。
更多的时候,你会在数据流中碰到占据多行的结构化信息。
典型的例子是包含地址和电话号码的数据,其中地址和电话号码各占一行。
如果用默认的 FS 和 RS 变量值来读取这组数据,awk 就会把每行作为一条单独的记录来读取,并将记录中的空格当作字段分隔符。
但符合实际预期的解读是,每四行组成的结构化区块为一条完整的记录,每条记录中的每一行对应一个字段域。
结构化的记录之间留一个空白行相间,可以将 RS
变量设置成空字符串,将 FS
设置为换行符(\n
)。
然后 awk 会把每个空白行当作一个记录分隔符,把文件中的每行当成一个字段。
data2 中有三条记录:
$ cat data2
Riley Mullen
123 Main Street
Chicago, IL 60601
(312)555-1234
Frank Williams
456 Oak Street
Indianapolis, IN 46201
(317)555-9876
Haley Snell
4231 Elm Street
Detroit, MI 48201
(313)555-4938
awk 提取姓名和电话号码打印输出:
$ awk 'BEGIN {RS=""; FS="\n"; OFS=": " } {print $1,$4}' data2
Riley Mullen: (312)555-1234
Frank Williams: (317)555-9876
Haley Snell: (313)555-4938
demo#
有时会遇到较长的单行文本,但包含固定的分隔符。
例1:按行打印输出 ifconfig -l
中的接口。
- 输入字段分割符FS默认为空白字符,然后在BEGIN中指定输出字段分隔符OFS为换行,BODY部分for循环打印各个字段,即实现了按行打印记录。
- 更简单的方案:指定记录分割符RS为空格,将接口列表按空格分割成多条记录,再按行打印记录(ORS默认即为换行):
例2:按行打印输出 PATH
中的环境变量(print -l $PATH
)。
-F
指定以:
作为字段分割符(FS),然后在BEGIN中指定输出字段分隔符OFS为换行,BODY部分for循环打印各个字段,即实现了按行打印记录。
- 更简单的方案:指定输入记录分隔符RS为
:
,将 PATH 环境变量按冒号分割成多条记录,再按行打印记录(ORS默认即为换行):
例3:按行打印输出用户身份 id 和所属用户组 groups。
# 默认动作print可省
groups | awk 'BEGIN {RS=" ";} {print}'
groups `whoami` | awk 'BEGIN {RS=" ";} {print}'
id | awk 'BEGIN {RS=" ";} {print}'
id `whoami` | awk 'BEGIN {RS=" ";} {print}'
例4:局域网内的某台服务器(raspi-ubuntu)由于采用了DHCP,如何查找它的IP地址,以便进行SSH连接呢?
假设我们知道设备wifi接口(wlan0)的MAC地址是以 dc:a6:32
开头,那么在 SSH 客户端,可以通过 arp 查询该 MAC 地址对应的 IP。
实际上,arp 命令输出的 ARP 报文格式是固定的,括号中即为对应设备分配占用的 IP 地址。
那么,如何提取这个IP地址呢?
可以基于awk先基于左括号进行切割,然后再移除右括号及其后的内容:
更简洁的思路是,基于左右括号两点切割,然后域 $2 即是中间的IP地址部分:
NF/NR/FNR#
当要在 awk 程序中跟踪数据字段和记录时,变量 FNR、NF 和 NR 用起来就非常方便。
变量 | 描述 |
---|---|
NF |
number of fields in the current record |
NR |
ordinal number of the current record |
FNR |
ordinal number of the current record in the current file |
NF#
有时并不知道记录中到底有多少个数据字段,NF 变量存储了记录中域(字段)的个数。
可以 $NF
形式引用记录中最后一个字段,RS 例程中的电话号码字段($4
)也可以 $NF
形式引用。
How to select only the first 10 rows in my AWK script
上节示例中,BODY中的for循环,采用NF作为索引上限,打印各个字段:
FNR#
FNR 和 NR 变量虽然类似,但又略有不同。
- FNR 变量记录了当前文件中已处理过的记录条数;
- NR 变量则记录着所有文件累计已处理过的记录条数。
由于默认 RS
="\n",每条记录为自然行,记录数 FNR
也即当前文件游标所在的行数(从1开始计数)。
在以下示例中,awk 指定输入了两个相同的文件,脚本打印第一个字段,并输出 FNR 变量值,追踪 FNR 的动态变化。
$ cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
# FNR-1:count from 0
$ awk 'BEGIN{FS=","} {print $1,"FNR="FNR}' data1 data1
data11 FNR=1
data21 FNR=2
data31 FNR=3
data11 FNR=1
data21 FNR=2
data31 FNR=3
注意:
- 直接引用 FNR 变量,不需要加
$
引用。 - 当 awk 程序处理第二个文件时,
FNR
值被设回了1。
NR#
现在,让我们加上 NR
变量看看会输出什么。
$ awk 'BEGIN{FS=","} {print $1,"FNR="FNR,"NR="NR}' data1 data1
data11 FNR=1 NR=1
data21 FNR=2 NR=2
data31 FNR=3 NR=3
data11 FNR=1 NR=4
data21 FNR=2 NR=5
data31 FNR=3 NR=6
FNR 变量在 awk 处理第二个文件时被重置了,而 NR 变量则在处理第二个数据文件时继续计数。
- 如果只输入一个文件时,FNR 和 NR 的值是相同的;
- 如果输入多个文件进行处理,FNR 会在处理完当前文件时重置复位;而 NR 则会继续累计,直到处理完所有的文件。
在处理单文件或不需要多文件累计记录数时,建议使用 FNR。
line-range#
以下示例,打印除第一行之外的所有行(记录):
# 方法一:前置不符合条件略过
$ awk 'NR==1 { next; } {print $0}' data2.txt
# 方法二:前置条件约束(省略默认动作)
$ awk 'NR>1' data2.txt
Two lines of test text.
Three lines of test text.
以下示例,只打印前两条记录:
# 方法一:前置条件约束,默认动作print可省
$ id `whoami` | awk 'BEGIN {RS=" ";} FNR<=2 {print}'
# 方法二:后置边界退出
$ id `whoami` | awk 'BEGIN {RS=" ";} {print} FNR==2{exit}'
要打印出从 M 行到 N 行这个范围内的文本内容,可使用下面的语法:
也可以用stdin作为输入:
只打印前两行,也可以指定 FNR 行号区间实现:
以下筛选打印文件 grade.txt 的第2~4行:
$ awk 'FNR==2, FNR==4' grade.txt
J.Lulu 06/99 48317 green 9 24 26
P.Bunny 02/99 48 Yellow 12 35 28
J.Troll 07/99 4842 Brown-3 12 26 26
打印 CSV 文件第2到4行的第2个字段:
以下脚本使用模式过滤 readelf -SW
输出的 section entry,结尾 END 统计总行数(FNR)。
然后,基于 FNR 过滤输出限定范围行。
# count output lines between [Section Headers:, Key to Flags:]
secnum=$(readelf -SW a.out | awk '/Section Headers:/,/Key to Flags:/' | wc -l)
secnum=$(readelf -SW a.out | awk '/Section Headers:/,/Key to Flags:/' | awk 'END{print FNR}')
# discard first three prefix lines and the last suffix line
readelf -SW a.out | awk '/Section Headers:/,/Key to Flags:/' | awk "FNR==4, FNR==$((secnum-1))"
csv-analyze#
CSV(Comma-Separated Values)即逗号分割值,有时也称为字符分割值。
因为分割字符也可以不是逗号,其文件以纯文本形式存储表格数据(数字和文本)。
在 CSV 文本文件中,每条记录占一行,每一行以逗号作为为分割符,逗号前后的空格会被忽略。
读取CSV第1行即表头:
读取所有记录的第1列:
对第1列进行去重输出:
对第1列进行合并统计和去重统计:
awk -F ',' 'FNR>1{print $1}' dependence.csv | uniq -c
awk -F ',' 'FNR>1{print $1}' dependence.csv | uniq | wc -l
user-defined#
跟其他典型的编程语言一样,awk 允许自定义变量在程序代码中使用。
awk 自定义变量名可以是任意数目的字母、数字和下划线,但不能以数字开头,且区分大小写。
assign var#
在 awk 中直接引用 shell 上下文的变量报错:
$ test="cat"
$ sentence="The cat sat on the mat"
$ awk 'BEGIN {print index($sentence, $test)}'
awk: illegal field $(), name "sentence"
source line number 1
可通过 -v
选项将 shell 变量赋值给 awk 内部变量,再引用。
local var#
在 awk 程序脚本中给变量赋值和在 shell 脚本中赋值类似,都用赋值语句。
$ awk 'BEGIN{testing="This is a test"; print testing}'
This is a test
$ awk 'BEGIN{x=4; x= x * 2 + 3; print x}'
11
awk 引用内部变量和 C 语言相似,不用
$
符号。
也可以用 awk 命令行来给程序中的变量赋值,这允许你在正常的代码之外赋值,即时改变变量的值。
cmd var#
以下示例使用命令行变量传参,来显示文件中特定数据字段。
$ cat script1.awk
BEGIN{FS=","}
{print $n}
$ awk -f script1.awk n=2 data1
data12
data22
data32
$ awk -f script1.awk n=3 data1
data13
data23
data33
但是,使用命令行参数来定义变量值会有一个问题,命令行传参其值在 BEGIN 部分不可用。
$ cat script2.awk
BEGIN{print "The starting value is",n; FS=","}
{print $n}
$ awk -f script2.awk n=3 data1
The starting value is
data13
data23
data33
可以用 -v
命令行参数来解决这个问题。它允许在 BEGIN 代码之前设定变量。
在命令行上,-v
命令行参数必须放在脚本代码之前。
array#
for 语句会在每次循环时将关联数组array的下一个索引值赋给变量var,然后执行一遍statements。
注意:
- 这个变量中存储的是 索引值 而不是数组元素值;
- 索引值不会按任何特定顺序返回,只能保证索引值和数据值的对应关系;
$ awk 'BEGIN{
var["a"] = 1
var["g"] = 2
var["m"] = 3
var["u"] = 4
for (test in var)
{
print "Index:",test," - Value:",var[test]
}
}'
Index: g - Value: 2
Index: m - Value: 3
Index: u - Value: 4
Index: a - Value: 1
这里的“数组”,更像是“字典”的概念。索引为字符串,并非整数。
$ awk 'BEGIN{
STR="mydoc.txt"
print split(STR,components,".")
print "prefix="components["1"]
print "suffix="components["2"]
}'
2
prefix=mydoc
suffix=txt
delete#
从关联数组中删除数组索引要用一个特殊的命令。
删除命令会从数组中删除关联索引值和相关的数据元素值。
$ awk 'BEGIN{
var["a"] = 1
var["g"] = 2
var["m"] = 3
var["u"] = 4
for (test in var)
{
print "Index:",test," - Value:",var[test]
}
delete var["g"]
print "---"
for (test in var)
{
print "Index:",test," - Value:",var[test]
}
}'
Index: g - Value: 2
Index: m - Value: 3
Index: u - Value: 4
Index: a - Value: 1
---
Index: m - Value: 3
Index: u - Value: 4
Index: a - Value: 1