【笔记】awk 命令 - 文本数据处理工具

外置命令,awk 命令也是逐行扫描文件(从第 1 行到最后一行),寻找含有目标文本的行,如果匹配成功,则会在该行上执行用户想要的操作;反之,则不对行做任何处理。

[root@localhost ~]# awk [选项] '脚本命令' 文件名
  • awk 命令的选项
选项 功能
-F fs 指定以 fs 作为输入行的分隔符,awk 命令默认分隔符为空格或制表符
-f file 从脚本文件中读取 awk 脚本指令,以取代直接在命令行中输入指令
-v var=val 在执行处理过程之前,设置一个变量 var,并给其设备初始值为 val
  • awk 命令的脚本命令

awk 的强大之处在于脚本命令,它由 2 部分组成,分别为匹配规则和执行命令。

'匹配规则{执行命令}'
  • 这里的匹配规则,和 sed 命令中的 address 部分作用相同,用来指定脚本命令可以作用到文本内容中的具体行,可以使用字符串(比如 /demo/,表示查看含有 demo 字符串的行)或者正则表达式指定。另外需要注意的是,整个脚本命令是用单引号('')括起,而其中的执行命令部分需要用大括号({})括起来。
  • 在 awk 程序执行时,如果没有指定执行命令,则默认会把匹配的行输出;如果不指定匹配规则,则默认匹配文本中所有的行。
  • 示例
[root@localhost ~]# awk '/^$/ {print "Blank line"}' test.txt

在此命令中,/^$/ 是一个正则表达式,功能是匹配文本中的空白行,同时可以看到,执行命令使用的是 print 命令,此命令经常会使用,它的作用很简单,就是将指定的文本进行输出。因此,整个命令的功能是,如果 test.txt 有 N 个空白行,那么执行此命令会输出 N 个 Blank line。


1. awk 使用数据字段变量

awk 的主要特性之一是其处理文本文件中数据的能力,它会自动给一行中的每个数据元素分配一个变量。

默认情况下,awk 会将如下变量分配给它在文本行中发现的数据字段:

  • $0 代表整个文本行;
  • $1 代表文本行中的第 1 个数据字段;
  • $2 代表文本行中的第 2 个数据字段;
  • $n 代表文本行中的第 n 个数据字段。

在 awk 中,默认的字段分隔符是任意的空白字符(例如空格或制表符)。 在文本行中,每个数据字段都是通过字段分隔符划分的。awk 在读取一行文本时,会用预定义的字段分隔符划分每个数据字段。

  • 示例
[root@localhost ~]# cat data2.txt
One line of test text.
Two lines of test text.
Three lines of test text.
[root@localhost ~]# awk '{print $1}' data2.txt
One
Two
Three
# awk 程序读取文本文件,只显示第 1 个数据字段的值

2. awk 使用多个脚本命令

awk 允许将多条命令组合成一个正常的程序。要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号即可。

  • 示例
[root@localhost ~]# echo "My name is Rich" | awk '{$4="Christine"; print $0}'
My name is Christine
# 第一条命令会给字段变量 $4 赋值。第二条命令会打印整个数据字段。可以看
# 到,awk 程序在输出中已经将原文本中的第四个数据字段替换成了新值
root@localhost ~]# awk '{
> $4="Christine"
> print $0}'
My name is Rich
My name is Christine
# 在你用了表示起始的单引号后,bash shell 会使用 > 来提示输入更多数据,
# 我们可以每次在每行加一条命令,直到输入了结尾的单引号
# 注意,此例中因为没有在命令行中指定文件名,awk 程序需要用户输入获得数
# 据,因此当运行这个程序的时候,它会一直等着用户输入文本,此时如果要退
# 出程序,只需按下 Ctrl+D 组合键即可

3. awk 从文件中读取程序

跟 sed 一样,awk 允许将脚本命令存储到文件中,然后再在命令行中引用。

  • 示例
[root@localhost ~]# cat awk.sh
{print $1 "'s home directory is " $6}
[root@localhost ~]# awk -F: -f awk.sh /etc/passwd
root's home directory is /root
bin's home directory is /bin
daemon's home directory is /sbin
adm's home directory is /var/adm
lp's home directory is /var/spool/lpd
...
Christine's home directory is /home/Christine
Samantha's home directory is /home/Samantha
Timothy's home directory is /home/Timothy
# awk.sh 脚本文件会使用 print 命令打印 /etc/passwd 文件的主目录数据字
# 段(字段变量 $6),以及 userid 数据字段(字段变量 $1)。注意,在程序
# 文件中,也可以指定多条命令,只要一条命令放一行即可,之间不需要用分号

