MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Bash脚本与文本处理:awk与sed

2021-05-062.8k 阅读

Bash 中的文本处理概述

在 Bash 脚本编程中,文本处理是一项极为重要的任务。无论是系统管理、数据处理还是自动化脚本编写,都常常需要对文本进行解析、修改和提取关键信息。而 awksed 工具在文本处理领域堪称两把利器,它们功能强大且高效,能够帮助开发者轻松应对各种复杂的文本处理需求。

awk 基础

awk 是一种编程语言,用于在 Unix - like 系统中处理文本数据。它的设计理念基于模式匹配和动作执行,语法结构如下:

awk 'pattern { action }' input_file

其中,pattern 是用于匹配输入文本行的条件,action 则是在匹配到符合 pattern 的行时执行的操作。如果省略 pattern,则对输入的每一行都执行 action;若省略 action,则默认打印匹配的行。

字段和记录

awk 中,输入文本被视为由记录组成,默认情况下,每一行就是一个记录。而每个记录又由字段组成,默认以空白字符(空格或制表符)作为字段分隔符。例如,假设有一个文件 data.txt 内容如下:

John 25 Engineer
Alice 30 Doctor
Bob 22 Student

awk 中,第一行就是一个记录,其中 John25Engineer 分别是字段。可以使用 $n 来引用第 n 个字段,$0 则表示整个记录(即整行文本)。

简单示例:打印特定字段

假设要打印上述 data.txt 文件中每个人的年龄,可以使用如下 awk 命令:

awk '{print $2}' data.txt

上述命令会遍历 data.txt 的每一行,由于省略了 pattern,所以对每一行都执行 print $2 操作,即打印每行的第二个字段,输出结果为:

25
30
22

模式匹配

awk 支持多种模式匹配方式,最常见的是使用正则表达式。例如,要在 data.txt 文件中找出职业为 Engineer 的人的信息,可以这样写:

awk '/Engineer/ {print $0}' data.txt

这里 /Engineer/ 是一个正则表达式模式,它会匹配包含 Engineer 的行,然后执行 print $0 操作,输出匹配的整行内容:

John 25 Engineer

awk 内置变量

awk 提供了许多内置变量,这些变量在文本处理中非常有用。

NR 和 FNR

NR 表示当前处理的记录数(即行数),从 1 开始计数。FNRNR 类似,但它是针对每个输入文件单独计数的。例如,当处理多个文件时,NR 会连续计数,而 FNR 在处理每个新文件时会重置为 1。

假设有两个文件 file1.txtfile2.txt,内容分别为:

file1.txt

Line1 in file1
Line2 in file1

file2.txt

Line1 in file2
Line2 in file2

执行以下命令:

awk '{print NR, FNR, $0}' file1.txt file2.txt

输出结果为:

1 1 Line1 in file1
2 2 Line2 in file1
3 1 Line1 in file2
4 2 Line2 in file2

NF

NF 表示当前记录中的字段数。例如,对于文件 data.txt,可以使用 NF 来查看每行有多少个字段:

awk '{print NF, $0}' data.txt

输出结果为:

3 John 25 Engineer
3 Alice 30 Doctor
3 Bob 22 Student

awk 中的算术运算

awk 支持基本的算术运算,如加(+)、减(-)、乘(*)、除(/)和取模(%)。可以在 action 中对字段或变量进行算术运算。

例如,假设有一个文件 scores.txt,记录了学生的成绩,每行格式为 “学生姓名 语文成绩 数学成绩”:

Tom 85 90
Jerry 78 82

要计算每个学生的总分,可以使用以下 awk 命令:

awk '{total = $2 + $3; print $1, "Total:", total}' scores.txt

这里定义了一个变量 total,用于存储语文成绩和数学成绩之和,然后打印学生姓名和总分。输出结果为:

Tom Total: 175
Jerry Total: 160

awk 控制结构

awk 支持常见的控制结构,如 if - elseforwhiledo - while

if - else 结构

