如何捕获重复模式的所有行并对子结果执行一些操作

如何捕获重复模式的所有行并对子结果执行一些操作

我正在寻找一种可能性,以重复的文本模式捕获它们之间的所有可变数量的行,然后在 bash 中对其执行操作。

示例文本:

Total:
text1
text2
Total:
text3
Total:
Text1
Text4
Text5

我的目标基本上是对匹配项进行 for 循环Total:,然后对其执行操作,这始终是后续潜台词的第一部分。

就像高级语言一样: for (cat filename = every "Total:" do <something> end

现在对我来说有趣的部分基本上是如何组织 for 循环?

<something>我想做的部分中,jqawk

结果基本上基于这三个匹配的示例文本:1.

Total:
text1
text2
Total:
text3
Total:
Text1
Text4
Text5

希望最后的描述能够描述它。

什么是捕捉这个的正确工具?那会是forandgrepforand的组合吗awk

我只想使用 GNU 工具。所以没有perl或其他外部工具。

多谢。

答案1

没有正确的工具©,但有很多合适的工具,当然包括awk但不是外壳)。经典的方法是使用一个变量,当您找到字符串时该变量会更改值。例如,假设您想将每个部分连接在一起:

$ awk '
{ 
 if($0 == "Total:"){
   c++
 } 
 else{
   lines[c] = lines[c] ? lines[c]","$0 : $0
 }
}
END{
  for (c in lines){
    printf "Text for total %d:\n%s\n",c,lines[c]
  }
}' file 
Text for total 1:
text1,text2
Text for total 2:
text3
Text for total 3:
Text1,Text4,Text5

或者,如果您只想分隔它们,可以将记录分隔符设置为Total:并执行类似的操作(使用 GNU awk):

$ gawk -v RS="Total:" 'NR>1{ print "Section "(NR-1),$0}' file
Section 1 
text1
text2

Section 2 
text3

Section 3 
Text1
Text4
Text5