4. awk BEGIN关键字

awk 中还可以指定脚本命令的运行时机。默认情况下,awk 会从输入中读取一行文本,然后针对该行的数据执行程序脚本,但有时可能需要在处理数据前运行一些脚本命令,这就需要使用 BEGIN 关键字。BEGIN 会强制 awk 在读取数据前执行该关键字后指定的脚本命令。

  • 示例
[root@localhost ~]# cat data3.txt
Line 1
Line 2
Line 3
[root@localhost ~]# awk 'BEGIN {print "The data3 File Contents:"}
> {print $0}' data3.txt
The data3 File Contents:
Line 1
Line 2
Line 3
# 这里的脚本命令中分为 2 部分,BEGIN 部分的脚本指令会在 awk 命令处理
# 数据前运行,而真正用来处理数据的是第二段脚本命令

5. awk END关键字

和 BEGIN 关键字相对应,END 关键字允许我们指定一些脚本命令,awk 会在读完数据后执行它们。

  • 示例
[root@localhost ~]# awk 'BEGIN {print "The data3 File Contents:"}
> {print $0}
> END {print "End of File"}' data3.txt
The data3 File Contents:
Line 1
Line 2
Line 3
End of File
# 当 awk 程序打印完文件内容后,才会执行 END 中的脚本命令

6. awk 使用变量

在 awk 的脚本程序中,支持使用变量来存取值。awk 支持两种不同类型的变量:

  • 内建变量:awk 本身就创建好,用户可以直接拿来用的变量,这些变量用来存放处理数据文件中的某些字段和记录的信息。
  • 自定义变量:awk 支持用户自己创建变量。

6.1 内建变量

awk 程序使用内建变量来引用程序数据里的一些特殊功能。常见的一些内建变量,包括上一节介绍的数据字段变量($0、$1、$2...$n)以及下表所示的这些变量。

  • 字段和记录分隔符变量
变量 功能
FIELDWIDTHS 由空格分隔的一列数字,定义了每个数据字段的确切宽度
FNR 当前输入文档的记录编号,常在有多个输入文档时使用
NR 输入流的当前记录编号
FS 输入字段分隔符
RS 输入记录分隔符,默认为换行符 \n
OFS 输出字段分隔符,默认为空格
ORS 输出记录分隔符,默认为换行符 \n
  • 变量 FS 和 OFS 定义了 awk 如何处理数据流中的数据字段。我们已经知道了如何使用变量 FS 来定义记录中的字段分隔符,变量 OFS 具备相同的功能,只不过是用在 print 命令的输出上。
  • FIELDWIDTHS 变量允许用户不依靠字段分隔符来读取记录。在一些应用程序中,数据并没有使用字段分隔符,而是被放置在了记录中的特定列,这种情况下,必须设定 FIELDWIDTHS 变量来匹配数据在记录中的位置。一旦设置了 FIELDWIDTH 变量,awk 就会忽略 FS 变量,并根据提供的字段宽度来计算字段。
  • 变量 RS 和 ORS 定义了 awk 程序如何处理数据流中的字段,默认情况下,awk 将 RS 和 ORS 设为换行符。默认的 RS 值表明,输入数据流中的每行新文本就是一条新纪录。 有时,你会在数据流中碰到占据多行的字段。典型的例子是包含地址和电话号码的数据,其中地址和电话号码各占一行。
  • 环境信息变量
变量名 功能
ARGC 命令行参数个数
ARGIND 当前文件在 ARGC 中的位置
ARGV 包含命令行参数的数组
CONVFMT 数字的转换格式,默认值为 %.6g
ENVIRON 当前 shell 环境变量及其值组成的关联数组
ERRNO 当读取或关闭输入文件发生错误时的系统错误号
FILENAME 当前输入文档的名称
FNR 当前数据文件中的数据行数
IGNORECASE 设成非 0 值时,忽略 awk 命令中出现的字符串的字符大小写
NF 数据文件中的字段总数
NR 已处理的输入记录数
OFMT 数字的输出格式,默认值为 %.6g
RLENGTH 由 match 函数所匹配的子字符串的长度
TSTART 由 match 函数所匹配的子字符串的起始位置

