去年出现在字符串中

last year occurrence from string

我有这样的字符串:

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\s)'

但它匹配:1910 和 1934

这是 Regex101 示例:

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

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

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

我没有看到使用 grep 执行此操作的方法,因为它不会让您只输出一个捕获组,而只会输出整个匹配项。

Wit perl 我会做类似的事情

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

想法:使用^.*(贪心)尽可能多地消耗前面的字符串,从而找到最后一个可能的匹配项。在匹配的数字周围使用 \b(单词边界)以防止匹配 01900X1911D。仅打印第一个捕获组 (</code>)。</p> <p>我尝试实现了你对1900-2050的要求;如果太复杂,<code>((?:19|20)\d\d) 就可以了(但也匹配 2099)。

使用 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.

否定前瞻提供您将只匹配 last 所需年份的发生。

如果你可以使用除grep以外的其他工具,支持调用上一个 编号组 (?n),其中 n 是另一个捕获的编号 组,正则表达式可以简单一点:

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

详情:

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

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

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

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

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

参见regex demo

详情

  • ^ - 行首
  • (?:.*\s)? - 可选的非捕获组匹配 1 次或 0 次出现
    • .* - 除换行字符外的任何 0+ 个字符,尽可能多
    • \s - 空白字符
  • \K - 匹配重置运算符丢弃目前匹配的文本
  • (?:19\d{2}|20(?:[0-4]\d|50)) - 19 和任意两个数字或 20 后跟从 04 的数字,然后是任何数字 (0049) 或 50.
  • (?!\S) - 空格或字符串结尾。

看到 online demo:

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

你听说过吗this saying:

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 内),在您的范围内使用不同的开始或结束日期是微不足道的(更改 min and/or max values), 等等,这强烈表明这是正确的方法。尝试使用基于正则表达式的解决方案更改任何这些要求。