(更好的是,使用RS="(^|\n)Total:\n"艾德·莫顿的回答

这实际上完全取决于您想做什么。 Awk 是一种编程语言,您真正受限的只是您的想象力*

*假设该程序的主要目标是解析文本。尝试在 中实现 3D 射击游戏不会有太多乐趣awk,尽管如果有些疯狂的受虐狂勤奋的 awk 程序员已经做到了这一点。

答案2

使用 GNU awk 处理多字符RS, RT,并使用 NUL ( \0) 将文件分割为 NUL 分隔的多行记录:

while IFS= read -r -d '' rec; do
    printf '=====\n%s\n=====\n' "$rec"
done < <(
        awk -v rs='Total:' -v ORS='\0' '
            BEGIN { RS = "(^|\n)((" rs "\n)|$)" }
            NR>1 { print rs "\n" $0 }
        ' file
    )

使用任何 awk 并使用 Form-Feed ( \f) (或您知道不能出现在输入中的任何其他字符)将文件拆分为 FF 分隔的多行记录:

sep=$'\f'    # or whatever non-NUL character you prefer
while IFS= read -r -d "$sep" rec; do
     printf '=====\n%s\n=====\n' "$rec"
done < <(
        awk -v rs='Total:' -v ORS="$sep" '
            $0 == rs { if (NR>1) print rec; rec=$0; next }
            { rec = rec RS $0 }
            END { if (NR>1) print rec }
        ' file
    )

两者都会输出:

=====
Total:
text1
text2
=====
=====
Total:
text3
=====
=====
Total:
Text1
Text4
Text5
=====

将 替换printf为您要在每个多行记录上运行的任何命令。

说明:

RS您可以使用 GNU awk for multi-char 、RT以及使用 NUL ( \0) 将文件拆分为 NUL 分隔的记录,然后使用 bash 读取循环一次处理一个记录,但您喜欢这样做:

while IFS= read -r -d '' rec; do
    printf '=====\n%s\n=====\n' "$rec"
done < <(
        awk -v rs='Total:' -v ORS='\0' '
            BEGIN { RS = "(^|\n)((" rs "\n)|$)" }
            NR>1 { print rs "\n" $0 }
        ' file
    )

上面使用 awk 来完成它的设计任务,即操作文本,并使用 shell 完成它的设计任务之一,即对工具的序列调用。您可以在对 awk 的调用中完成这一切,使用system()在每个文本块上调用其他工具,但是然后您使用 awk 来执行 shell 设计的功能,即对工具的序列调用,因此生成的代码将是与我上面所做的那样直接从 shell 调用这些工具相比,更难编写健壮且更慢的代码(由于每个输入块生成一个子 shell)。

awk 脚本正在寻找由Total:它自己的行分隔的记录,因此我们需要设置RS为包含\n之前和之后,Total:否则它会匹配行上的任何位置,并且我们需要包含^作为之前的可能性Total:,因此它也匹配在输入开始时。在文件末尾,最后一条记录仅以 a 结尾,\n因此我们也需要添加该可能性 ( \n$) RS。请记住 - 尽管经常说,$并不意味着正则表达式中的行结束,它意味着字符串/缓冲区的结束,因此在输入RS文件$的末尾仅匹配,就像^仅在输入文件的开头匹配,而不是在每行的开头。

如果您不确定这意味着什么,只需添加一些跟踪print语句以转储RT$0每个记录的值,例如:

$ awk -v rs='Total:' -v ORS='\0' '
        BEGIN { RS = "(^|\n)((" rs "\n)|$)" }
        NR>1 {
            printf "NR=<%d>, $0=<%s>, RT=<%s>\n-----\n", NR, $0, RT
            #print rs "\n" $0
        }
    ' file
NR=<2>, $0=<text1
text2>, RT=<
Total:
>
-----
NR=<3>, $0=<text3>, RT=<
Total:
>
-----
NR=<4>, $0=<Text1
Text4
Text5>, RT=<
>
-----

记录编号从 2 开始,因为第一条记录是文件第一行之前的空字符串,因为第一行包含记录分隔符,Total:\n因此根据定义,必须有一些记录以该字符串结尾,即使它是空的。

如果您的 awk 不支持多字符 RS 和/或打印 NUL 字符,那么使用任何 awk 您可以一次构造记录 1 行,并选择您知道(希望!)不会出现在输入中的其他字符,例如一些控制字符,例如\r回车符或\f换页符,然后ORS更改 bash 读取循环以将其用作分隔符(参数-d ...),例如:

sep=$'\f'    # or whatever character you prefer
while IFS= read -r -d "$sep" rec; do
     printf '=====\n%s\n=====\n' "$rec"
done < <(
        awk -v rs='Total:' -v ORS="$sep" '
            $0 == rs { if (NR>1) print rec; rec=$0; next }
            { rec = rec RS $0 }
            END { if (NR>1) print rec }
        ' file
    )

NR>1本节中的检查END是为了让我们在给定空输入文件的情况下不打印空行,而是在这种情况下不输出任何内容。

答案3

我发现这个问题有点宽泛,但作为一个非常通用的答案,在 Perl 中,您可以根据模式匹配操作,然后对它们执行某些操作。

perl -wne '
  chomp; 
  if (/^(Total:)$/) { 
    $Last_Action = $1; 
    next 
  }; 
  print "Applying ${Last_Action} on line ${.}: ${_}\n"
' <test.input

print "Applying ${Last_Action} on line ${.}: ${_}\n" 是您想要更改的部分,以更改脚本对不同操作的响应方式。例如,您可以有一个 if 语句,该语句将根据最后匹配的操作执行不同的操作。您必须添加更多模式/^(Total:)$/才能捕获更多操作。

您没有确切地透露如何处理这些行,因此在这种情况下,我只是打印行号和将应用于它的操作,然后是行内容,但您可以对它们执行任何您想要的操作。

perl -wne 'chomp; if (/^(Total:)$/) { $Last_Action = $1; next }; print "Applying ${Last_Action} on line ${.}: ${_}\n"' <test.input
Applying Total: on line 2: text1
Applying Total: on line 3: text2
Applying Total: on line 5: text3
Applying Total: on line 7: Text1
Applying Total: on line 8: Text4
Applying Total: on line 9: Text5

答案4

这个问题是开放式的,没有特定输入所需的特定输出。有一种语言可以使用跨文本文档的多行模式提取数据:TXR

假设您的数据中有text4故意的重复:

Total:
text1
text2
 random
  junk
Total:
text3
 more
  random
 junk
Total:
text7
no
match
  here
Total:
text1
text4
text5

假设我们想要寻找一种模式,其中有一个两行Total:部分,然后在某个地方有一个单行部分,然后是第三个三行部分,其中第一行与第一行的第一行相匹配:

$ txr match.txr data
t1: text1
t2: text2
t3: text3
t4: text4
t5: text5

哪里match.txr

Total:
@text1
@text2
@(skip)
Total:
@text3
@(skip)
Total:
@text1
@text4
@text5
@(output)
t1: @text1
t2: @text2
t3: @text3
t4: @text4
t5: @text5
@(end)

做事的方法有很多种,具体取决于要求。我们可以简单地迭代以Total:等等开头的部分。

$ txr  tabulate.txr data
Total: text1,text2, random,  junk
Total: text3, more,  random, junk
Total: text7,no,match,  here
Total: text1,text4,text5

其中 `tabulate.txr 是:

@(collect)
Total:
@   (collect)
@line
@   (until)
Total:
@   (end)
@(end)
@(output)
@  (repeat)
Total: @{line ","}
@  (end)
@(end)

相关内容