FNR 和 NR 变量虽然类似,但又略有不同。FNR 变量含有当前数据文件中已处理过的记录数,NR 变量则含有已处理过的记录总数。

  • 示例
[root@localhost ~]# cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
[root@localhost ~]# awk 'BEGIN{FS=","; OFS="-"} {print $1,$2,$3}' data1
data11-data12-data13
data21-data22-data23
data31-data32-data33
[root@localhost ~]# awk 'BEGIN{FS=","; OFS="--"} {print $1,$2,$3}' data1
data11--data12--data13
data21--data22--data23
data31--data32--data33
# print 命令会自动将 OFS 变量的值放置在输出中的每个字段间。
# 通过设置 OFS 变量,可以在输出中使用任意字符串来分隔字段
[root@localhost ~]# cat data1b
1005.3247596.37
115-2.349194.00
05810.1298100.1
[root@localhost ~]# awk 'BEGIN{FIELDWIDTHS="3 5 2 5"}{print $1,$2,$3,$4}' data1b
100 5.324 75 96.37
115 -2.34 91 94.00
058 10.12 98 100.1
# 一旦设定了 FIELDWIDTHS 变量的值,就不能再改变了,因此,这种
# 方法并不适用于变长的字段
[root@localhost ~]# 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
[root@localhost ~]# awk 'BEGIN{FS="\n"; RS=""} {print $1,$4}' data2
Riley Mullen (312)555-1234
Frank Williams (317)555-9876
Haley Snell (313)555-4938
# 把 FS 变量设置成换行符,这就表明数据流中的每行都是一个单独的字
# 段,每行上的所有数据都属于同一个字段;与此同时,把 RS 变量设置
# 成空字符串,然后在数据记录间留一个空白行,awk 会把每个空白行当
# 作一个记录分隔符
[root@localhost ~]# cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
[root@localhost ~]# awk '
> BEGIN {FS=","}
> {print $1,"FNR="FNR,"NR="NR}
> END{print "There were",NR,"records processed"}' 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
There were 6 records processed
# 当只使用一个数据文件作为输入时,FNR 和 NR 的值是相同的;如果
# 使用多个数据文件作为输入,FNR 的值会在处理每个数据文件时被重置,
# 而 NR 的值则会继续计数直到处理完所有的数据文件

6.2 自定义变量

和其他典型的编程语言一样,awk 允许用户定义自己的变量在脚本程序中使用。awk 自定义变量名可以是任意数目的字母、数字和下划线,但不能以数字开头。更重要的是,awk 变量名区分大小写。

  • 示例
[root@localhost ~]# awk '
> BEGIN{
> testing="This is a test"
> print testing
> testing=45
> print testing
> }'
This is a test
45
# print 语句的输出是 testing 变量的当前值
[root@localhost ~]# cat script1
BEGIN{FS=","} {print $n}
[root@localhost ~]# awk -f script1 n=2 data1
data12
data22
data32
[root@localhost ~]# awk -f script1 n=3 data1
data13
data23
data33
# 可以用 awk 命令行来给程序中的变量赋值,这允许我们在正常的代码
# 之外赋值,即时改变变量的值
[root@localhost ~]# cat script2
BEGIN{print "The starting value is",n; FS=","}
{print $n}
[root@localhost ~]# awk -f script2 n=3 data1
The starting value is
data13
data23
data33
# 使用命令行参数来定义变量值会有一个问题,即设置了变量后,这个值
# 在代码的 BEGIN 部分不可用
[root@localhost ~]# awk -v n=3 -f script2 data1
The starting value is 3
data13
data23
data33
# 可以用 -v 命令行参数,它可以实现在 BEGIN 代码之前设定变量。在命
# 令行上,-v 命令行参数必须放在脚本代码之前

7. awk 使用数组

为了在单个变量中存储多个值,许多编程语言都提供数组,awk 使用关联数组提供数组功能。
关联数组跟数字数组不同之处在于,它的索引值可以是任意文本字符串。用户不需要用连续的数字来标识数组中的数据元素;相反,关联数组用各种字符串来引用值。每个索引字符串都必须能够唯一地标识出赋给它的数据元素。

