Как grep для групп из n цифр, но не более n?

33

Я изучаю Linux, и у меня есть проблема, которую я, кажется, не могу решить самостоятельно. Вот:

grep строка из файла, который содержит 4 числа подряд, но не более 4.

Я не уверен, как подойти к этому. Я могу искать конкретные числа, но не их количество в строке.

Будда
источник
2
Должна 1234a12345ли отображаться строка как или нет?
Элия ​​Каган,
@ Будда, тебе нужно объяснить свой вопрос вместе с примером.
Авинаш Радж
если числам предшествует пробел или начало якоря строки, а затем пробел или конец якоря строки, вы можете просто использовать границы слов. \b\d{4}\b
Авинаш Радж
1
Этот вопрос отличается от некоторых вопросов о регулярных выражениях тем, что явно касается использования grep . Вопросы об использовании утилит Unix в Ubuntu, таких как grep, sed и awk, всегда считались здесь хорошими. Иногда люди спрашивают, как сделать работу с неправильным инструментом; тогда отсутствие контекста - большая проблема, но это не то, что здесь происходит. Это тематическая тема, достаточно ясная, чтобы на нее можно было с пользой ответить, она полезна для нашего сообщества, и нет смысла предотвращать дальнейшие ответы или подталкивать их к удалению или миграции. Я голосую, чтобы открыть его.
Элия ​​Каган,
1
Большое спасибо, ребята, я понятия не имел, что получу такую ​​обратную связь. Это ответ, который я искал: grep -E '(^ ​​| [^ 0-9]) [0-9] {4} ($ | [^ 0-9])' файл. Команда должна быть в состоянии вытащить строку, как это (что она делает): abc1234abcd99999
Будда

Ответы:

52

Есть два способа интерпретировать этот вопрос; Я рассмотрю оба случая. Возможно, вы захотите отобразить строки:

  1. которые содержат последовательность из четырех цифр, которая сама по себе не является частью более длинной последовательности цифр, или
  2. которая содержит последовательность из четырех цифр, но больше не является последовательностью цифр (даже отдельно).

Например, (1) будет отображаться 1234a56789, но (2) не будет.


Если вы хотите отобразить все строки, которые содержат последовательность из четырех цифр, которая сама по себе не является частью более длинной последовательности цифр, один из способов:

grep -P '(?<!\d)\d{4}(?!\d)' file

Здесь используются регулярные выражения Perl , которые поддерживает Ubuntu grep( GNU grep ) -P. Это не будет соответствовать тексту, как 12345, и не будет соответствовать 1234или 2345которые являются его частью. Но это будет соответствовать 1234в1234a56789 .

В Perl регулярные выражения:

  • \dозначает любую цифру (это короткий способ сказать [0-9]или[[:digit:]] ).
  • x{4}соответствует x4 раза. ( { }синтаксис не является специфичным для регулярных выражений Perl; он также присутствует в расширенных регулярных выражениях через grep -E.) Так \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, с 12345abc789d0123e4 в качестве вывода, с 0123, выделенным красным.

И вы даже можете grepнапечатать только соответствующий текст, а не всю строку, с помощью -o:

ek@Io:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123

Альтернативный путь, без заглядывающих и заглядывающих утверждений

Однако, если вы:

  1. нужна команда, которая также будет работать в системах, где grepона не поддерживает -Pили иным образом не хочет использовать регулярное выражение Perl, и
  2. не нужно специально сопоставлять четыре цифры - что обычно бывает, если ваша цель просто отображать строки, содержащие совпадения, и
  3. все в порядке с решением, которое немного менее элегантно

... тогда вы можете достичь этого с помощью расширенного регулярного выражения :

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ИНГ для двух моделей отдельно.

При этом вы не получаете никаких преимуществ от расширенных функций регулярных выражений 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команду, которая

  1. использует одно регулярное выражение (не два greps, разделенные каналом , как указано выше)
  2. отображать строки, содержащие как минимум одну последовательность из четырех цифр,
  3. но нет последовательности из пяти (или более) цифр,
  4. и вы не против сопоставить всю строку, а не только цифры (вы, вероятно, не против этого)

... тогда вы можете использовать:

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)*или ([0-9]{0,4}[^0-9])*соответствует нулю или более ( *) экземпляров не более четырех цифр, за которыми следует не цифра. Нулевые цифры (т. Е. Ничего) - это одна возможность для «не более четырех цифр». Это соответствует (а) пустой строке или (б) любой строке, заканчивающейся нецифровой цифрой и не содержащей последовательности из более чем четырех цифр.

    Поскольку текст, находящийся непосредственно слева от центральной части \d{4}(или [0-9]{4}), должен быть либо пустым, либо заканчиваться нецифровой цифрой, это препятствует тому, чтобы центральная часть \d{4}совпала с четырьмя цифрами, имеющими еще одну (пятую) цифру слева от них.

  • Справа (\D\d{0,4})*или ([^0-9][0-9]{0,4})*соответствует нулю или нескольким ( *) экземплярам нецифровой цифры, за которой следуют не более четырех цифр (которые, как и прежде, могут быть четырьмя, тремя, двумя, одной или даже вообще ни одной). Это соответствует (a) пустой строке или (b) любой строке, начинающейся с нецифровой цифры и не содержащей последовательности из более чем четырех цифр.

    Поскольку текст, находящийся непосредственно справа от центральной части \d{4}(или [0-9]{4}), должен быть либо пустым, либо начинаться с нецифровой цифры, это препятствует тому, чтобы центральная часть \d{4}соответствовала четырем цифрам, имеющим еще одну (пятую) цифру справа от них.

Это гарантирует, что где-то присутствует последовательность из четырех цифр, и что нигде нет последовательности из пяти или более цифр.

Это не плохо или неправильно делать это таким образом. Но, возможно, наиболее важной причиной для рассмотрения этой альтернативы является то, что она разъясняет преимущества использования (или аналогичного) вместо этого, как предложено выше и в ответе Мэтта .grep -P '\d{4}' file | grep -Pv '\d{5}'

Таким образом, ясно, что ваша цель состоит в том, чтобы выбрать строки, которые содержат одно, а не другое. Кроме того, синтаксис более прост (так что многие читатели / сопровождающие могут его быстрее понять).

Элия ​​Каган
источник
9

Это покажет вам 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]
матовый
источник
К сожалению, не нужно быть egrep - я редактировал его
Мэтт
2
Первый неправильный - он находит a12345b, потому что он совпадает 2345b.
Фолькер Сигел
0

Если 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повторяет шаблон.

kenorb
источник
0

Вы можете попробовать команду ниже, заменив file фактическое имя файла в вашей системе:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

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

Майк Тайсон
источник