使用Gnu AWK

时间:2018-07-25 06:59:21

标签: awk

使用AWK,我正在通过将文本文件拆分为多个记录来对其进行处理。作为记录分隔符RS,我使用正则表达式。有没有一种方法可以获取找到的记录分隔符,因为RS仅代表正则表达式字符串?

示例:

BEGIN { RS="a[0-9]*. "; ORS="\n-----\n"}
  /foo/ {print $0 RS;}
END {}

输入文件:

a1. Hello
this
is foo
a2. hello
this
is bar
a3. Hello
this
is foo

输出:

Hello
this
is foo
a[0-9]*.
-----
Hello
this
is foo
a[0-9]*.
-----

如您所见,输出将RS打印为表示正则表达式的字符串,但不打印实际值。 如何获取记录分隔符的实际匹配值?

预期输出:

Hello
this
is foo
a1
-----
Hello
this
is foo
a3
-----

3 个答案:

答案 0 :(得分:4)

在符合POSIX的AWK中,记录分隔符RS仅是一个字符,因此很容易以的形式回叫它。

awk 'BEGIN{RS="a"}{print $0 RS}'
另一方面,

GNU AWK并不将RS限制为一个字符的字符串,而是允许其为任何正则表达式。在这种情况下,使用上述AWK会变得有些棘手,因为RS是正则表达式而不是字符串。

为此,GNU AWK引入了变量RT,该变量仅代表找到的记录分隔符。当RS是单个字符时,RT包含相同的单个字符。但是,当RS是正则表达式时,RT包含与正则表达式匹配的实际输入文本。

天真的,可以将您的AWK程序更新为:

BEGIN{RS="a[0-9]+[.] "; ORS="\n-----\n"}
/foo/{print $0 RT}

不幸的是,RT设置为在当前记录之后找到的值,并且似乎OP在当前记录之前请求了该值,因此您可以引入一个新变量pRT,该变量可以读为找到上一个记录分隔符

BEGIN{RS="a[0-9]+[.] "; ORS="\n-----\n"}
/foo/{print $0 pRT}{pRT=RT}

并且正如Shaki Siegalcomments中指出的那样,您仍然必须更新pRT来删除最后的空格和点:

BEGIN{RS="a[0-9]+[.] "; ORS="\n-----\n"}
/foo/{print $0 pRT}{pRT=RT;sub(/[.] $/,"",pRT)}

注释:OP(RS)的原始RS="a[0-9]*. "已更新,以改进与RS="a[0-9]+[.] "的匹配,从而确保了后面出现的数字a和实际的.

如果如原始示例所示,记录分隔符始终出现在行的开头,则应将RS稍微修改为RS="(^|\n)a[0-9]+[.] ",Dito注释也提出了许多要点。因此,如果字符串a[0-9]+.始终出现在开头,则您需要进行更多处理:

BEGIN {
   RS ="(^|\n)a[0-9]+[.] ";
   ORS="\n-----\n"
}
/foo/ {
   if (RT ~ /^$/ && NR != 2) pRT = substr(pRT,2)
   print $0 pRT 
}
{pRT=RT;sub(/[.] $/,"",pRT)}

在这里,我们添加了更正以修复最后一条记录。

  • 如果有两个以上的AWK记录(第一个记录始终为空),则需要从pRT中删除第一个换行符,否则您将包含由最后一条记录引起的额外换行以换行符结尾(与其他所有行相比)。
  • 如果只有两个AWK记录(在文本中一个有效),则您不应该进行此更正,因为第一个RT不是以换行开头

最终的改进是通过意识到我们始终会删除pRT中的初始换行符,因此我们可以将其合并为一个gsub

BEGIN {
   RS ="(^|\n)a[0-9]+[.] ";
   ORS="\n-----\n"
}
/foo/ { print $0 pRT }
{pRT=RT;gsub(/^\n|[.] $/,"",pRT)}

  

RS :输入记录分隔符。它的默认值是一个包含单个换行符的字符串,这意味着输入记录由一行文本组成。它也可以是空字符串,在这种情况下,记录由空白行分隔。如果是正则表达式,则记录将由输入文本中的正则表达式匹配项分隔。

     

RS成为正则表达式的能力是gawk的扩展。在大多数其他AWK实现中,或者如果gawk处于兼容模式(请参阅选项),则仅使用RS值的第一个字符。

     

ORS:输出记录分隔符。在每个打印语句的末尾输出。它的默认值是换行符“ \ n”。

     

RT(特定于GNU AWK)与记录分隔符RS表示的文本匹配的输入文本。每次读取记录时都会设置它。

     

来源:GNU AWK manual

答案 1 :(得分:1)

这可能对您有用(GNU sed):

sed -rn '/^a[0-9]+\.\s/{:a;x;/foo/{s/^(a[0-9]+\.)\s*(.*)/\2\n\1\n-----/p;$d};x;h;b};H;$ba' file

聚集以an.开头的行,其中n是整数。如果这些行包含单词foo,请进行必要的替换并打印结果,否则不执行任何操作。

道歉:开始解决问题时,这个问题被标记为sed

遇到以an.开头的行时,该行将替换保留空间中的所有行。但是,在此之前,首先检查保留空间,如果它包含单词foo,即已经存在一个集合,则满足要处理的要求,因此将行格式化并打印出来。其他行将追加到保留空间。当遇到文件结尾时,将满足特殊条件,该条件与行开始an.时的条件相同。这可以通过添加goto标签:a来实现。

答案 2 :(得分:1)

对于已经用于多字符RS的GNU awk,包含与RS regexp匹配的字符串的内置变量为RT

尽管我们需要修复您的RS设置,因为您需要RS的正则表达式在行首(a<integer><dot><blank>)匹配(^|\n)a[0-9]+[.]或在文件末尾单独换行(\n$),因此文件中的最后一条记录与所有其余记录的解析方式相同,下面是如何写入的记录。请注意,除了文件中的第一个匹配项外,RT都将以换行符开头,因此我们需要从RT中删除该前导换行符,以获取要为每条记录打印的实际标识符:

$ cat tst.awk
BEGIN {
    RS  = "(^|\n)a[0-9]+[.] |\n$"
    ORS = "\n-----\n"
}
/foo/ { print $0 "\n" id }
{ id = gensub(/^\n|[.] /,"","g",RT) }

这是给定此输入的结果,其中包括比问题多的雨天案例(您应对此进行测试其他提议的解决方案):

输入:

$ cat file
a1. Hello
this
is foo bat man

a2. hello
this
is bar
a3. Hello
this is a7. just fine
is foo

输出:

$ awk -f tst.awk file
Hello
this
is foo bat man

a1
-----
Hello
this is a7. just fine
is foo
a3
-----