我正在寻找一种可能性,以重复的文本模式捕获它们之间的所有可变数量的行,然后在 bash 中对其执行操作。
示例文本:
Total:
text1
text2
Total:
text3
Total:
Text1
Text4
Text5
我的目标基本上是对匹配项进行 for 循环Total:
,然后对其执行操作,这始终是后续潜台词的第一部分。
就像高级语言一样:
for (cat filename = every "Total:" do <something> end
现在对我来说有趣的部分基本上是如何组织 for 循环?
在<something>
我想做的部分中,jq
和awk
。
结果基本上基于这三个匹配的示例文本:1.
Total:
text1
text2
Total:
text3
Total:
Text1
Text4
Text5
希望最后的描述能够描述它。
什么是捕捉这个的正确工具?那会是for
andgrep
或for
and的组合吗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)