去年出现在字符串中
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
(单词边界)以防止匹配 01900
或 X1911D
。仅打印第一个捕获组 (</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
后跟从 0
到 4
的数字,然后是任何数字 (00
到 49
) 或 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), 等等,这强烈表明这是正确的方法。尝试使用基于正则表达式的解决方案更改任何这些要求。
我有这样的字符串:
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
(单词边界)以防止匹配 01900
或 X1911D
。仅打印第一个捕获组 (</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
后跟从0
到4
的数字,然后是任何数字 (00
到49
) 或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), 等等,这强烈表明这是正确的方法。尝试使用基于正则表达式的解决方案更改任何这些要求。