7.1 关联数组的定义和使用

在 awk 脚本程序中,定义一个数组变量可以使用标准复制语句。

var[index]=element
  • 其中,var 是数组名,index 是关联数组的索引值,element 是数据元素值。
  • 示例
[root@localhost ~]# awk 'BEGIN{
> capital["Illinois"] = "Springfield"
> print capital["Illinois"]
> }'
Springfield
# 在引用数组变量时,必须用索引值(index)来提取相应的数据元素值
[root@localhost ~]# awk 'BEGIN{
> var[1] = 34
> var[2] = 3
> total = var[1] + var[2]
> print total
> }'
37
# 数组变量也是变量,也可以使用其进行基本的算术运算

7.2 关联数组的遍历

for (var in array)
{
    statements
}

这个 for 语句会在每次循环时将关联数组 array 的下一个索引值赋给变量 var,然后执行一遍 statements。
再次强调,整个遍历过程中,传给 var 的都是每个数组元素的索引值(也就是 index),不是数组元素的值。

  • 示例
[root@localhost ~]# 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: u  - Value: 4
Index: m  - Value: 3
Index: a  - Value: 1
Index: g  - Value: 2
# 注意,索引值不会按任何特定顺序返回,但它们都能够指向对应的数据元素值

7.3 删除数组变量

awk脚本程序还支持从关联数组中删除某个数组索引,使用 delete 命令就可以,此命令会从数组中删除指定的索引值及相关的数据元素的值。

delete array[index]
  • 示例
[root@localhost ~]# awk 'BEGIN{
> var["a"] = 1
> var["g"] = 2
> 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: a  - Value: 1
Index: g  - Value: 2
-
Index: a  - Value: 1
# 需要注意的是,一旦从关联数组中删除了索引值,就没法再用它
# 来提取元素值

8. awk 使用分支结构

if (condition)
    statement1
else
    statements
也可以将它放在一行上
if (condition) statement1;else statement2
  • 示例
[root@localhost ~]# cat data4
10
5
13
50
34
[root@localhost ~]# awk '{if ($1 > 20) print $1 * 2; else print $1 / 2}' data4
5
2.5
6.5
100
68

9. awk 使用循环结构

awk 脚本程序中,可以使用 while、do-while、for 这 3 种循环结构。

while (条件) {
   运行代码;
}
# 第一种结构
do
{
运行代码;
}while(条件)
# 第二种结构
for(变量;条件;计数器)
{
    运行代码;
}
# 第三种结构

awk 支持使用的循环结构的用法和 C 语言完全一样,除此之外,awk 还支持使用 break(跳出循环)、continue(终止当前循环)关键字,其用法和 C 语言中也完全相同。

  • 示例
[root@localhost ~]# cat data5
130 120 135
160 113 140
145 170 215
[root@localhost ~]# awk '{
> total = 0
> i = 1
> while (i < 4)
> {
>    total += $i
>    i++
> }
> avg = total / 3
> print "Average:",avg
> }' data5
Average: 128.333
Average: 137.667
Average: 176.667
# 第一种结构
[root@localhost ~]# awk '{
> total = 0
> i = 1
> do
> {
>    total += $i
>    i++
> } while (total < 150)
> print total }' data5
250
160
315
# 第二种结构
[root@localhost ~]# awk '{
> total = 0
> for (i = 1; i < 4; i++)
> {
>    total += $i
> }
> avg = total / 3
> print "Average:",avg
> }' data5
Average: 128.333
Average: 137.667
Average: 176.667
# 第三种结构

10. awk 使用函数

10.1 内建函数

和内建变量类似,awk 也提供了不少内建函数,可进行一些常见的数学、字符串以及时间函数运算。

