【问题标题】:Perl one liner to simulate awk scriptPerl 一个班轮模拟 awk 脚本
【发布时间】:2017-06-14 11:57:31
【问题描述】:

我是awkperl 的新手,所以请多多包涵。 我有以下awk 脚​​本:

awk '/regex1/{p = 0;} /regex2/{p = 1;} p'

这基本上是打印从与 regex2 匹配的行开始的所有行,直到找到与 regex1 匹配的行。

例子:

 regex1
 regex2
 line 1
 line 2
 regex1
 regex2
 regex1

输出:

 regex2
 line 1
 line 2
 regex2

是否可以使用perl 单线来模拟这种情况?我知道我可以使用保存在文件中的脚本来做到这一点。

编辑:

一个实际的例子:

2017 年 5 月 24 日 17:00:06,827 [INFO] 123456 (Blah : Blah1) Service-name:: 单行内容

2017 年 5 月 24 日 17:00:06,828 [INFO] 567890 (Blah : Blah1) 服务名称:: 内容(可能跨越多行)

2017 年 5 月 24 日 17:00:06,829 [INFO] 123456 (Blah : Blah2) 服务名称:多行内容。打印对象[ ID1=fac-adasd ID2=123231
ID3=123108 状态=未知
代码=530007 目的地=CA
]

2017 年 5 月 24 日 17:00:06,830 [INFO] 123456 (Blah : Blah1) Service-name:: 单行内容

2017 年 5 月 24 日 17:00:06,831 [INFO] 567890 (Blah : Blah2) 服务名称:: 内容(可能跨越多行)

给定搜索键 123456 我想提取以下内容:

2017 年 5 月 24 日 17:00:06,827 [INFO] 123456 (Blah : Blah1) Service-name:: 单行内容

2017 年 5 月 24 日 17:00:06,829 [INFO] 123456 (Blah : Blah2) 服务名称:多行内容。打印对象[ ID1=fac-adasd ID2=123231
ID3=123108 状态=未知
代码=530007 目的地=CA
]

2017 年 5 月 24 日 17:00:06,830 [INFO] 123456 (Blah : Blah1) Service-name:: 单行内容

以下 awk 脚本可以完成这项工作:
awk '/[0-9]{2}\s\w+\s[0-9]{4}/{n = 0} /123456/ {n =1}n' file

【问题讨论】:

标签: perl awk range


【解决方案1】:
perl -ne 'print if (/regex2/ .. /regex1/) =~ /^\d+$/'

这有点疯狂,但它是这样工作的:

  • -n 在输入行上添加一个隐式循环
  • 当前行在$_
  • 两个裸正则表达式匹配(/regex2//regex1/)隐式测试 $_
  • 我们在标量上下文中使用..,这将它变成了一个有状态的触发器操作符

    我的意思是:X .. Y 以“假”状态开始。在“假”状态下,它只评估X。如果X 返回 false 值,则它保持在“false”状态(并且本身返回 false)。一旦X 返回一个真值,它就会进入“真”状态并返回真。

    在“真”状态下,它只计算Y。如果Y 返回 false,则它保持在“true”状态(并且本身返回 true)。一旦Y 返回一个真值,它就会进入“假”状态,但它仍然返回真。

  • 如果我们只使用 print if /regex2/ .. /regex1/,它也会打印所有终止的 regex1

  • 仔细阅读Range Operators in perldoc perlop 发现你可以区分范围的端点
  • ..返回的“true”值实际上是一个从1开始的序列号,所以可以通过检查1来识别范围的开始
  • 当到达范围的末尾时(即,我们即将再次从“true”状态移动到“false”状态),返回值将在末尾添加 "E0"

    "E0" 添加到整数不会影响其数值。 Perl 在需要时会隐式将字符串转换为数字,而 "5E0" 之类的只是科学记数法(意思是 5 * 10**0,即 5 * 1,即 5)。

  • ..返回的“false”值为空字符串""

我们检查.. 的结果是否与正则表达式/^\d+$/ 匹配,即所有数字。这不包括空字符串(因为我们需要至少一位数字才能匹配),因此我们不会打印范围之外的行。它还排除了我们范围内的最后一行,因为E 不是数字。

【讨论】:

  • 感谢您的解释。这确实很疯狂。实际上,我在问题中给出了一个非常一般的示例,您的代码适用于此。我还需要打印 regex1 和 regex2 在同一行的情况(优先考虑 regex2)。但我相信我可以自己做到这一点,感谢您的解释。
  • @AkhilAvinash 这听起来像my $p = /regex2/ .. /regex1/; print if $p && ($p == 1 || $p !~ /E/);这样的东西可以做到
  • 不,那没有做这项工作。当我们有某行 regex1 regex2 时,它只打印该行而不是后面的行,因为 $p 的值在该行本身内部以某种方式设置为 1E0。我相信每个 $_ 都与 /regex2/ 和 /regex1/ 匹配,因此范围在行本身内结束。让我知道是否有办法解决这个问题。
  • @AkhilAvinash 哦!如果我理解正确,那只是... 而不是..
  • 很抱歉,事情变得如此混乱。但还有另一个问题。当我们有:regex1 regex2\n multiple lines \n regex1 regex2 脚本不会打印最后一行。上面给出的实际示例是我正在测试的。我的脚本是perl -ne 'my $p = (/123456/.../[0-9]+ \w+ [0-9]{4}/); print if $p && ($p == 1 || $p !~ /E/);' file
【解决方案2】:

不确定 awk 是否会打印范围的开始和结束,但 Perl 会:

perl -ne 'if(/regex2/ ... /regex1/){print}' file

编辑:Awk(至少是 Gnu awk)也有一个范围运算符,所以这可以更简单地完成:

awk '/regex2/,/regex1/' file

【讨论】:

  • 我实际上需要脚本只打印开头和中间的行,不包括范围的结尾。 awk 脚本正是这样做的。有没有办法修改你的 perl 单行代码来做同样的事情?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多