Shell 使用awk或sed获取两个模式之间的第n个匹配项

Shell 使用awk或sed获取两个模式之间的第n个匹配项,shell,sed,awk,Shell,Sed,Awk,我有一个问题,我想通过一个文件的输出进行解析,我想抓住两个模式之间出现的第n个文本,最好使用awk或sed category 1 s t done category 2 n d done category 3 r d done category 4 t h done 让我们就这个例子来说,我想抓住类别和完成之间的第三个文本,基本上输出是 category 3 r d done 尝试这样做: awk -v n=3 '/^category/{l++} (l==n){print}' file.t

我有一个问题,我想通过一个文件的输出进行解析,我想抓住两个模式之间出现的第n个文本,最好使用awk或sed

category
1
s
t
done
category
2
n
d
done
category
3
r
d
done
category
4
t
h
done
让我们就这个例子来说,我想抓住类别和完成之间的第三个文本,基本上输出是

category
3
r
d
done
尝试这样做:

 awk -v n=3 '/^category/{l++} (l==n){print}' file.txt
或者更隐晦:

awk -v n=3 '/^category/{l++} l==n' file.txt
如果您的文件很大:

awk -v n=3 '/^category/{l++} l>n{exit} l==n' file.txt

如果您的文件不包含任何空字符,请使用
gnused
。这将找到模式范围的第三个匹配项。但是,您可以很容易地修改它以获得您想要的任何事件

sed -n '/^category/ { x; s/^/\x0/; /^\x0\{3\}$/ { x; :a; p; /done/q; n; ba }; x }' file.txt
结果:

category
3
r
d
done
说明:

使用
-n
开关关闭默认打印。在行首匹配单词“category”。将模式空间与保留空间交换,并在模式的开头追加一个空字符。在本例中,如果模式包含两个前导空字符,则将模式从保持空间中拉出。现在创建一个循环并打印图案空间的内容,直到最后一个图案匹配为止。当找到最后一个模式时,
sed
将退出。如果找不到它,sed将继续读取中的下一行输入并在其循环中继续。

这可能适用于您(GNU-sed):

使用
-n
选项关闭自动打印。收集
类别
完成
之间的行。将计数器存储在保留空间中,当计数器达到3时,在图案空间中打印集合并退出

或者,如果您更喜欢awk:

awk  '/^category/,/^done/{if(++m==1)n++;if(n==3)print;if(/^done/)m=0}'  file

使用GNU awk,可以将记录分隔符设置为正则表达式:

<file awk 'NR==n+1 { print rt, $0 } { rt = RT }' RS='\\<category' ORS='' n=3
RT
是匹配的记录分隔符。请注意,相对于
n
的记录将关闭1,因为第一条记录是指第一条
RS
之前的记录

编辑 根据教育署的意见,如果记录之间有其他数据,例如:

category
1
s
t
done
category
2
n
d
done

foo

category
3
r
d
done

bar
category
4
t
h
done
解决此问题的一种方法是使用第二个(或第一个)awk清理输入:

编辑2 正如Ed在评论中指出的,上述方法不搜索结束模式。实现这一点的一种方法是使用
getline
(注意,还有一些使用awk getline的方法),其他答案中没有涉及到这一点:


您在这方面遇到了什么问题?您有没有尝试过的代码不起作用?很抱歉,让我们假设开头和结尾不是同一个词,我希望在category和done之间出现第三个字符。
/^category/
表示以“category”开头的字符串,它与包含category的行完全不同。因此,无需任何修改,脚本仍按原样工作。这将与发布的示例输入一起工作,但如果出现“未完成的类别”或“完成”与“类别”之间的文本,则无法工作。这将在“类别”一词出现之间打印文本,而不是在“类别”与“完成”之间打印文本。在发布的输入中,这并不重要,但一般来说可能是这样,例如,如果f在“完成”和“类别”之间可以有其他文本,或者在“类别”出现时没有关联的“完成”。@EdMorton:True。一个可能的解决方法是首先清理输入,请参见编辑。如果在示例输入的第一个“完成”之前添加“类别”行,例如在“s”和“t”行之间,它仍然会失败并打印第二条记录,而不是第三条记录。@EdMorton:对,我明白了,没有搜索结束模式。我添加了一个
getline
选项,可以搜索
done
。我希望您将此作为与其他解决方案的对比,但对于OPs的好处,我认为值得明确指出的是,它附带了一些行李。sed是一个用于在单行线上进行简单替换的优秀工具。对于任何其他内容,只需使用awk,否则您会发现最微小的需求更改(例如,也打印行号)需要重新编写脚本,可能需要使用不同的语言。在sed中执行任何需要超过“s”和“g”命令的操作都是浪费时间。awk脚本将在“完成”之后继续打印“如果完成和下一个类别之间存在tes=xt。如果类别可以在没有完成的情况下存在,它也会打印错误的块。不知道sed脚本会做什么。@EdMorton我认为打印范围缩小到
类别
完成
之间,如果没有
完成
,这可能是用户需要的。请使用在第一个“完成”之前有两行“类别”的文件进行尝试。它将打印第二个类别->完成块,而不是第三个。只是好奇:为什么?它多次测试相同的条件,如果您的输入文件稍有更改,它将不起作用。如果您对只适用于发布的输入格式的解决方案感到满意,@sputnik的解决方案更简洁。我希望它打印第三个匹配项,但前提是第二个匹配项包含单词“awk”。我将如何修改sed命令来实现这一点?在awk中,我只需创建一个“prevRec”变量来存储以前的记录,并在打印之前添加一个
if(prevRec~/awk/)
category 
3
r
d
done
category
1
s
t
done
category
2
n
d
done

foo

category
3
r
d
done

bar
category
4
t
h
done
<file awk '/^category$/,/^done$/' |
  awk 'NR==n+1 { print rt, $0 } { rt = RT }' RS='\\<category' ORS='' n=3
category 
3
r
d
done
<file awk '
  /^category$/ {
    v = $0
    while(!/^done$/) { 
      if(!getline) 
        exit
      v = v ORS $0
    }
    if(++nr == n) 
      print v
}' n=3
<file awk '/^category$/ { v = $0; while(!/^done$/) { if(!getline) exit; v = v ORS $0 } if(++nr == n)  print v }' n=3
awk -v tgt=3 '
/^category$/ { fnd=1; rec="" }

fnd {
   rec = rec $0 ORS
   if (/^done$/) {
      if (++cnt == tgt) {
         printf "%s",rec
         exit
      }
      fnd = 0
   }
}
' file