Я изучаю Linux, и у меня есть задача, которую я, похоже, не могу решить самостоятельно. Вот она:
найти строку из файла, содержащую 4 числа подряд, но не более 4.
Я не уверен, как к этому подойти. Я могу искать конкретные числа, но не их сумму в строке.
решение1
Есть два способа интерпретировать этот вопрос; я рассмотрю оба случая. Вы можете захотеть отобразить строки:
- которые содержат последовательность из четырех цифр, которая сама по себе не является частью какой-либо более длинной последовательности цифр,или
- который содержит последовательность из четырех цифр, но больше не является последовательностью цифр (даже по отдельности).
Например, (1) отобразит 1234a56789
, а (2) — нет.
Если вы хотите отобразить все строки, содержащие последовательность из четырех цифр, которая сама по себе не является частью какой-либо более длинной последовательности цифр, один из способов:
grep -P '(?<!\d)\d{4}(?!\d)' file
Это используетРегулярные выражения Perl, который в Ubuntugrep
(GNU-команда grep) поддерживает через -P
. Он не будет соответствовать тексту типа 12345
, а также не будет соответствовать 1234
или 2345
, которые являются его частью.Но он будет соответствовать 1234
в 1234a56789
.
В регулярных выражениях Perl:
\d
означает любую цифру (это сокращенный вариант «[0-9]
или[[:digit:]]
»).x{4}
Матчиx
4 раза. ({
}
синтаксис не является специфичным для регулярных выражений Perl; онgrep -E
также присутствует в расширенных регулярных выражениях via.) So\d{4}
то же самое, что и\d\d\d\d
.(?<!\d)
— это отрицательное утверждение с просмотром назад нулевой ширины. Оно означает «если не предшествует\d
».(?!\d)
— это отрицательное утверждение с нулевой шириной. Оно означает «если не следует\d
».
(?<!\d)
и (?!\d)
не сопоставляют текст за пределами последовательности из четырех цифр; вместо этого они (при совместном использовании) предотвращают сопоставление последовательности из четырех цифр, если она является частью более длинной последовательности цифр.
Использование только просмотра назад или только просмотра вперед недостаточно, поскольку крайняя правая или крайняя левая четырехзначная подпоследовательность все равно будет сопоставлена.
Одно из преимуществ использованияутверждения о взгляде назад и взгляде впередзаключается в том, что ваш шаблон соответствует только самим четырехзначным последовательностям, а не окружающему тексту. Это полезно при использовании цветного выделения (с опцией --color
).
ek@Io:~$ grep -P '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
12345abc789d0123e4
По умолчаниюВ Ubuntu каждый пользователь имеет alias grep='grep --color=auto'
в своем распоряжении~.bashrc
файл. Таким образом, вы автоматически получаете цветное выделение, когда запускаете простую команду, начинающуюся с grep
(это когдапсевдонимырасширены) истандартный выводявляетсятерминал(Это то, что--color=auto
проверяет). Совпадения обычно выделяются оттенком красного (близким ккиноварь), но я выделил его жирным курсивом.Вот скриншот:
И вы даже можете сделать grep
так, чтобы был напечатан только соответствующий текст, а не вся строка, с помощью -o
:
ek@Io:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123
Альтернативный путь,БезУтверждения о взгляде назад и взгляде вперед
Однако, если вы:
- нужна команда, которая также будет работать в системах, где
grep
не поддерживаются-P
или по иным причинам не хотят использоваться регулярные выражения Perl,и - не нужно специально сопоставлять четыре цифры — что обычно и происходит, если ваша цель — просто отобразить строки, содержащие совпадения,и
- согласны с решением, которое немного менее элегантно
...тогда вы можете добиться этого с помощьюрасширенное регулярное выражениевместо:
grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file
Это соответствует четырем цифрам и нецифровому символу — или началу или концу строки — вокруг них. А именно:
[0-9]
соответствует любой цифре (например[[:digit:]]
, или\d
в регулярных выражениях Perl) и{4}
означает «четыре раза». То есть[0-9]{4}
соответствует последовательности из четырех цифр.[^0-9]
соответствует символам, не входящим в диапазон от0
до9
. Это эквивалентно[^[:digit:]]
(или\D
, в регулярных выражениях Perl).^
, когда он не указан в[
]
скобках, соответствует началу строки. Аналогично,$
соответствует концу строки.|
означаетилии скобки для группировки (как в алгебре). Так(^|[^0-9])
соответствует началу строки или нецифровому символу, а($|[^0-9])
соответствует концу строки или нецифровому символу.
Таким образом, совпадения возникают только в строках, содержащих четырехзначную последовательность ( [0-9]{4}
), которая одновременно:
- в начале строки или с предшествующим ему символом, не являющимся цифрой (
(^|[^0-9])
),и - в конце строки или за ним следует нецифровой символ (
($|[^0-9])
).
Если, с другой стороны, вы хотите отобразить все строки, которые содержат четырехзначную последовательность, но не содержатлюбойпоследовательность из более чем четырех цифр (даже если она отделена от другой последовательности, состоящей всего из четырех цифр), то концептуально ваша цель — найти строки, которые соответствуют одному шаблону, но не соответствуют другому.
Поэтому, даже если вы знаете, как это сделать с помощью одного шаблона, я бы посоветовал использовать что-то вродеМэттвторое предложение, grep
ing для двух шаблонов по отдельности.
Вы не получите особой выгоды от каких-либо расширенных возможностей регулярных выражений Perl, делая это, поэтому вы можете предпочесть не использовать их. Но в соответствии с вышеуказанным стилем, вот сокращениерешение Мэттаиспользуя \d
(и фигурные скобки) вместо [0-9]
:
grep -P '\d{4}' file | grep -Pv '\d{5}'
Так как он использует [0-9]
,путь Мэттаболее переносима -- она будет работать на системах, где grep
не поддерживаются регулярные выражения Perl. Если вы используете [0-9]
(или [[:digit:]]
) вместо \d
, но продолжаете использовать {
}
, вы получаете переносимость способа Мэтта немного более лаконично:
grep -E '[0-9]{4}' file | grep -Ev '[0-9]{5}'
Альтернативный способ, с единым узором
Если вы действительно предпочитаете grep
команду, которая
- использует одно регулярное выражение(не две буквы
grep
s, разделенные знакомтрубка, как указано выше) - для отображения строк, содержащих хотя бы одну последовательность из четырех цифр,
- но нет последовательностей из пяти (или более) цифр,
- и вы не против совпадения всей строки, а не только цифр (вероятно, вас это не смущает)
...тогда вы можете использовать:
grep -Px '(\d{0,4}\D)*\d{4}(\D\d{0,4})*' file
Флаг -x
отображает grep
только те строки, в которых совпадает вся строка (а не любая строкасодержащийматч).
Я использовал регулярное выражение Perl, потому что считаю, что краткость \d
и \D
существенно повышает ясность в этом случае. Но если вам нужно что-то переносимое на системы, где grep
не поддерживается -P
, вы можете заменить их на [0-9]
и [^0-9]
(или на [[:digit:]]
и [^[:digit]]
):
grep -Ex '([0-9]{0,4}[^0-9])*[0-9]{4}([^0-9][0-9]{0,4})*' file
Вот как работают эти регулярные выражения:
В середине,
\d{4}
или[0-9]{4}
соответствует одной последовательности из четырех цифр. У нас может быть больше одного из них, но нам нужно иметь по крайней мере один.Слева
(\d{0,4}\D)*
or([0-9]{0,4}[^0-9])*
соответствует нулю или более (*
) экземпляров не более четырех цифр, за которыми следует нецифра. Ноль цифр (т.е. ничего) — одна из возможностей для «не более четырех цифр». Это соответствует(а)пустая строка или(б)любая строкаокончаниев нецифровом формате и не содержащем последовательностей из более чем четырех цифр.Поскольку текст, расположенный непосредственно слева от центрального символа
\d{4}
(или[0-9]{4}
), должен быть либо пустым, либо заканчиваться нецифрой, это не позволяет центральному символу\d{4}
сопоставлять четыре цифры, слева от которых находится еще одна (пятая) цифра.Справа
(\D\d{0,4})*
or([^0-9][0-9]{0,4})*
соответствует нулю или более (*
) экземпляров нецифры, за которыми следует не более четырех цифр (которые, как и прежде, могут быть четырьмя, тремя, двумя, одной или даже ни одной). Это соответствует(а)пустая строка или(б)любая строканачалов нецифровом формате и не содержащем последовательностей из более чем четырех цифр.Поскольку текст, расположенный непосредственно справа от центрального символа
\d{4}
(или[0-9]{4}
), должен быть либо пустым, либо начинаться с символа, отличного от цифры, это не позволяет центральному символу\d{4}
сопоставлять четыре цифры, справа от которых находится еще одна (пятая) цифра.
Это гарантирует, что где-то присутствует последовательность из четырех цифр и что нигде не присутствует последовательность из пяти или более цифр.
Это не плохо и не неправильно делать это таким образом. Но, возможно, самая важная причина рассмотреть эту альтернативу заключается в том, что она проясняет выгоду использования (или аналогичного) вместо этого, как предложено выше и вgrep -P '\d{4}' file | grep -Pv '\d{5}'
ответ Мэтта.
При таком подходе становится ясно, что ваша цель — выбрать строки, содержащие одно, но не другое. Плюс синтаксис проще (поэтому его могут быстрее понять многие читатели/сопровождающие).
решение2
Это покажет вам 4 числа подряд, но не больше.
grep '[0-9][0-9][0-9][0-9][^0-9]' file
Обратите внимание, что ^ означает «нет».
Есть проблема, хотя я не уверен, как ее исправить... если номер является концом строки, то он не будет отображаться.
Однако эта более уродливая версия подойдет для этого случая.
grep '[0-9][0-9][0-9][0-9]' file | grep -v [0-9][0-9][0-9][0-9][0-9]
решение3
Вы можете попробовать следующую команду, заменив file
ее фактическим именем файла в вашей системе:
grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file
Вы также можете проверитьэтот урокдля получения дополнительных сведений о применении команды grep.
решение4
Если grep
регулярные выражения Perl ( ) не поддерживаются -P
, используйте следующую команду оболочки:
grep -w "$(printf '[0-9]%.0s' {1..4})" file
где printf '[0-9]%.0s' {1..4}
даст 4 раза [0-9]
. Этот метод полезен, когда у вас длинные цифры и вы не хотите повторять шаблон (просто замените 4
на свое число ваши цифры для поиска).
Использование -w
будет искать целые слова. Однако, если вас интересуют буквенно-цифровые строки, такие как 1234a
, то добавьте [^0-9]
в конец шаблона, например
grep "$(printf '[0-9]%.0s' {1..4})[^0-9]" file
Использование $()
в основном являетсязамена команды. Проверь этопочтачтобы увидеть, как printf
повторяется узор.