【问题标题】:Sed Regex to delete all numbers except ordinalsSed Regex 删除除序数外的所有数字
【发布时间】:2018-05-26 17:36:58
【问题描述】:

我需要从文件中删除所有数字,除了 (ST|TH|[RN]D) 后面的数字(序数)。我不确定如何在 sed 中引入这样的异常(我知道 [^] 但这不会让我给字符串可选的 (ST|TH|[RN]D)。

看起来前瞻可能是答案,但我的构造不起作用

s/[0-9][0-9]*(?!(ST|[RN]D))//g

示例输入:

12663 METRO CONDOMINIUM AS DESC IN INST# 200800031138 UNIT A
126TH AVENUE INDUSTRIAL PARK
13 AND 12-29-19
102-1st AVE CONDO

刚刚添加了最后一个,这是一大堆输入。我真的很想消除前面的数字,但留下序数。 Revo 的例子效果很好。但这种极端情况对我来说实际上很重要。

预期输出:

METRO CONDOMINIUM AS DESC IN INST#  UNIT A
126TH AVENUE INDUSTRIAL PARK
 AND --
-1st AVE CONDO

不要关心消除空格。我可以自己做。

【问题讨论】:

  • Sed 不支持环视,这是一种 PCRE 构造(Perl 兼容的正则表达式); sed 仅支持基本和扩展正则表达式。
  • 我可以用什么工具代替?规定的正则表达式会起作用吗?
  • 您能添加示例输入吗?数字是在单独的行上还是在文本中?至于工具,我想到了 Perl。
  • 你想输出什么?
  • 请添加预期输出。

标签: regex perl sed regex-lookarounds


【解决方案1】:

Sed 不支持前瞻,但 Perl 支持。但是,您的正则表达式不太正确:在123RD 中,它匹配12(因为12 是一个数字序列,后面没有STNDRD;后面是@987654327 @)。

您可以通过在前瞻中添加 [0-9] 来解决此问题:

perl -pe 's/[0-9][0-9]*(?!([0-9]|ST|[RN]D))//g'

另外,前瞻组中不需要内部捕获括号,XX* 可以简化为 X+,我们也想排除 TH

perl -pe 's/[0-9]+(?![0-9]|ST|[RN]D|TH)//g'

测试输入的示例输出:

 METRO CONDOMINIUM AS DESC IN INST#  UNIT A
126TH AVENUE INDUSTRIAL PARK
 AND --
-st AVE CONDO

请注意,1st 中的 1 已被删除。这是因为S 不匹配s。我们可以通过使正则表达式不区分大小写来解决这个问题:

perl -pe 's/[0-9]+(?![0-9]|ST|[RN]D|TH)//ig' test.txt
 METRO CONDOMINIUM AS DESC IN INST#  UNIT A
126TH AVENUE INDUSTRIAL PARK
 AND --
-1st AVE CONDO

【讨论】:

  • 只使用非回溯量词:s/ \d++ (?!ST|TH|[RN]D) /igx
  • @Borodin 这也行得通,但我更喜欢在可能的情况下编写与回溯无关的正则表达式(即无论量词是贪婪的、非贪婪的还是所有格的,正则表达式的工作方式相同),在这种情况下是很容易成为可能。此外,您的代码有两个错误: 1. 错字:您缺少/。 2. \d 不仅仅匹配[0-9];它匹配所有 Unicode 数字字符。你需要/a 来解决这个问题。
【解决方案2】:

由于 sed 不支持环视,您必须使用以下方法定义每个路径:

[0-9]+(([sS]([^Tt]|$)|[Tt]([^Hh]|$)|[RNrn]([^Dd]|$))|[^RNSTrnst0-9]|$)

Live demo

为了不区分大小写,我将大写和小写都包含在括号符号中。

GNU sed 命令(POSIX ERE):

sed -r 's/[0-9]+(([sS]([^Tt]|$)|[Tt]([^Hh]|$)|[RNrn]([^Dd]|$))|[^RNSTrnst0-9]|$)/\1/g' file

正则表达式分解:

[0-9]+ # Match digits
( # Start of Capturing Group #1
    ( # Start of Capturing Group #2
        [sS] # Match S or s
        ( # Start of Capturing Group #3
            [^Tt] # If a character exists after S it shouldn't be T
            | # Or
            $ # Match end of line position
        ) # End of Capturing Group #3
        | # Or 
        [RNrn] # Match a letter from set
        ( # Start of Capturing Group #4
            [^Dd] # If a character exists after R or N it shouldn't be D
            | # Or
            $ # Match end of line position
        ) # End of Capturing Group #4
    ) # End of Capturing Group #2
    | # Or
    [^RNSrns0-9] # Match a letter from other than one in set
    | # Or
    $ # Match end of line position
) # End of Capturing Group #1

【讨论】:

  • 这是一个非常令人困惑的正则表达式。你能稍微解释一下吗?美元符号和 [^T] 和 [^D] 把我扔了。此外,需要支持序数“TH”。
  • 对不起,我要添加一个细分。
  • 与其他答案中的正则表达式相比,我无法想象 OP 是如何看待该正则表达式的,并认为“是的,这正是我希望我的代码看起来的方式!”但无论如何 - 你应该提到解决方案是 GNU sed 仅适用于-r
  • 我没有对你投反对票,否则我会留下评论说明原因。我当然不会仅仅因为您没有说明答案是 GNU-only 并且 OP 确实要求提供 sed 解决方案,所以我不能指责您提供了一个解决方案。你可能会注意到我的评论得到了赞成,所以也许这两件事是相关的,idk。是的,您使用的是 POSIX ERE,但您的答案需要 GNU sed 来执行它,所以我只是说明您应该声明 GNU sed 是必需的,这样未来的读者就不会浪费时间尝试使用非 GNU seds。
  • @EdMorton 我们可以使用 ERE 版本和 POSIX sed 吗?!无论如何,我更新以明确说明。
【解决方案3】:

也许这会让你走得更远:后面没有字母数字字符或行尾的数字序列

$ cat file
foo 1234 bar 32nd gaz 1234
1234hello

$ sed -E 's/[[:digit:]]+($|[^[:alnum:]])/\1/g' file
foo  bar 32nd gaz 
1234hello

【讨论】:

    【解决方案4】:

    sed 用于单个行的简单替换(例如s/old/new/),仅此而已。对于其他任何事情,您都应该使用 awk。使用 GNU awk 进行多字符 RS、RT 和 IGNORECASE:

    $ awk -v RS='[0-9]+(ST|TH|[RN]D)' -v IGNORECASE=1 '{gsub(/[0-9]+/,""); ORS=RT} 1' file
     METRO CONDOMINIUM AS DESC IN INST#  UNIT A
    126TH AVENUE INDUSTRIAL PARK
     AND --
    -1st AVE CONDO
    

    【讨论】:

    • sed 和 awk 在 Perl 出现的几十年前就已经过时了。它能够完成旧实用程序可以做的所有事情,而且perl -pe 's/ \d++ (?!ST|TH|[RN]D) /igx' myfile 可以做到这一点。
    • @Borodin 绝对正确。我想知道为什么没有发生? zoitz.com/archives/13 :-)。
    • @EdMorton 好吧,如果人们真的学习了 Perl 并在其中编写了好的代码(而不是害怕它并吐出糟糕的代码),那么世界可能会变得更美好。恰当的例子:您链接到的漫画中的代码 (s/:/g) 具有相同的错误:替换部分中缺少 /
    • 我认为这是让人们不快的印记,它们本质上是内置的匈牙利符号:它使 Perl 代码异常丰富地包含非字母数字。对错误感到抱歉;我没有命令提示符来测试东西。缺少斜线,替换应以 ...//igx 结尾。
    • malan 你从不将 awk+sed 结合使用,而且你不需要一本书来了解 sed 的用途(s/old/new/)。我完全不同意@Borodins 声称 perl 脚本比 awk 脚本更易读的说法,我个人的经验是 awk 脚本总是更清晰。我同意他的观点,sed 脚本比 s/old/new/ 做更多的事情比 perl 脚本更难以理解,但你不应该为此使用 sed。 Python 或 Ruby 可以处理同样的事情。与其他的不同之处在于,在所有 UNIX 系统上,只有 awk 是标准的,并且只有一种用于文本处理的小型语言。
    【解决方案5】:

    使用 sed 和您的输入文件

    sed -E 's/(\<[0-9]+\>)//g' infile
    

    输出

     METRO CONDOMINIUM AS DESC IN INST#  UNIT A
    126TH AVENUE INDUSTRIAL PARK
     AND --
    -1st AVE CONDO
    

    【讨论】:

      【解决方案6】:

      这可能对你有用(GNU sed):

      sed -r 's/^/\n/;:a;s/\n([^0-9]+)/\1\n/;ta;s/\n([0-9]*(1st|2nd|3rd|[4-90]th))/\1\n/I;ta;s/\n[0-9]+/\n/;ta;s/\n//' file
      

      使用换行符作为分隔符来解析每一行。在行首插入换行符。如果换行符后面的字符串不是数字,则传递该字符串。如果换行符后面的字符串是序数,也传递字符串。如果换行符后面的字符串是数字,则将其删除。在行尾,删除换行符。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-11-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-08
        • 2019-07-05
        • 1970-01-01
        • 2017-10-11
        相关资源
        最近更新 更多