使用 awk 作为流编辑器

joe*_*epd 2 sed awk text-processing

awk是文本处理的瑞士军刀。但是,如果我需要更改文本中的小部分,我sed通常会联系。虽然它可能是完成这项工作的最佳工具,但了解如何使用其他工具完成如此简单的任务是值得的。我将如何awk用作流编辑器的替代品sed

特别是,使用以下文件text

Comparing apples with oranges.
Comparing rockets with bicycles.
Run Code Online (Sandbox Code Playgroud)

如何实现以下结果awk

sed 's/apples/fruit/' text
sed 's/apples\|oranges/fruit/g' text
Run Code Online (Sandbox Code Playgroud)

作为奖励,我如何awk使用这些函数更改变量?

joe*_*epd 7

流编辑器是一种特殊类型的过滤器。过滤器是一个程序,它接受标准输入上的文本,执行一些魔术,然后将其吐出到标准输出上。 grep,而且基本上coreutils都是过滤器。流编辑器是一种特殊类型的过滤器:它对传入的文本应用一个或多个编辑命令。

在 中awk,可以使用以下三个函数:subgsubgensub,概要如下:

sub(regexp, replacement [, target])
gsub(regexp, replacement [, target])
gensub(regexp, replacement, how [, target])
Run Code Online (Sandbox Code Playgroud)

在所有这三个函数中,如果target省略,$0则假定当前行 ( )。

sub 和 gsub

我们先来看看sub

$ awk '{rt = sub(/apple|orange/, "fruit"); print rt, $0}' text 
1 Comparing fruits with oranges.
0 Comparing rockets with bicycles.
Run Code Online (Sandbox Code Playgroud)

这里,sub()-function的返回值存储在rt. 正则表达式/apple|orange/,意思是匹配appleorange应用一次。调用之后什么都不会发生sub,但是在后台,当前行发生了变化,返回值有一个值。

返回值是0未进行任何更改时的值,这意味着如果 sub 在 之外应用{action},则可用于模拟sed.

$ awk 'sub(/apple|orange/, "fruit")' text    
Comparing fruits with oranges.
Run Code Online (Sandbox Code Playgroud)

现在,由于只有第一行发生了变化,因此只打印了第一行。请记住,如果未指定,则操作是打印该行。

要模拟sed 's/apple/fruit/' text,可以这样写:

$ awk 'sub(/apple|orange/, "fruit") || 1' text
Comparing fruits with oranges.
Comparing rockets with bicycles.
Run Code Online (Sandbox Code Playgroud)

现在,将尝试第一个功能。如果某些内容已被替换,则返回值非零,并打印该行。如果没有被替换,PATTERN则将尝试第二个测试,它恰好总是非零,即1。结果,将打印(未修改的)行。

另一种编写相同且可能更惯用的方法是:

$ awk '{sub(/apple|orange/, "fruit")};1' text 
Comparing fruits with oranges.
Comparing rockets with bicycles.
Run Code Online (Sandbox Code Playgroud)

在这里,尝试更改第一个ACTION块中的当前行。的返回码sub将被默默忽略。什么都不会打印。第二个PATTERN{ACTION}-block ( 1),始终匹配,并且默认操作 idf top 打印它,无论是修改行还是未修改行。

您已经注意到第一行的第二个匹配项orange没有被替换。一种解决方案是将sub-function包装在 while 循环中:

$ awk '{while (sub(/apple|orange/, "fruit")){}};1' text
Comparing fruits with fruits.
Comparing rockets with bicycles.
Run Code Online (Sandbox Code Playgroud)

只要sub返回一个非零值, sub 就会被重复。作为一个方便的简写,并且由于 while 循环在PATTERNa 中不起作用,gsub因此引入了一个函数。

$ awk 'gsub(/apple|orange/, "fruit")' text             
Comparing fruits with fruits.
Run Code Online (Sandbox Code Playgroud)

这意味着sed 's/regex/replacement/g'可以awk像这样模拟著名的:

