使用awk从目标正则表达式向前和向后输出文件的部分

时间:2014-01-31 01:33:52

标签: regex bash awk

这是Using awk/find to output result and file name的扩展,我在那里找到了如何使用awk输出文件名和由开始和结束正则表达式匹配的文件部分。

所以,如果我的文件 fileThree.txt 包含内容

XXX >>
 xxx one
 xxx two
 xxx three
<<

ZZZ >>
 zzz one
 zzz two
 zzz three
<<

然后这个命令:

awk '/XXX/,/<</{print a[FILENAME]?$0:FILENAME RS $0;a[FILENAME]++}' *.txt

会输出

/d/Temp/temp/fileTwo.txt
XXX >>
 xxx one
 xxx two
 xxx three
<<

我喜欢这个并且每天都在使用它,但我想进一步扩展它,并且没有弄清楚如何。基本上我想说“在y和z之间搜索x,输出y和z之间的所有行(包括行)”。

所以,我想搜索“xxx two”并从“&gt;&gt;”行开始获取该“块”内的所有内容并以“&lt;&lt;”结尾的行结束 - 即它将具有与上述完全相同的输出。


更新:2014年1月31日星期五,下午03:53:29

显示@ Endoro的suggestion的结果,该结果输出不正确。命令:

awk '/xxx one/{f=7};/>>/{delete(s)};{s[++i]=$0};/<</&&f {print FILENAME;for (j in s) print s[j];f=0}' *.txt

输出:

fileThree.txt
 xxx three
<<
XXX >>
 xxx one
 xxx two
fileTwo.txt
XXX >>
 xxx one
 xxx two
 xxx three
<<

更新:2014年2月4日星期日

回应@ EdMorton的回答,这些文件只是示例,一般格式是“记录”以任何以“&gt;&gt;”结尾的行开头并以任何只包含“&lt;&lt;”的行结束。这意味着记录可以包含空行。


更新:2014年2月3日星期一,上午11:49:22

在回顾@ EdMorton的回答时,我设计了一个以这种方式在脚本中使用的解决方案:

# Set these based on input arguments.
ignoreCase=
searchTerm=
directory=
# Then do the search
gawk -v RS='\n<<\n+' "BEGIN{IGNORECASE=$ignoreCase} /${searchTerm}/{print FILENAME ORS \$0 ORS \"<<\"}" "${directory}"/*.txt | less -I -p "$searchTerm"

3 个答案:

答案 0 :(得分:4)

根据您发布的输入格式,使用awk获取所需输出的方法是:

awk -v RS= '/xxx two/{print FILENAME ORS $0}' file

请参阅:

$ cat file
XXX >>
 xxx one
 xxx two
 xxx three
<<

ZZZ >>
 zzz one
 zzz two
 zzz three
<<
$
$ awk -v RS= '/xxx two/{print FILENAME ORS $0}' file
file
XXX >>
 xxx one
 xxx two
 xxx three
<<

或者,根据您更新的问题中的信息,记录可以包含空行,使用GNU awk进行多字符RS:

$ gawk -v RS='\n<<\n+' '/xxx two/{print FILENAME ORS $0 ORS "<<"}' file
file
XXX >>
 xxx one
 xxx two
 xxx three
<<

或(接受你的选择):

$ gawk -v RS='\n<<' '/xxx two/{sub(/^\n+/,""); print FILENAME ORS $0 RT}' file
file
XXX >>
 xxx one
 xxx two
 xxx three
<<

或者如果您在记录之间确实没有空白行或者确实有它们但是不关心它们是否在输出中重现:

$ gawk -v RS='\n<<\n' '/xxx two/{printf "%s", FILENAME ORS $0 RT}' file
file
XXX >>
 xxx one
 xxx two
 xxx three
<<

顺便说一句,如果你不得不用非gawk这样做,那么你有2个主要选择:

1)将您的真实RS映射到单个字符:

$ awk '{sub(/<</,SUBSEP)}1' file | awk -v f=file 'BEGIN{RS=SUBSEP} /xxx two/{print f ORS $0 "<<"}'
file
XXX >>
 xxx one
 xxx two
 xxx three
<<

2)或通过连接行创建记录字符串,例如:

$ awk '{rec = rec $0 ORS} /^<</{ if (rec ~ /xxx two/) printf "%s", FILENAME ORS rec; rec=""}' file
file
XXX >>
 xxx one
 xxx two
 xxx three
<<

无论哪种方式,您都不需要构建数组,设置标志,循环等。 - 始终只识别/创建记录并对每条记录进行RE比较。

答案 1 :(得分:1)

您可以使用对其进行测试:

awk '/xxx one/{f=7};/>>/{delete(s)};{s[++i]=$0};/<</&&f {print FILENAME;for (j in s) print s[j];f=0}' *.txt

要获得有序输出,请参阅@ EdMorton的评论:

awk '/zzz one/{f=7}/>>/{delete(s);i=0}{s[++i]=$0}/<</&&f {print FILENAME;for (j=1;j<=i;j++) print s[j];f=0}' *.txt

答案 2 :(得分:1)

当Endoro提交时,我正在研究这个问题。我认为这在多行上更具可读性。 Endoro的解决方案与此解决方案之间的主要区别 - 这一点保持读取行的顺序并丢弃不包含搜索文本的匹配块:

#!/bin/sh

awk '/>>/ { p=1 }
p     { a[i++]=$0; if(/xxx two/) m=1 }
/<</  {
    if(m) {
        print FILENAME
        for( j=0; j<i; j++ ) { print a[j] }
        m=0
    }
    p=0; i=0; delete a
}' $*

通过awk阻止,它基本上是:

  • 启动模式
  • 当“在块中”时将行存储在“索引”数组中,如果块匹配,则设置标志
  • 在模式的末尾,按顺序打印出数组,然后重置变量并清除数组

这是带有额外; s

的“单行”版本
awk '/>>/ {p=1} p {a[i++]=$0; if(/xxx two/) m=1} /<</{if(m){print FILENAME; for(j=0;j<i;j++) {print a[j]} m=0 } p=0; i=0; delete a}' *.txt