【问题标题】:Using regex in Bash with mapfile在带有 mapfile 的 Bash 中使用正则表达式
【发布时间】:2020-06-09 23:34:50
【问题描述】:

编辑 2

最小输入文件:input/input.txt

#-----------
snapshot=83
#-----------
time=30142088
mem_heap_B=20224
mem_heap_extra_B=8
mem_stacks_B=240480
heap_tree=empty
#-----------
snapshot=84
#-----------
time=30408368
mem_heap_B=20224
mem_heap_extra_B=8
mem_stacks_B=240552
heap_tree=empty
#-----------
snapshot=85
#-----------
time=30674648
mem_heap_B=20224
mem_heap_extra_B=8
mem_stacks_B=240464
heap_tree=empty
#-----------
snapshot=86
#-----------

实际输出:

input.txt/*
time, heap, stack


input/input.txt
time, heap, stack
30674648, 20224, 240464


input/input.txt
time, heap, stack
input/input.txt
time, heap, stack
input/input.txt
time, heap, stack
30674648, 20224, 240464

预期输出:

input.txt
time, heap, stack,
30142088, 20224, 240480
30408368, 20224, 240552
30674648, 20224, 240464

编辑:最初,问题可能是由于 Bash 的正则表达式缺乏多行功能。但是,从文本中删除换行符后,问题仍然存在,只是输出现在有 1 到 5 行而不是 0 行。

我正在尝试编写一个 Bash 脚本来将文本文件解析为具有所需信息的所需 CSV 文件。

作为脚本的一部分,我会遍历 n 个文件。每个文件都包含给定正则表达式的 m 个匹配项,每个匹配项包含三个捕获组。

我想将三个捕获组格式化为一个CSV行,然后将所有文件的所有匹配项的所有行连接起来,并将它们写入一个*.csv文件。

我很喜欢在 Kotlin 或 C# 等高级语言中使用 Regex,但是我没有在 Bash 中使用 Regex 的经验。我使用this 回答作为起点,但它似乎对我不起作用(mapfile -t matches < <( format_row "$text" "$regex" ) 没有做任何事情。

这里是完整的代码,相关部分注明:

#!/bin/bash


# RELEVANT CODE BELOW

regex="time=([0-9]+)\nmem_heap_B=([0-9]+)\n.*\nmem_stacks_B=([0-9]+)"

format_row() {
    local s=$1 regex=$2
    while [[ $s =~ $regex ]]
    do
            time="${BASH_REMATCH[1]}"
            heap="${BASH_REMATCH[2]}"
            stack="${BASH_REMATCH[3]}"

            echo "${time}, ${heap}, ${stack}"
            echo ""

            s=${s#*"${BASH_REMATCH[3]}"}
    done
}

for file in $1/*
do
    echo "Parsing ${file}..."

    echo $file >> $2
    echo "time, heap, stack" >> $2

    text=$(<${file})
    mapfile -t matches < <( format_row "$text" "$regex" )

    printf "%s\n" "${matches[@]}" >> $2
    echo "" >> $2
done


echo ""
echo "Done"

谢谢!

【问题讨论】:

  • 只隔离有问题的代码而不是整个脚本怎么样?请阅读MCVE。 bash 正则表达式不支持多行模式
  • 可能发生的情况是您假设您知道 bash 实现的正则表达式语言。它可能与您习惯的完全不同。不知道 bash 用什么,不过可能和linuxize.com/post/regular-expressions-in-grep类似
  • perl 可能会为您提供更像您习惯的结果?
  • @JoelFan ya 我认为事实证明就是这样,我习惯于使用“标准”高级正则表达式。谢谢!
  • 我认为他们建议使用 perl,而不是 bash 或 grep

标签: regex bash shell unix


【解决方案1】:

这里有两个问题:

  1. 虽然 bash 的 =~ 运算符可以匹配换行符,但它不理解 \n 转义序列。您必须在正则表达式中使用实际的换行符。这也可以通过 C 风格的字符串 $'\n' 来实现。

  2. 正则表达式量词* 是贪婪的。匹配时...
    [[ "a=1,b=1 a=2,b=2 a=3,b=3" =~ a=(.).*b=(.) ]]
    ...您最终会得到 BATCH_REMATCH=(1 3) 而不是 (1 1)
    在 PCRE 等其他正则表达式方言中,您可以使用非贪婪量词 *?。但是,在 bash 中,我们必须使用一种解决方法,并且必须将 .* 替换为无法匹配的内容,例如
    [[ "a=1,b=1 a=2,b=2 a=3,b=3" =~ a=(.)[^=]*b=(.) ]]
    在您的情况下,我们必须确保下一个 mem_stacks 不匹配

由于您没有发布任何示例输入和预期输出,我只能猜测。但是,我认为以下正则表达式可以为您工作:

regex=$'time=([0-9]+)
mem_heap_B=([0-9]+)
([^\n]*\n){TODO set number of lines allowed here}
mem_stacks_B=([0-9]+)'

请注意,现在您必须使用 BASH_REMATCH[4] 而不是 [3]

在标记的位置,您必须插入mem_heapmem_stacks 之间允许的行数。该数字可以是常数(例如{5})或范围(例如{1,10})。在范围的情况下,您必须确保最大界限不会太高,以至于您可能会意外跳过下一个 mem_stacks 并匹配另一个 mem_stacks。因此,在范围的情况下,使用两个匹配项可能更合适。类似的东西

regex1='time=([0-9]+)
mem_heap_B=([0-9]+)'
regex2='mem_stacks_B=([0-9]+)'

while
  [[ "$s" =~ $regex1 ]] &&
  time="${BASH_REMATCH[1]}" &&
  heap="${BASH_REMATCH[2]}" &&
  [[ "$s" =~ $regex2 ]] &&
  stack="${BASH_REMATCH[1]}"
do
  echo "$time, $heap, $stack"
  s="${s#*$stack}"
done >> "$2"

顺便说一句:

  • https://www.shellcheck.net/ 帮助您使脚本更加健壮。
    首先也是最重要的:引用你的变量。
  • 您可以使用do cmd1; cmd2 done &gt;&gt; file 代替do cmd1 &gt;&gt; file; cmd2 &gt;&gt; file; done
  • mapfile -t matches &lt; &lt;(format_row "$text" "$regex")
    printf "%s\n" "${matches[@]}" &gt;&gt; "$2"
    可以写成
    format_row "$text" "$regex" &gt;&gt; "$2"

【讨论】:

    猜你喜欢
    • 2017-05-04
    • 2021-02-19
    • 1970-01-01
    • 2010-09-18
    • 2022-08-12
    • 2011-01-26
    • 2019-08-10
    • 2013-10-21
    相关资源
    最近更新 更多