【问题标题】:How to process each result -instead of line- of grep (oz) command (older then 2.25)如何处理每个结果 - 而不是行 - grep (oz) 命令(旧于 2.25)
【发布时间】:2017-02-06 10:26:30
【问题描述】:

从版本 2.25 开始,"bug" in grep 被固定,因此空字节而不是换行符用于终止输出行。 这与捕获和处理多行 grep 匹配的简单性一样好(参见示例)

不幸的是,我在生产中使用 grep 版本 2.20。 这意味着对于处理 \n 终止的日志文件,您无法将 grep 匹配与每一行输出区分开来。

因此我的问题:

当您遇到 2.25 之前的版本时,处理 grep (oz) 命令的每个结果的最有效方法是什么?

(注意:这是一个更复杂脚本的小示例,需要根据请求处理超过 10k 的大日志文件,因此我寻求“最有效”的解决方案)

一个简单的例子:

测试日志

flag test1
flag test2
flag test3
    test4
    test5
flag test6

test7

flag test8

test.sh

#!/bin/bash
#regex explained: 
#(?s)enable multiline pattern search
#(flag) capturegroup with pattern indicating new entry
#[[:blank:]] followed by a space
#(.*?) capturegroup for the rest of the entry, non-greedy
#(?=(?:\r\n|[\r\n])(flag)|\z) positive lookahead: 
# - stop when the next newline begins with flag 
# - OR if last entry is a match: proceed 'till end of entry

regex_multiline="(?s)(flag)[[:blank:]](.*?)(?=(?:\r\n|[\r\n])(flag)|\z)"
logfile="./test.log"

test1(){
    #this works only with grep 2.25 or higher, 
    #which returns a NULL-byte delimiter after each capture
    echo start
    while IFS= read -r -d '' line ; do
        printf '<test>%s</test>\n' "$line"
    done < <(grep -Pzo $regex_multiline $logfile)
    echo end
}

test2(){
    #I need this to work for each match, instead of each line
    echo start
    while IFS= read -r line ; do
        printf '<test>%s</test>\n' "$line"
    done < <(grep -Pzo $regex_multiline $logfile)
    echo end
}

测试 1 的结果是我想要的:

start
<test>flag test1</test>
<test>flag test2</test>
<test>flag test3
        test4
        test5</test>
<test>flag test6

test7
 </test>
<test>flag test8</test>
end

测试 2 结果

start
<test>flag test1</test>
<test>flag test2</test>
<test>flag test3</test>
<test>       test4</test>
<test>       test5</test>
<test>flag test6</test>
<test></test>
<test>test7</test>
<test> </test>
<test>flag test8</test>
end

【问题讨论】:

  • 测试 2 失败,因为您在 read 中删除了空分隔符 -d '',但 grep 继续产生输出,NULL 分隔
  • 不,测试 2 失败,-d '',因为 grep pre 2.25 not 产生空输出,而是 \n。 (如我的 OP 中的链接中所引用)。如果您尝试使用 grep start\nend
  • 是否允许使用其他工具或必须使用 grep 完成?
  • 是的,当然!但是当我开始使用echo "$this" | cut -d "that" 时,性能就会下降。我尝试使用tr '\n '\0' | grep -Pzo etc. 扭转局面,但我没能让它在while IFS= read -r -d '\n' line ; do 之后继续工作
  • 这样的东西有用吗? tr '\n' '-' &lt;file |sed 's/^flag/&lt;test&gt;\0/g; s/-flag/&lt;\/test&gt;\n&lt;test&gt;\0/g; s/&lt;test&gt;-/&lt;test&gt;/g; $s/$/&lt;\/test&gt;/' |tr '-' '\n' 性能好像很差,不过你试试看....

标签: regex linux bash shell grep


【解决方案1】:

我认为你最好在这里使用perl 而不是grep。您可以使用几乎未修改的正则表达式1,只需将其替换为\1\x002

regex_multiline="(?s)(flag[[:blank:]].*?)(?=(?:\r\n|[\r\n])flag|\z)"
perl -0777 -pe "s/$regex_multiline/\1\x00/g" < "$logfile"

1你的正则表达式有点奇怪,捕获组在你的 grep 命令的上下文中没有做任何事情(比如(flag))。我只是将您想要匹配的整个部分放入一个组中,以便它对应于替换部分中的\1。根据需要进行调整/我缺少一些东西。

2使用\1\0(用于“匹配组一”,“空字节”)实际上也可以,但这似乎有点令人困惑。

【讨论】:

  • 确实,正则表达式捕获组在 grep 中没有任何用处,但在原始脚本中,相同的正则表达式在 while read etc. ~= "$regex_multiline"&lt;tag&gt;${BASH_REMATCH[1]}&lt;/tag&gt; etc... 中重复使用
  • 抱歉耽搁了,但我肯定会尽快测试您的解决方案。我会告诉你的。
【解决方案2】:

我找到了解决办法。我猜这有点hack-isch,但它与grep 2.20 及更高版本一致。虽然不要将它与 grep 2.25 及更高版本一起使用。 它是 grep 与参数 -zon 的组合: -z(将输入视为一组行,每行以零字节结尾) -o(仅打印匹配行的匹配(非空)部分) -n(在其输入文件中使用从 1 开始的行号为每行输出添加前缀。)

此组合将在每个新匹配开始时输出一个“1:”。总是。 (不确定这是 grep 中的错误,还是设计使然,但使用选项 -z 和 -o 确实有意义)

1:flag test1
1:flag test2
1:flag test3
    test4
    test5
1:flag test6

test7

1:flag test8

因此,知道了这一点,这将导致以下查找和替换函数,该函数将从 1: 开始的每一行替换为一个空字节字符。请注意,每行末尾都需要一个空字节字符,因此我们必须为最后一行手动添加一个!

这可以通过:

sed -e 's/^1:/\x0/g' | sed -e '$a\x0' 或者 awk '{gsub(/^1:/,"\x0");}1' | sed -e '$a\x0'

(我认为 sed 对这种操作更有效/更快,但请不要把我束缚在这一点上。)

test2(){
    #This finally works!
    echo start
    while IFS= read -r -d '' line ; do
        printf '<test>%s</test>\n' "$line"
    done < <(grep -Pzon $regex_multiline $logfile | sed -e 's/^1:/\x0/g' | sed -e '$a\\x0' )
    echo end
}

【讨论】:

    猜你喜欢
    • 2015-12-22
    • 1970-01-01
    • 2015-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-20
    • 2012-12-24
    • 1970-01-01
    相关资源
    最近更新 更多