分类 函数原型 函数功能
atan2(x, y) x/y 的反正切,x 和 y 以弧度为单位
cos(x) x 的余弦,x 以弧度为单位
exp(x) x 的指数函数
int(x) x 的整数部分,取靠近零一侧的值
log(x) x 的自然对数
srand(x) 为计算随机数指定一个种子值
rand() 比 0 大比 1 小的随机浮点值
sin(x) x 的正弦,x 以弧度为单位
sqrt(x) x 的平方根
and(v1, v2) 执行值 v1 和 v2 的按位与运算
compl(val) 执行 val 的补运算
lshift(val, count) 将值 val 左移 count 位
or(v1, v2) 执行值 v1 和 v2 的按位或运算
rshift(val, count) 将值 val 右移 count 位
xor(v1, v2) 执行值 v1 和 v2 的按位异或运算
asort(s [,d]) 将数组 s 按数据元素值排序。索引值会被替换成表示新的排序顺序的连续数字。另外,如果指定了 d,则排序后的数组会存储在数组 d 中
asorti(s [,d]) 将数组 s 按索引值排序。生成的数组会将索引值作为数据元素值,用连续数字索引来表明排序顺序。另外如果指定了 d,排序后的数组会存储在数组 d 中
gensub(r, s, h [, t]) 查找变量 $0 或目标字符串 t(如果提供了的话)来匹配正则表达式 r。如果 h 是一个以 g 或 G 开头的字符串,就用 s 替换掉匹配的文本。如果 h 是一个数字,它表示要替换掉第 h 处 r 匹配的地方
gsub(r, s [,t]) 查找变量 $0 或目标字符串 t(如果提供了的话)来匹配正则表达式 r。如果找到了,就全部替换成字符串 s
index(s, t) 返回字符串 t 在字符串 s 中的索引值,如果没找到的话返回 0
length([s]) 返回字符串 s 的长度;如果没有指定的话,返回 $0 的长度
match(s, r [,a]) 返回字符串 s 中正则表达式 r 出现位置的索引。如果指定了数组 a,它会存储 s 中匹配正则表达式的那部分
split(s, a [,r]) 将 s 用 FS 字符或正则表达式 r(如果指定了的话)分开放到数组 a 中,并返回字段的总数
sprintf(format, variables) 用提供的 format 和 variables 返回一个类似于 printf 输出的字符串
sub(r, s [,t]) 在变量 $0 或目标字符串 t 中查找正则表达式 r 的匹配。如果找到了,就用字符串 s 替换掉第一处匹配
substr(s, i [,n]) 返回 s 中从索引值 i 开始的 n 个字符组成的子字符串。如果未提供 n,则返回 s 剩下的部分
tolower(s) 将 s 中的所有字符转换成小写
toupper(s) 将 s 中的所有字符转换成大写
mktime(datespec) 将一个按 YYYY MM DD HH MM SS [DST] 格式指定的日期转换成时间戳值
strftime(format [,timestamp]) 将当前时间的时间戳或 timestamp(如果提供了的话)转化格式化日期(采用 shell 函数 date() 的格式)
systime() 返回当前时间的时间戳
  • 时间戳指的是格林威治时间,即从 1970年1月1日8时1起到现在的总秒数。

10.2 自定义函数

除了awk 中的内建函数,还可以在 awk 脚本程序中自定义函数。注意,自定义函数的函数名必须能够唯一标识此函数,换句话说,在同一个 awk 脚本程序中,多个函数的函数名不能相同。同时,函数的参数可以有多个(0 个、1 个或多个)。需要注意的是,在定义函数时,它必须出现在所有代码块之前(包括 BEGIN 和 END代码块)。

function 函数名(参数1,参数2,...)
{
    运行代码;
}
  • 示例
function printthird()
{
    print $3
}
# 此函数会打印记录中的第三个数据字段

10.3 创建函数库

awk 提供了一种途径来将多个函数放到一个库文件中,这样用户就能在所有的 awk 脚本程序中使用了。

  • 示例
[root@localhost ~]# cat funclib
function myprint() {
   printf "%-16s - %s\n", $1, $4
}
function myrand(limit)
{
   return int(limit * rand())
}
function printthird()
{
   print $3
}
# 创建一个存储所有 awk 函数的文件
[root@localhost ~]# cat script4
BEGIN{ FS="\n"; RS=""}
{
     myprint()
}
[root@localhost ~]# awk -f funclib -f script4 data2
Riley Mullen     - (312)555-1234
Frank Williams   - (317)555-9876
Haley Snell      - (313)555-4938
# 要想让 awk 成功读取 funclib 函数库文件,就需要使用 -f 选
# 项,但此选项无法和 awk 脚本程序同时放到命令行中一起使用。
# 因此,要使用库函数文件,只能再创建一个脚本程序文件

你可能感兴趣的:(【笔记】awk 命令 - 文本数据处理工具)