【问题标题】:SED: multiple patterns on the same line, how to match/parse first oneSED:同一行上的多个模式,如何匹配/解析第一个
【发布时间】:2023-03-10 10:19:01
【问题描述】:

我有一个文件,里面有电话号码数据,还有一些没用的东西。 我正在尝试解析数字,当只有 1 个电话号码/线路时,这不是问题。 但是当我有多个数字时, sed 匹配最后一个(即使它说它应该只匹配第一个模式?),我无法得到其他数字..

我的数据.txt:

bla bla bla NUM:09011111111 bla bla bla bla NUM:08022222222 bla bla bla

当我解析数据时,我的想法是首先删除第一个电话号码前面的所有“初始”“bla bla bla”(所以我搜索第一次出现的 'NUM:'),然后我删除电话号码后面的所有东西,并获取号码。 之后我想从剩余的字符串中解析下一个匹配项。

所以现在当我尝试 sed 时,我总是得到最后一个数字:

>sed 's/.*NUM://' data.txt
08022222222 bla bla bla
> 

首先我想了解我对 SED 的理解有什么问题。当然欢迎更有效的建议! 我的 sed 命令不是说,用 ''(空)替换 'NUM:' 之前的所有内容吗?为什么它总是匹配最后一次出现?

谢谢!

【问题讨论】:

  • Sed 是贪婪的。如果有第二个 NUM:,第一个可以被 .* 消费。
  • +1 用于示例数据、隐含的预期输出和一些不起作用的示例代码。祝你好运。

标签: regex parsing sed last-occurrence


【解决方案1】:

这可能对你有用:

echo "bla bla bla NUM:09011111111 bla bla bla bla NUM:08022222222 bla bla bla" |
sed 's/NUM:/\n&/g;s/[^\n]*\n\(NUM:[0-9]*\)[^\n]*/\1 /g;s/.$//'
NUM:09011111111 NUM:08022222222

您遇到的问题是了解.* 是贪婪的,即它匹配最长的匹配而不是第一个匹配。通过在我们感兴趣的字符串 (NUM:...) 前面放置一个唯一字符(\n sed 将其用作行分隔符,因此它不能存在于行中)并删除所有不是唯一字符 @987654325 @ 后跟唯一字符 \n,我们有效地将字符串拆分为可管理的部分。

【讨论】:

  • 我怀疑这确实与贪婪有关。
  • 哇,这回答了我长时间搜索基于字符而不是基于行的 sed 工作的示例。我看到我们将换行符作为标记粘贴在基于行的模式空间中,然后删除以该标记结尾的部分以对抗 sed 的贪婪匹配。
  • OSX:'\n' 不适用于 sed。请改用“gsed”(可通过​​ Brew 安装)。
【解决方案2】:

如你所知,sed 正则表达式是贪婪的,据我所知,不能不贪婪。

到目前为止尚未提出的两种替代方法是仅使用其他工具进行这种匹配/提取。

您可以使用perl 作为带有-pe 参数的sed 的直接替代品。它支持?非贪婪修饰符:

$ perl -pe 's/.*?NUM://' data.txt
09011111111 bla bla bla bla NUM:08022222222 bla bla bla

您可以使用 GNU grep 的 -o 选项来仅获取与正则表达式匹配的数据位:

$ egrep -o 'NUM:[0-9]*' data.txt 
NUM:09011111111
NUM:08022222222

【讨论】:

  • 感谢您提出替代方案,我一定会研究 sed 和 perl 之间可能存在的性能差异
  • 感谢 egrep 的建议。太糟糕了 sed 将自己限制为占用整行的模式空间。
【解决方案3】:

如果数字由NUM: 后面的数字定义:

sed -n -e 's/$/\n/' -e ':begin' \
  -e 's/\(NUM:[0-9][0-9]*\)\(.*\)\n\(.*\)/\2\n\3 \1/' \
  -e 'tbegin' -e 's/.*\n //' -e '/NUM/p'

这是做什么的:

  1. 在行尾添加\n 作为标记。
  2. 尝试在标记之前找到一个数字,并将其放在行尾(标记之后)。
  3. 如果找到数字,请转到上面的 2。
  4. 当标记前没有数字时,删除数字前的所有内容。
  5. 如果有数字在行,打印它(以处理没有找到数字的情况。

也可以反过来做,先去掉没有数字的行:

sed  -e '/NUM/!d' -e 's/$/\n/' -e ':begin' \
  -e 's/\(NUM:[0-9][0-9]*\)\(.*\)\n\(.*\)/\2\n\3 \1/' \
  -e 'tbegin' -e 's/.*\n //'

【讨论】:

  • 感谢您花时间给我一个替代解决方案,我会研究它。但是,这似乎有点难以理解,而且这里有很多对 sed 的调用,我担心性能比“3调用解决方案”要慢
  • 只有一个对sed 的调用,只是一个更复杂的脚本,包含 6 个命令。你是对的,potong 的解决方案只有 3 个命令,但是这些命令执行了不止一次(s 命令的g 参数),所以并不意味着它更快。我同意这个问题更优雅一点。
【解决方案4】:

你可以使用这个模式:

sed -r 's/^(.*NUM:)(.*NUM:.*)$/\2/'

【讨论】:

    【解决方案5】:
    sed -E 's/(-y)|(-f)|(\+incdir\+)/\n&/g' abcfile > cdeop
    

    其中 abcfile 将以 -y / -f / +incdir+ 模式为例,当模式匹配时,它将在其前面插入新行。

    【讨论】:

      猜你喜欢
      • 2020-08-04
      • 1970-01-01
      • 2015-05-07
      • 2018-04-30
      • 1970-01-01
      • 2018-10-04
      • 2021-04-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多