Як грепати за групами з n цифр, але не більше n?


33

Я вивчаю Linux, і у мене є завдання, яке, здається, не вдається вирішити самостійно. Ось:

перетягніть рядок із файлу, який містить 4 числа підряд, але не більше 4.

Я не впевнений, як до цього підійти. Я можу шукати конкретні числа, але не їх кількість в рядку.


2
Чи повинен відображатися такий рядок 1234a12345, чи ні?
Елія Каган

@Buddha вам потрібно пояснити своє запитання разом із прикладом.
Авінаш Радж

якщо цифрам передує пробіл або початок якірного рядка і супроводжується пробілом або кінцем рядка якоря, ви можете просто використовувати межі слова. \b\d{4}\b
Avinash Raj

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], спосіб matt є більш портативним - він буде працювати в системах, де 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])*збігається з нуля або більше ( *) екземплярів не більше чотирьох цифр, за якими слід нецифровий. Нульові цифри (тобто нічого) - це одна можливість "не більше чотирьох цифр". Це відповідає (а) порожній рядку або (b) будь-якій рядку, що закінчується нецифровою і не містить послідовностей, що містять більше чотирьох цифр.

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

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

    Оскільки текст, що знаходиться праворуч від центрального \d{4}(або [0-9]{4}), повинен бути порожнім або починатись з нецифрової, це не дозволяє центральному \d{4}зіставити чотири цифри, які мають іншу (п'яту) цифру праворуч від них.

Це забезпечує, що десь присутня чотиризначна послідовність, і ніде не існує послідовності з п'яти чи більше цифр.

Непогано чи неправильно це робити так. Але, мабуть, найважливіша причина розглянути цю альтернативу полягає в тому, що вона роз'яснює перевагу використання (або подібного) натомість, як було запропоновано вище та у відповіді matt .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]

ой, не потрібно було старіти - я це відредагував
мат

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повторюється візерунок.


0

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

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

Ви також можете перевірити цей підручник на предмет використання команд grep.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.