awk '{gsub(/apple|orange/, "fruit")};1' text
Run Code Online (Sandbox Code Playgroud)

gensub:无副作用

警告gensub不在 POSIX awk 标准中,并且可能在您的安装中不可用。它在gawk, 中可用busybox awk,但在mawk和 中不可用nawk

这些机制已经展示了一些如何使用变量的工作方式。变量就地改变了。

$ awk '{a=$0; rt=sub(/apple|orange/, "fruit", a); print rt, a, $0}' text
1 Comparing fruits with oranges. Comparing apples with oranges.
0 Comparing rockets with bicycles. Comparing rockets with bicycles.
Run Code Online (Sandbox Code Playgroud)

这可能不是您想要的。计算中的一个合理原则是不处理输入本身,而是处理输入的副本。如果您不想更改输入,而是将替换结果分配给新变量怎么办?输入gensub

$ awk '{rt=gensub(/apple|orange/, "fruit", "g"); print rt, $0}' text
Comparing fruits with fruits. Comparing apples with oranges.
Comparing rockets with bicycles. Comparing rockets with bicycles.
Run Code Online (Sandbox Code Playgroud)

这里,返回值不是返回值,而是将结果字符串赋给变量 rt。第四个参数现在是默认值,$0。

gensub 的第三个参数是如何。此参数的合理值是“g”或“G”,代表全局。这将使用替换字符串更改所有出现的 /regex/。还可以指定一个正整数 i,其中第 i 个出现将被替换。

$ gawk '{print gensub(/apple|orange/, "fruit", 1)}' text
Comparing fruits with oranges.
Comparing rockets with bicycles.

$ gawk '{print gensub(/apple|orange/, "fruit", 2)}' text
Comparing apples with fruits.
Comparing rockets with bicycles.

$ gawk '{print gensub(/apple|orange/, "fruit", 3)}' text
Comparing apples with oranges.
Comparing rockets with bicycles.

$ gawk '{print gensub(/apple|orange/, "fruit", "g")}' text
Comparing fruits with fruits.
Comparing rockets with bicycles.
Run Code Online (Sandbox Code Playgroud)

如果 how 不是正整数,或者不是以 G 或 g 开头的字符串,gawk 将发出警告。

注意 gensub 的另一个惯用用法:直接打印替换的结果。最后一种形式也可以作为sed 's/regex/replacement/g'命令的替代品。

使用替换字符串做更多事情

到目前为止,我们已经完成了一些直接的字符串替换。如果要修改匹配的字符串怎么办?

有一些特殊的变量可以捕获匹配的文本。使用 POSIX-conform sub 和 gsub,可以用 & 重复匹配的部分:

$ awk '{rt=gsub(/apple|orange/, "a basket of &"); print rt, $0}' text
2 Comparing a basket of apples with a basket of oranges.
0 Comparing rockets with bicycles.
Run Code Online (Sandbox Code Playgroud)

从 sed 和 perl/PCRE 中已知的带有编号匹配的奇特事物对于 sub 和 gsub 变体来说太现代了。gensub 可以用 & 做同样的事情,但是当你在正则表达式中使用分组来指定你的正则表达式时,会多一点:

$ awk '{rt=gensub(/(appl|orang)(e)/, "a basket of \\1\\2","g"); print rt}' text
Comparing a basket of apples with a basket of oranges.
Comparing rockets with bicycles.
Run Code Online (Sandbox Code Playgroud)

TL; DR

将 sub 和 gsub 用于快速而肮脏的任务:

  • 当你想立即改变一个变量,而不关心它的旧值时
  • 当您希望根据是否进行替换来执行操作时,通过使用返回码

使用gensub在其它情况下:

  • 它在替换字符串中提供了更精细的反向引用
  • 如果你想保持原始变量不变
  • 如果要将结果分配给变量

  • 最近的 GNU `awk` 版本也有 `-iinplace` 选项,其作用类似于 `sed -i`。 (2认同)