в прошлом году вхождение из строки - PullRequest
0 голосов
/ 02 декабря 2018

У меня есть такие строки:

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 ]

0 голосов
/ 03 декабря 2018

Вы когда-нибудь слышали это высказывание :

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), использовать разные даты начала или окончания в вашем диапазоне - тривиально (изменить минимальное и / или максимальное значения) и т. Д.и т. д., что является убедительным свидетельством того, что это правильный подход.Попробуйте изменить любое из этих требований с помощью решения на основе регулярных выражений.

0 голосов
/ 02 декабря 2018

Регулярное выражение для выполнения вашей задачи с использованием 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 , поддерживающий вызов предыдущей пронумерованной группы (?n), где n - номер другой группы захвата, регулярное выражение может быть битом simpler:

(\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

0 голосов
/ 02 декабря 2018

Вы можете использовать регулярное выражение PCRE без каких-либо групп, чтобы возвращать только последнее вхождение нужного вам шаблона, если вы добавляете шаблон с ^.*\K, или, в вашем случае, так как вы ожидаете границу пробела, ^(?:.*\s)?\K:

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

См. Демонстрационный пример regex .

Подробности

  • ^ - начало строки
  • (?:.*\s)? - необязательная группа без захвата, соответствующая 1 или 0 вхождениям
    • .* - любые 0+ символов, кроме символов разрыва строки, максимально возможное количество
    • \s - символ пробела
  • \K - оператор сброса совпадений, отбрасывающий сопоставленный текст
  • (?:19\d{2}|20(?:[0-4]\d|50)) - 19 и любые две цифрыили 20, за которым следует либо цифра от 0 до 4, а затем любая цифра (00 до 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
0 голосов
/ 02 декабря 2018

Я не вижу способа сделать это с grep, потому что он не позволяет выводить только одну из групп захвата, только целое совпадение.

Wit Perl Я бы что-нибудь сделалкак

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

Идея: используйте ^.* (жадный), чтобы потреблять как можно больше строки впереди, таким образом находя последнее возможное совпадение.Используйте \b (граница слова) вокруг совпадающего числа, чтобы предотвратить совпадение 01900 или X1911D.Напечатайте только первую группу захвата ($1).

Я пытался выполнить ваше требование 1900-2050;если это слишком сложно, ((?:19|20)\d\d) подойдет (но также совпадет, например, с 2099).

...