Shell流编辑器awk
1 awk简介
awk 是一种强大的文本处理工具和编程语言,广泛用于处理和分析文本文件,尤其是结构化文本(如 CSV 文件和日志文件)。awk 提供了丰富的功能,可以对文本进行过滤、格式化和数据提取。
2 awk语法格式
awk [options] 'pattern { action }' file
· options:awk 的选项,用于设置输入输出格式、定义变量等。
· pattern:模式,用于匹配输入文本中的特定行。如果省略模式,awk 将应用动作(action)到所有行。
· action:在匹配模式的行上执行的命令。用 {} 包围,可以包含多条命令。
· file:要处理的输入文件。awk 也可以处理来自管道的输入。
3 awk工作原理
-
启动与初始化: awk 程序启动时,首先会执行 BEGIN 块中的语句(如果存在)。这通常用于初始化操作,如设置字段分隔符(FS)等。
示例: awk ‘BEGIN { FS=“,”; OFS=“,” } { print $1, $2 }’ filename -
逐行读取输入: awk 从输入文件或标准输入中逐行读取数据。 每次读取一行,将其存储在模式空间(pattern space)中,同时会自动将行分割成字段(默认以空白字符为分隔符)。
-
模式匹配: awk 对每一行应用给定的模式(pattern),如果模式匹配成功,则执行相应的动作(action)。 模式可以是正则表达式、表达式、特定的条件(如字段值的比较)等。
示例: awk ‘/pattern/ { print $0 }’ filename -
执行动作: 如果模式匹配成功,awk 执行大括号 {} 内的动作。 动作可以是打印、赋值、算术运算、逻辑判断等。
示例: awk ‘{ sum += $1 } END { print sum }’ filename -
结束与清理:
当所有行都处理完后,awk 执行 END 块中的语句(如果存在)。这通常用于输出总结结果或进行清理操作。
示例: awk ‘END {print “Processing completed.” }’ filename
4 awk选项参数说明
4.1 -F fs 或 --field-separator fs
用于指定 awk 的输入字段分隔符(Field Separator)。默认情况下,awk 使用空白字符(空格或制表符)作为字段分隔符。但是,如果数据使用其他字符(如逗号、冒号、分号等),则可以使用 -F 选项来指定这些分隔符。
示例1 使用逗号作为分隔符
假设有一个文件 data.txt,内容如下:
Alice,30,New York
Bob,25,Los Angeles
Charlie,35,Chicago
打印每行的第一个字段和第三个字段。
awk -F ',' '{ print $1, $3 }' data.txt
输出:
Alice New York
Bob Los Angeles
Charlie Chicago
示例 2:使用冒号作为分隔符
文件 passwd.txt,内容如下:
root:x:0:0:root:/root:/bin/bashuser:x:1000:1000:User Name:/home/user:/bin/bash
打印用户名和主目录
awk -F ':' '{ print $1, $6 }' passwd.txt
输出:
root /root
user /home/user
示例 3:使用制表符作为分隔符
文件 data.tsv,内容如下:
Alice 30 New York
Bob 25 Los Angeles
Charlie 35 Chicago
打印每行的第一个字段和第三个字段
awk -F '\t' '{ print $1, $3 }' data.tsv
输出:
Alice New York
Bob Los Angeles
Charlie Chicago
示例4:设置内部字段分隔符
在 awk 脚本中,可以通过设置内置变量 FS 来指定字段分隔符,而不是命令行选项 -F。
BEGIN {
FS = ","
}
{
print $1, $3
}
将以上脚本保存为 script.awk,然后运行:
awk -f script.awk data.txt
输出与使用 -F ‘,’ 选项时相同。
4.2 -v var=value
说明:定义一个 awk 变量 var 并赋予初始值 value。
示例1:使用外部传入的阈值
假设有一个文件 data.txt,内容如下:
Alice 30
Bob 25
Charlie 35
打印年龄大于一个指定阈值的人的名字。这个阈值将通过命令行参数传递给 awk。
awk -v threshold=30 '$2 > threshold { print $1 }' data.txt
输出:
Charlie
在这个示例中,使用 -v threshold=30 定义了一个名为 threshold 的变量,并将其值设为 30。在 awk 脚本中,使用这个变量来过滤和打印年龄大于 30 的人的名字。
示例 2:结合多个外部变量
假设我们有一个更复杂的文件 data.txt,内容如下:
Alice 30 Sales
Bob 25 Marketing
Charlie 35 Engineering
过滤和打印特定部门中年龄大于某个值的人的名字。
awk -v threshold=30 -v dept="Sales" '$2 > threshold && $3 == dept { print $1 }' data.txt
输出:
Alice
在这个示例中,定义了两个变量 threshold 和 dept,并在 awk 脚本中使用它们来进行过滤。
示例 3:在脚本中使用外部变量
还可以将 -v 选项与 -f 选项结合使用,将外部变量传递给 awk 脚本文件。
假设有一个 awk 脚本文件 script.awk,内容如下:
$2 > threshold && $3 == dept { print $1 }
然后可以通过以下方式运行脚本,并传递外部变量:
awk -v threshold=30 -v dept="Sales" -f script.awk data.txt
输出与前面的示例相同。
示例 4:使用外部传入的变量控制输出格式
假设有一个文件 data.txt,内容如下:
Alice 30
Bob 25
Charlie 35
我们希望通过外部变量控制输出格式。
awk -v format="%-10s %d\n" '{ printf(format, $1, $2) }' data.txt
输出:
Alice 30
Bob 25
Charlie 35
· %-10s:左对齐的字符串,占用10个字符宽度。如果字符串长度不足10个字符,右边会补空格。
· %d:表示整数。
4.3 -f program-file 或 --file program-file:
说明:从指定文件中读取 awk 程序。
示例 1:简单的 awk 脚本
假设有一个包含以下内容的 data.txt 文件:
Alice 30 New York
Bob 25 Los Angeles
Charlie 35 Chicago
创建一个名为 script.awk 的文件,内容如下:
{ print $1, $3 }
然后运行以下命令:
awk -f script.awk data.txt
输出:
Alice New York
Bob Los Angeles
Charlie Chicago
示例 2:带有 BEGIN 和 END 块的 awk 脚本
创建一个名为 script.awk 的文件,内容如下:
BEGIN {
print "Name\tCity"
print "--------------"
}
{
print $1, "\t", $3
}
END {
print "Processing completed."
}
然后运行以下命令:
awk -f script.awk data.txt
输出:
Name City
--------------
Alice New York
Bob Los Angeles
Charlie Chicago
Processing completed.
示例 3:使用外部变量的 awk 脚本
创建一个名为 script.awk 的文件,内容如下:
$2 > threshold {
print $1, $2
}
然后运行以下命令,将变量 threshold 传递给脚本:
awk -v threshold=30 -f script.awk data.txt
输出:
Charlie 35
4.4 -mf n 和 -mr n:
说明:设置最大记录数(-mr)或字段数(-mf)。这些选项通常在现代 awk 实现中不常用,因为默认设置已经足够大。
示例1:限制最大字段数
awk -mf 100 '{ print $1, $2 }' data.txt
设置 awk 在处理过程中允许的最大字段数为 100。如果一行中包含的字段数超过 100,awk 可能会报错或跳过处理。
示例2:限制最大记录数
awk -mr 1000 '{ print $1, $2 }' data.txt
设置 awk 在处理过程中允许的最大记录数为 1000。如果总记录数超过 1000,awk 可能会报错或停止处理。
4.5 --posix:
说明:使 awk 仅使用 POSIX 标准的功能,禁用所有 GNU 扩展。
使用 --posix 选项的目的
- 移植性:确保脚本可以在不同的 awk 实现间无缝运行。
- 可靠性:避免使用特定于 GNU awk 的功能,以防- 在非 GNU 环境中运行失败。
- 一致性:强制遵循 POSIX 标准,保证脚本行为的一致性。
示例:
awk --posix '{ print $1 }' file.txt
–posix 选项是确保 awk 脚本具有良好移植性和一致性的有力工具。通过禁用 GNU 扩展,它确保脚本在任何符合 POSIX 标准的 awk 实现上都能正常运行。使用这个选项可以避免在不同系统间运行脚本时遇到的兼容性问题,是编写可移植和可靠 awk 脚本的最佳实践。
4.6 --lint:
说明:在脚本中启用警告,可以使用 --lint 选项来检查脚本的语法和潜在问题:
awk --lint -f script.awk input.txt
如果脚本中存在语法错误或不推荐的语法结构,awk 将会显示相应的警告或错误信息,帮助您在脚本执行之前发现并修正问题。
建议在开发和调试阶段使用 --lint 选项,以确保 awk 脚本的质量和可靠性。在生产环境中,通常不建议启用此选项,以避免不必要的性能损耗。
5 awk的print和prinft
- print item1, item2, …
作用:将指定的内容打印输出到标准输出(通常是终端或者重定向到文件),自动处理空格和换行符。
假设有一个文件 data.txt 包含如下内容:
Alice 30
Bob 25
Charlie 35
要打印每行的第一个字段和第二个字段,可以这样使用 print:
awk '{ print $1, $2 }' data.txt
输出:
Alice 30
Bob 25
Charlie 35
- printf format, item1, item2, …
作用:根据指定的格式 (format) 输出内容。
format 是一个控制输出格式的字符串,可以包含格式说明符(如 %s, %d, %f 等)和可选的宽度、精度等信息。
可以根据需要输出固定格式的内容,例如对齐、保留小数位等。
没有自动添加的换行符,需要手动控制输出格式和换行。
示例:
继续使用上面的 data.txt 文件,现在想要按照一定格式输出姓名和年龄:
awk '{ printf("Name: %-10s Age: %d\n", $1, $2) }' data.txt
输出:
Name: Alice Age: 30
Name: Bob Age: 25
Name: Charlie Age: 35
6 awk内置变量
File1.txt中的内容如下:
123456 Hello World
678901 Goodbye
File2.txt内容如下:
Alice 30
Bob 25
Charlie 35
变量 | 描述 | 示例 |
---|---|---|
$n | 当前记录的第 n 个字段,字段间由 FS 分隔 | root@root:~$ awk ‘{ print $1 }’ file1.txt 123456 678901 |
$0 | 完整的输入记录 | root@root:~$ awk ‘{ print $0 }’ file1.txt 123456 Hello World 678901 Goodbye |
ARGC | 命令行参数的数目,注意awk 命令本身占据了一个参数位置。 | root@root:~$ awk ‘BEGIN { print ARGC }’ file1 file2 3 |
ARGIND | 命令行中当前文件的位置(从0开始算) | root@root:~$ awk ‘{ print ARGIND, FILENAME }’ file1.txt file2.txt 1 file1.txt 1 file1.txt 2 file2.txt 2 file2.txt 2 file2.txt |
ARGV | ARGV 是一个数组,包含了命令行中传递给 awk 的所有参数,包括 awk 自身和文件参数。ARGV[0]:awk 自身的执行路径或名称。ARGV[1]:第一个文件参数值,即 file1.txt。 | root@root:~$ awk ‘BEGIN { print ARGV[1] }’ file1.txt file1.txt |
CONVFMT | 字段转换格式变量,只影响由 awk 进行数值转换时的输出格式。 它仅在将字符串转换为数字时才会起作用。数字转换格式(默认为 %.6g) | root@root:~$ awk ‘BEGIN { CONVFMT = “%.3f”; str = “123.45678”; num = str + 0; print “Converted number:”, num; }’ Converted number: 123.457 |
ENVIRON | 环境变量关联数组,ENVIRON[“PATH”]: 使用 ENVIRON 数组来访问环境变量,“PATH” 是环境变量的键名。 | root@root:~$ awk ‘BEGIN { print ENVIRON[“PATH”] }’ /usr/local/sbin:/usr/local/bin:/usr/sbin |
ERRNO | 用于获取最后一个系统错误的描述。ERRNO 仅在出现系统相关错误时才会有值。 | root@root:~$ awk ‘BEGIN { print “Error:”, ERRNO }’ Error: |
FIELDWIDTHS | 字段宽度列表,使用 -v 选项定义FIELDWIDTHS 变量,指定字段的长度,包含空格 | root@root:~$ awk -v FIELDWIDTHS=“5 10” ‘{ print $1, $2 }’ file1.txt 12345 6 Hello 67890 1 Goodby |
FILENAME | 处理多文件时识别当前文件名 | root@root:~$ awk ‘{ print FILENAME }’ file1.txt file2.txt file1.txt file1.txt file2.txt file2.txt file2.txt |
FNR | awk 的一个内置变量,表示当前正在处理的文件中的行号(从1开始计数)。 | root@root:~$ awk ‘{ print FNR, $0 }’ file1.txt file2.txt 1 123456 Hello World 2 678901 Goodbye 1 lice 30 2 Bob 25 3 Charlie 35 |
FS | 字段分隔符(默认为空格) | root@root:~$ awk -F’:’ ‘{ print $1 }’ /etc/passwd root daemon bin sys |
IGNORECASE | { IGNORECASE=1 }': IGNORECASE 变量为 1,表示忽略大小写进行匹配。 | root@root:~$ awk ‘BEGIN { IGNORECASE=1 } /g/ { print $0 }’ file1.txt 678901 Goodbye |
NF | 当前行的字段数,以空格分割统计 | root@root:~$ awk ‘{ print NF }’ file1.txt 3 2 |
NR | 已经读出的记录数,即行号,从1开始 | root@root:~$ awk ‘{ print NR, $0 }’ file1.txt 1 123456 Hello World 2 678901 Goodbye |
OFMT | 指定数字的输出格式(默认为 %.6g) | root@root:~$ awk ‘BEGIN { OFMT = “%.2f”; print 10/3 }’ 3.33 |
OFS | 输出字段分隔符(默认与 FS 相同) | root@root:~$ awk -v OFS=‘,’ ‘{ print $1, $2 }’ file1.txt 123456,Hello 678901,Goodbye |
ORS | 输出记录每行分隔符(默认是一个换行符) | root@root:~$ awk ‘BEGIN { ORS = “\n\n” } { print $0 }’ file1.txt 123456 Hello World 678901 Goodbye |
RLENGTH | 由 match 函数所匹配的字符串的长度 | root@root:~$ awk ‘match($0, /Good/) { print RLENGTH }’ file1.txt 4 |
RS | 记录分隔符(默认是一个换行符) | root@root:~$ awk ‘BEGIN { RS = “\r\n” } { print $0 }’ file1.txt file2.txt 123456 Hello World 678901 Goodbye lice 30 Bob 25 Charlie 35 |
RSTART | 由 match 函数所匹配的字符串在当前行中的起始位置 | root@root:~$ awk ‘match($0, /Good/) { print RSTART }’ file1.txt 10 |
SUBSEP | 数组下标分隔符(默认值是 /034) | 指定多维数组下标分隔符,例如 awk ‘BEGIN { SUBSEP = “-” } { array[“key1”, “key2”] = “value” }’ |
7 awk脚本
awk 脚本结构
一个典型的 awk 脚本结构如下:
BEGIN {
# 初始化代码块
# 例如,设置字段分隔符
FS = ","
print "Starting processing"
}
# 主处理代码块
# 对每一行进行处理
/pattern/ {
# 如果匹配成功,执行这些动作
print $1, $2
}
END {
# 结束代码块
# 例如,输出总结信息
print "Processing completed."
}
示例:
假设有一个包含以下内容的文件 data.txt:
apple 10
banana 20
cherry 30
现需要计算第一列为 “banana” 的第二列的总和
awk 'BEGIN { sum = 0 }
$1 == "banana" { sum += $2 }
END { print sum }' data.txt
BEGIN 块:初始化总和变量 sum 为 0。
主处理块:逐行读取文件,检查第一列是否为 “banana”。如果是,则将第二列的值加到 sum 中。
END 块:所有行处理完后,打印总和 sum。
8 awk条件语句与循环
- 条件语句(if-else)
在 awk 中,条件语句的语法与其他语言类似,可以使用 if、else if 和 else 来根据不同的条件执行不同的代码块。
awk '{
if ($1 > 50) {
print $1, "is greater than 50";
} else if ($1 < 50) {
print $1, "is less than 50";
} else {
print $1, "is equal to 50";
}
}' file.txt
- 循环结构
awk 支持 for 循环和 while 循环来遍历数据或者重复执行代码块。
for循环:
awk 'BEGIN {
for (i = 1; i <= 5; i++) {
print "Count:", i;
}
}'
while 循环:
awk 'BEGIN {
i = 1;
while (i <= 5) {
print "Count:", i;
i++;
}
}'
这个示例也会输出从 1 到 5 的计数,但是使用了 while 循环。
9 awk数组
9.1 创建数组
awk 'BEGIN {
# 创建一个名为 scores 的数组
scores["Alice"] = 85;
scores["Bob"] = 92;
scores["Eve"] = 78;
# 输出数组中的值
print "Alice's score:", scores["Alice"];
print "Bob's score:", scores["Bob"];
}' file.txt
9.2 多维数组
方式1:
awk 'BEGIN {
# 创建一个二维数组
grades["Alice"]["Math"] = 85;
grades["Bob"]["Science"] = 92;
grades["Alice"]["Science"] = 90;
# 访问和输出多维数组中的元素
print "Alice's Math score:", grades["Alice"]["Math"];
print "Bob's Science score:", grades["Bob"]["Science"];
}' file.txt
方式2:
awk 'BEGIN {
# 创建一个二维数组
array["0,0"] = 100;
array["0,1"] = 200;
array["0,2"] = 300;
array["1,0"] = 400;
array["1,1"] = 500;
array["1,2"] = 600;
# 输出数组元素
print "array[0,0] =", array["0,0"];
print "array[0,1] =", array["0,1"];
print "array[0,2] =", array["0,2"];
print "array[1,0] =", array["1,0"];
print "array[1,1] =", array["1,1"];
print "array[1,2] =", array["1,2"];
}'
10 awk的函数
常用的awk内置函数如下
函数 | 描述 | 示例 |
---|---|---|
length(str) | 返回字符串 str 的长度 length(“Hello”) 返回 5 | |
substr(str, start, length) | 返回字符串 str 从 start 位置开始 length 长度的子串 substr(“Hello World”, 7, 5) 返回 “World” | |
index(str, search) | 返回 search 在字符串 str 中第一次出现的位置 index(“Hello World”, “World”) 返回 7 | |
split(str, arr, sep) | 将字符串 str 按分隔符 sep 分割并存入数组 arr 中 split(“apple orange banana”, fruits, " ") | |
tolower(str) | 将字符串 str 转换为小写 tolower(“Hello”) 返回 “hello” | |
toupper(str) | 将字符串 str 转换为大写 toupper(“Hello”) 返回 “HELLO” | |
sprintf(fmt, expr) | 根据格式字符串 fmt 返回格式化后的字符串 sprintf(“%.2f”, 3.14159) 返回 “3.14” | |
int(expr) | 返回表达式 expr 的整数部分 int(3.99) 返回 3 | |
sin(expr) | 返回角度 expr 的正弦值 sin(45) 返回正弦值 | |
cos(expr) | 返回角度 expr 的余弦值 cos(60) 返回余弦值 | |
log(expr) | 返回 expr 的自然对数(以 e 为底) log(10) 返回约 2.30259 | |
sqrt(expr) | 返回 expr 的平方根 sqrt(25) 返回 5 | |
rand() | 返回 [0, 1) 范围内的随机数 rand() 返回随机数 | |
srand(seed) | 设置随机数种子 seed,用于 rand() 函数生成随机数 srand(1234) | |
getline var | 从输入文件中读取下一行,存入变量 var awk ‘BEGIN { getline line < “file.txt”; print line }’ |
详细函数说明可参考https://www.runoob.com/w3cnote/awk-built-in-functions.html