例如,要根据学生的平均分判断成绩等级,可以这样写:

awk '{avg = ($2 + $3) / 2; if (avg >= 90) {print $1, "A";} else if (avg >= 80) {print $1, "B";} else {print $1, "C";}}' scores.txt

上述命令先计算每个学生的平均分,然后根据平均分判断成绩等级并打印。

for 循环

假设要打印从 1 到 5 的数字,可以在 awk 中使用 for 循环:

awk 'BEGIN {for (i = 1; i <= 5; i++) {print i;}}'

这里 BEGINawk 的特殊关键字,表示在处理输入文件之前执行的动作。上述命令会输出:

1
2
3
4
5

sed 基础

sed 是 “stream editor” 的缩写,它是一个基于行的文本编辑器,主要用于对文本进行过滤和转换操作。sed 命令的基本语法如下:

sed 'command' input_file

其中 commandsed 的操作指令,常见的指令有 s(替换)、d(删除)、p(打印)等。

替换操作(s 指令)

sed 的替换操作是其最常用的功能之一。语法为:

sed's/old_string/new_string/' input_file

这会将 input_file 中每行的第一个 old_string 替换为 new_string。例如,假设有一个文件 text.txt,内容为:

Hello, world!

要将 world 替换为 Universe,可以执行:

sed's/world/Universe/' text.txt

输出结果为:

Hello, Universe!

如果要替换每行中所有的 old_string,可以在命令末尾加上 g(global)标志:

sed's/world/Universe/g' text.txt

删除操作(d 指令)

d 指令用于删除匹配指定模式的行。例如,要删除 text.txt 中包含 Hello 的行,可以使用:

sed '/Hello/d' text.txt

如果 text.txt 只有一行 Hello, world!,执行上述命令后将不会有任何输出,因为该行被删除了。

打印操作(p 指令)

p 指令用于打印匹配指定模式的行。例如,要打印 text.txt 中包含 world 的行,可以使用:

sed -n '/world/p' text.txt

这里 -n 选项表示默认不输出所有行,只输出执行了 p 指令的行。

sed 地址范围

sed 可以指定操作应用的地址范围。地址可以是行号、正则表达式或者两者的组合。

行号范围

例如,要对 text.txt 的第 2 到第 4 行执行替换操作,可以这样写:

sed '2,4s/old/new/g' text.txt

正则表达式范围

要对 text.txt 中从匹配 start_pattern 的行开始,到匹配 end_pattern 的行结束的范围内执行删除操作,可以使用:

sed '/start_pattern/,/end_pattern/d' text.txt

sed 高级替换

sed 的替换操作中,可以使用一些特殊的符号和技巧来实现更复杂的替换。

后向引用

后向引用允许在替换字符串中引用在搜索模式中匹配的内容。例如,假设有一个文件 names.txt,内容为:

John Doe
Jane Smith

要将姓名顺序颠倒,变为 “Doe John” 的格式,可以使用后向引用:

sed's/\([^ ]*\) \([^ ]*\)/\2 \1/' names.txt

这里 \([^ ]*\) 是一个捕获组,\1\2 分别引用第一个和第二个捕获组匹配的内容。输出结果为:

Doe John
Smith Jane

多行替换

sed 可以通过 -z 选项和特殊的换行符处理来实现多行替换。例如,假设有一个文件 multi_line.txt,内容为:

Line1
Line2
Line3

要将 Line2Line3 合并为一行,可以使用:

sed -z 's/Line2\nLine3/Line2 Line3/' multi_line.txt

这里 -z 选项表示以 NUL 字符作为记录分隔符,而不是默认的换行符,这样就可以跨多行进行操作。

