Bash 一种高效的模式间多行返回的非贪婪方法

Bash 一种高效的模式间多行返回的非贪婪方法,bash,awk,sed,Bash,Awk,Sed,我有这样一个文件: bar 1 foo 1 how now manchu 50 foo 2 brown cow manchu 55 foo 3 the quick brown manchu 1 bar 2 foo 1 fox jumped manchu 8 foo 2 over the manchu 20 foo 3 lazy dog manchu 100 foo 4 manchu 5 foo 5 manchu 7 bar

我有这样一个文件:

bar 1
 foo 1
  how now
  manchu 50
 foo 2
  brown cow
  manchu 55
 foo 3
  the quick brown
  manchu 1
bar 2
 foo 1
  fox jumped
  manchu 8
 foo 2
  over the
  manchu 20
 foo 3
  lazy dog
  manchu 100
 foo 4
  manchu 5
 foo 5
  manchu 7
bar 3
bar 4
我想搜索“满语55”并收到:

FOONUMBER=2

(满语55上面的foo#)

巴农数=1

(在那个foo上面的横杆)

PHRASETEXT=“棕色奶牛”

(满语55上一行的文字)

因此,我最终可以输出:

棕色奶牛,酒吧一号,富二号

到目前为止,我已经用一些非常难看的grep代码实现了这一点,比如:

FOONUMBER=`grep -e "manchu 55" -e ^" foo" -e ^"bar" | grep -B 1 "manchu 55" | grep "foo" | awk '{print $2}'`

BARNUMBER=`grep -e ^" foo $FOONUMBER" -e ^"bar" | grep -B 1 "foo $FOONUMBER" | grep "bar" | awk '{print $2}'`

PHRASETEXT=`grep -B 1 "manchu 55" | grep -v "manchu 55"`
此代码有3个问题:

  • 这让我畏缩,因为我知道这很糟糕
  • 它很慢;我必须浏览成千上万的条目,而且花的时间太长了
  • 有时,就像我的例子中的第2栏、第4栏和第5栏一样,“满语”上面没有文字。在本例中,它错误地返回一个foo,这不是我想要的
我怀疑我可以用sed做这件事,比如:

FOONUMBER=`sed -n '/foo/,/manchu 55/p' | grep foo | awk '{print $2}'
不幸的是,塞德太贪婪了。我一直在读AWK和状态机,这似乎是一种更好的方法,但我仍然不太理解它,无法设置它

正如你们现在可能已经能够确定的那样,编程不是我赖以生存的职业,但最终我还是有了这个动力。我希望重写我已经做过的,以提高效率,希望不要太复杂,因为其他一些没有编程学位的可怜的草皮可能在将来某个时候不得不支持对它的任何更改。

我建议

sed -n '/foo/ { s/.*foo\s*//; h }; /manchu 55/ { x; p }' filename
这很简单:

/foo/ {         # if you see a line with "foo" in it,
  s/.*foo\s*//  # isolate the number
  h             # and put it in the hold buffer
}
/manchu 55/ {   # if you see a line with "manchu 55" in it,
  x             # exchange hold buffer and pattern space
  p             # and print the pattern space.
}
然后,这将打印
foo
之后
满语55
行之前看到的最后一个数字。基本上可以用相同的方法提取条号,对于短语文本,可以使用

 sed -n '/manchu 55/ { x; p }; h'
要在看到
满语55
之前保留该行。或者可能

 sed -n '/manchu 55/ { x; p }; s/^\s*//; h'
删除这样一行中的前导空格

如果您确定文件中只存在一行
满语55
,或者您只需要第一个匹配项,则可以替换
x;p
x;Pq
。打印结果后,
q
将直接退出。

使用awk:

awk -v nManchu=55 -v OFS=", " '
  $1 == "bar" {bar = $0}    # store the most recently seen "bar" line
  $1 == "foo" {foo = $0}    # store the most recently seen "foo" line 
  $1 == "manchu" && $2 == nManchu {print prev, bar, foo} 
  {prev = $0}               # remember the previous line
' file
输出

  brown cow, bar 1,  foo 2
使用“nManchu=100”输出运行


这样做的优点是只对文件进行一次遍历,而不是对文件进行三次解析以获得“bar”、“foo”和上一行。

没有时间写,但使用awk,你基本上只需保存最后一个条和foo以及你看到的行,当你击中目标行时将它们吐出来,当你得到一个新条时清除保存的foo。@Eleck你能写一个预期的输出吗?它在OP中,见黄色框。如果我搜索“满语55”,我希望能够返回foo下的单独变量、foo下的条以及foo内“满语”行上方的文本。+1这是执行请求的方法。我可能会将变量命名为“manguinr”或仅仅命名为“nr”或类似的名称,以避免第一眼看到(
$1==“manguin”&&&$2==manguin
令人困惑:-)。向上投票,只是一个副例:manguin 5(它将打印
foo 4,bar 2,foo 4
):我将添加一个
&&prev!~/(bar | foo)/
在打印行conditionTensibai中,很好的捕获,谢谢@格伦·杰克曼,这段代码飞过了我现有的垃圾,运行得很好。在看了一会儿之后,我想我对它的工作原理有了相当好的理解。我一定要好好读书@Tensibai,我确实想到了这一点,但我决定我不必为OP发明需求。@glennjackman我真的觉得这是作者解决方案中提到的3个问题中的最后一个问题的OP陈述(不确定我是否清楚…),这是一个伟大的一系列单行线,分别填充每个变量。我将在我的代码中的其他地方使用这个方法,我非常感谢您的详细解释。因为我知道
mangur 55
在我的文件中只存在一次,所以退出选项肯定会在运行时间上节省一点时间,这在我的脚本中是至关重要的。再次感谢!
  lazy dog, bar 2,  foo 3