2

我有这样的字符串:

ACB 01900 X1911D 1910 1955-2011 3424 2135 1934 foobar

我试图获得最后一次出现的年份(从 1900 年到 2050 年),所以我只需要从该字符串中提取1934 年。

我正在尝试:

 grep -P -o '\s(19|20)[0-9]{2}\s(?!\s(19|20)[0-9]{2}\s)'

或者

grep -P -o '((19|20)[0-9]{2})(?!\s\1\s)'

但它匹配:1910 和 1934

这是 Regex101 示例:

https://regex101.com/r/UetMl0/3

https://regex101.com/r/UetMl0/4

另外:如何在没有周围空间的情况下提取年份而不进行额外的 grep 过滤?

4

4 回答 4

1

我看不到这样做的方法,grep因为它不允许您仅输出一个捕获组,而只能输出整个匹配项。

机智 perl 我会做类似的事情

perl -lpe 'if (/^.*\b(19\d\d|20(?:0-4\d|50))\b/) { print $1 }'

想法:使用^.*(greedy) 尽可能多地消耗前面的字符串,从而找到最后一个可能的匹配项。在匹配的数字周围使用\b(word boundary) 以防止匹配01900X1911D. 仅打印第一个捕获组 ( $1)。

我试图实现你对 1900-2050 的要求;如果这太复杂了,((?:19|20)\d\d)可以(但也匹配例如 2099)。

于 2018-12-02T17:01:59.370 回答
1

使用 grep 执行任务的正则表达式如下:

\b(?:19\d{2}|20[0-4]\d|2050)\b(?!.*\b(?:19\d{2}|20[0-4]\d|2050)\b)

细节:

  • \b- 单词边界。
  • (?:- 一个非捕获组的开始,需要作为替代品的容器。
    • 19\d{2}|- 第一种选择(1900 - 1999)。
    • 20[0-4]\d|- 第二种选择(2000 - 2049)。
    • 2050- 第三种选择,只是 2050。
  • )- 非捕获组的结束。
  • \b- 单词边界。
  • (?!- 负前瞻:
    • .*- 任何字符的序列,实际上意味着“接下来的内容可以发生在更远的地方”。
    • \b(?:19\d{2}|20[0-4]\d|2050)\b- 和以前一样的表情。
  • )- 负前瞻结束。

单词边界锚提供您不会匹配数字 -较长单词的部分,例如X1911D

负前瞻表示您将匹配 所需年份的最后一次出现。

如果您可以使用grep以外的其他工具,支持调用先前编号的 group (?n),其中n是另一个捕获组的编号,则正则表达式可以更简单一些:

(\b(?:19\d{2}|20[0-4]\d|2050)\b)(?!.*(?1))

细节:

  • (\b(?:19\d{2}|20[0-4]\d|2050)\b)- 像以前一样的正则表达式,但包含在捕获组中(稍后将被“调用”)。
  • (?!.*(?1))- 捕获第 1 组的负前瞻,位于更远的任何地方。

这样可以避免再次编写相同的表达式。

有关工作示例,regex101请参阅https://regex101.com/r/fvVnZl/1

于 2018-12-02T18:01:04.603 回答
1

您可以使用不带任何组的 PCRE 正则表达式来仅返回您需要的模式的最后一次出现,如果您在模式前面加上^.*\K,或者,在您的情况下,因为您期望空白边界,^(?:.*\s)?\K

grep -Po '^(?:.*\s)?\K(?:19\d{2}|20(?:[0-4]\d|50))(?!\S)' file

请参阅正则表达式演示

细节

  • ^- 线的开始
  • (?:.*\s)?- 一个可选的非捕获组匹配 1 或 0 次出现
    • .*- 除换行符以外的任何 0+ 个字符,尽可能多
    • \s- 一个空格字符
  • \K- 匹配重置运算符丢弃到目前为止匹配的文本
  • (?:19\d{2}|20(?:[0-4]\d|50))-19和任何两位数字或20后跟一个数字 from 0to4然后是任何数字 ( 00to 49) 或50
  • (?!\S)- 空格或字符串结尾。

查看在线演示

s="ACB 01900 X1911D 1910 1955-2011 3424 2135 1934 foobar"
grep -Po '^(?:.*\s)?\K(?:19\d{2}|20(?:[0-4]\d|50))(?!\S)' <<< "$s"
# => 1934
于 2018-12-02T18:28:59.297 回答
1

你有没有听过这样的说法

Some people, when confronted with a problem, think
“I know, I'll use regular expressions.”   Now they have two problems. 

保持简单 - 您有兴趣在 2 个数字之间找到一个数字,因此只需使用数字比较,而不是正则表达式:

$ awk -v min=1900 -v max=2050 '{yr=""; for (i=1;i<=NF;i++) if ( ($i ~ /^[0-9]{4}$/) && ($i >= min) && ($i <= max) ) yr=$i; print yr}' file
1934

如果您的范围内没有日期,您没有说明该怎么做,因此如果发生这种情况,上面会输出一个空行,但很容易调整以执行其他任何操作。

要更改上述脚本以查找第一个而不是最后一个日期是微不足道的(将打印移动到 if 内),在您的范围内使用不同的开始或结束日期是微不足道的(更改最小值和/或最大值)等。等,这强烈表明这是正确的方法。尝试使用基于正则表达式的解决方案更改任何这些要求。

于 2018-12-03T14:59:31.383 回答