awk 与 sed 的比较

  1. 功能侧重
    • awk 更适合于复杂的数据处理和分析,它具有完整的编程语言特性,如变量、控制结构、函数等。可以进行算术运算、数据统计、格式化输出等。例如,计算文件中某列数据的总和、平均值等。
    • sed 则主要用于文本的简单替换、删除、插入等编辑操作,它基于行进行处理,更侧重于文本的快速修改和过滤。例如,批量替换文件中的字符串、删除特定行。
  2. 语法复杂度
    • awk 的语法相对更复杂,因为它是一种编程语言,有自己的变量定义、函数调用等规则。不过,一旦掌握,其功能的灵活性和强大性是非常突出的。
    • sed 的语法相对简洁,主要基于一些简单的指令和地址范围操作。对于简单的文本处理任务,sed 可以快速编写和执行。
  3. 处理效率
    • 在处理大数据量时,sed 通常会更快一些,因为它的操作相对简单,基于行的处理方式使得它在读取和修改文本时开销较小。
    • awk 由于功能复杂,在处理大数据时可能会消耗更多的系统资源,但对于需要复杂计算和逻辑处理的任务,其优势明显。

实际应用案例

  1. 系统日志分析(awk):假设系统日志文件 syslog 记录了用户登录信息,每行格式为 “时间 用户名 IP 地址”。要统计每个用户的登录次数,可以使用以下 awk 脚本:
awk '{users[$2]++} END {for (user in users) {print user, users[user];}}' syslog

这里使用了关联数组 users 来统计每个用户名出现的次数,END 关键字表示在处理完所有输入行后执行的动作,用于打印统计结果。

  1. 配置文件修改(sed):假设有一个配置文件 config.ini,其中有一行配置为 server = old_server_ip,要将其修改为新的服务器 IP 地址,可以使用 sed
sed's/old_server_ip/new_server_ip/' config.ini > new_config.ini

上述命令将修改后的内容输出到 new_config.ini 文件中。

结合 awk 和 sed

在实际的文本处理任务中,经常需要结合 awksed 的优势来完成复杂的需求。例如,先使用 awk 提取出文本中的关键信息,然后再用 sed 对这些信息进行进一步的格式化或替换。

假设有一个文件 report.txt,内容为:

Product1: 100 units, $20 each
Product2: 50 units, $30 each

要先提取出产品名称和总价格(数量 * 单价),然后将格式化为 “产品名称: 总价格” 的形式,可以这样做:

awk '{name = $1; sub(":", "", name); units = $3; price = $5; total = units * price; print name, total}' report.txt | sed 's/ /: /'

上述命令先用 awk 提取产品名称、数量和单价,并计算总价格,然后通过管道将结果传递给 sedsed 将空格替换为 “: ”,最终输出:

Product1: 2000
Product2: 1500

总结

awksed 是 Bash 脚本编程中强大的文本处理工具。awk 以其丰富的编程特性适用于复杂的数据处理和分析任务,而 sed 则凭借简洁的语法和高效的行处理能力在文本编辑和过滤方面表现出色。通过深入理解它们的原理、语法和应用场景,并在实际项目中灵活运用,开发者能够极大地提高文本处理的效率和质量,轻松应对各种文本相关的挑战。无论是系统管理、数据分析还是自动化脚本开发,这两个工具都将成为开发者的得力助手。在实际应用中,还可以根据具体需求将 awksed 与其他 Bash 命令或工具结合使用,发挥更大的威力。例如,结合 grep 进行文本搜索,再用 awksed 进行后续处理;或者利用 xargs 命令将 awksed 的处理结果作为其他命令的参数,进一步拓展文本处理的功能边界。熟练掌握这两个工具,对于提升 Bash 脚本编程水平和解决实际问题的能力具有重要意义。

希望通过本文的介绍,读者能够对 awksed 有更深入的了解,并在日常的开发和系统管理工作中充分发挥它们的优势。在实践过程中,不断探索和尝试新的用法和技巧,以更好地满足不同的文本处理需求。同时,注意在处理大数据量文本时,合理选择工具和优化命令,以确保处理效率和系统性能。

高级 awk 技巧

  1. 函数定义与使用awk 允许用户定义自己的函数,这在处理复杂逻辑时非常有用。例如,假设有一个需求是对文件中的数字进行四舍五入操作。可以定义如下函数:
awk 'function round(num) { return int(num + 0.5); } {print round($1);}' numbers.txt

这里定义了一个 round 函数,它接受一个数字参数 num,并返回四舍五入后的结果。在主程序部分,对 numbers.txt 文件中每行的第一个字段应用这个函数并打印。

  1. 数组操作awk 中的数组不仅可以是关联数组,还可以进行一些复杂的操作。例如,要对数组进行排序,可以利用 awk 的一些技巧。假设有一个文件 words.txt,每行一个单词,要按单词长度对这些单词进行排序:
awk '{words[NR] = $1; len[NR] = length($1)} END {for (i = 1; i <= NR; i++) { for (j = i + 1; j <= NR; j++) { if (len[i] > len[j]) { temp = words[i]; words[i] = words[j]; words[j] = temp; temp_len = len[i]; len[i] = len[j]; len[j] = temp_len; } } } for (i = 1; i <= NR; i++) {print words[i];}}' words.txt

这里使用了两个数组,words 数组存储单词,len 数组存储每个单词的长度。在 END 块中,通过比较单词长度对 words 数组进行排序,最后打印排序后的单词。

高级 sed 技巧

  1. 使用脚本文件:对于复杂的 sed 操作,可以将多个命令写入一个脚本文件,然后通过 -f 选项来执行。例如,创建一个 sed_script.sed 文件,内容如下:
s/old_word1/new_word1/g
s/old_word2/new_word2/g
/skip_pattern/d

然后对文件 input.txt 执行这些操作:

sed -f sed_script.sed input.txt

这样可以将复杂的 sed 操作模块化,便于维护和复用。

  1. 保持空间与模式空间sed 有两个重要的缓冲区,模式空间(pattern space)和保持空间(hold space)。模式空间是默认处理文本的地方,而保持空间可以用于临时存储数据。例如,要交换文件中相邻两行的内容,可以利用这两个空间:
sed '1!G;h;$!d' file.txt

解释如下: - 1!G:除了第一行外,将保持空间的内容追加到模式空间。 - h:将模式空间的内容复制到保持空间。 - $!d:除了最后一行外,删除模式空间的内容(即不输出当前行,而是输出下一行交换后的内容)。

与其他工具结合的高级应用

  1. grep 结合grep 用于文本搜索,与 awksed 结合可以实现更强大的功能。例如,要在一个大的日志文件中搜索特定错误信息,并对包含错误信息的行进行进一步处理。假设日志文件 big_log.log,要搜索 “ERROR: Network connection lost”,并提取出相关时间和错误详情:
grep 'ERROR: Network connection lost' big_log.log | awk '{print $1, $2, $(NF - 1), $NF}'

这里先用 grep 筛选出包含特定错误信息的行,然后通过 awk 提取出每行的前两个字段(假设是时间相关信息)以及最后两个字段(假设是错误详情)。

  1. xargs 结合xargs 可以将标准输入转换为命令行参数。例如,有一个文件 files.txt 列出了多个文件的名称,要对这些文件中的内容进行统一的 sed 替换操作:
cat files.txt | xargs -I {} sed -i 's/old_text/new_text/g' {}

这里 -I {} 表示用 {} 替换 xargs 读取的每一项(即文件名),-i 选项表示直接在文件中进行替换,而不是输出到标准输出。

通过不断探索 awksed 与其他工具的结合使用,可以进一步拓展它们在文本处理领域的应用范围,满足各种复杂多变的实际需求。无论是在系统运维、数据分析还是软件开发过程中的文本处理任务,这些工具的组合使用都能带来高效和便捷。在实际应用中,还需要根据具体情况灵活调整命令和参数,以达到最佳的处理效果。同时,要注意处理过程中的数据准确性和性能优化,避免因不当操作导致数据丢失或处理效率低下的问题。随着对这些工具理解的深入,开发者能够更加熟练地运用它们解决各种文本相关的难题,提升工作效率和质量。