як використовувати sed, awk або gawk для друку лише того, що відповідає?


100

Я бачу безліч прикладів та довідкових сторінок про те, як робити такі речі, як пошук і заміна за допомогою sed, awk або gawk.

Але в моєму випадку у мене є регулярний вираз, що я хочу зіткнутися з текстовим файлом, щоб отримати певне значення. Я не хочу займатися пошуком і заміною. Це називається з баш. Давайте скористаємося прикладом:

Приклад регулярного вираження:

.*abc([0-9]+)xyz.*

Приклад вхідного файлу:

a
b
c
abc12345xyz
a
b
c

Як це просто звучить, я не можу зрозуміти, як правильно викликати sed / awk / gawk. Що я сподівався зробити, це з мого баш сценарію:

myvalue=$( sed <...something...> input.txt )

Я спробував:

sed -e 's/.*([0-9]).*/\\1/g' example.txt # extracts the entire input file
sed -n 's/.*([0-9]).*/\\1/g' example.txt # extracts nothing

10
Нічого ... люди проголосували за це питання -1? Невже це питання недоречне?
Стефан

Це здається цілком доречним, використання Regex та потужних утиліт командного рядка, таких як sed / awk або будь-якого редактора, таких як vi, emacs або teco, може бути більше схожим на програмування, ніж просто використання якогось додатка ol. IMO це належить на SO більше, ніж SU.
Опубліковано

Можливо, він був проголошений, оскільки в початковій формі він не чітко визначав деякі його вимоги. Це все ще не відбувається, якщо ви не прочитали коментарі ОП до відповідей (включаючи той, який я видалив, коли все пішло грушоподібну форму).
pavium

Відповіді:


42

Мій sed(Mac OS X) не працював +. Я *замість цього спробував, і я додав pтег для друку відповідності:

sed -n 's/^.*abc\([0-9]*\)xyz.*$/\1/p' example.txt

Для відповідності хоча б одному цифровому символу +я б використав:

sed -n 's/^.*abc\([0-9][0-9]*\)xyz.*$/\1/p' example.txt

Дякую, це працювало і для мене, коли я використовував * замість +.
Стефан

2
... і опція "p" для друку збігу, про яку я не знав. Знову дякую.
Стефан

2
Я повинен був уникнути, +і тоді це спрацювало для мене:sed -n 's/^.*abc\([0-9]\+\)xyz.*$/\1/p'
Призупинено до подальшого повідомлення.

3
Це тому, що ви не використовуєте сучасний формат RE, тому + - це стандартний символ, і ви повинні виразити це синтаксисом {,}. Ви можете додати параметр -E sed для запуску сучасного формату RE. Перевірте re_format (7), а саме останній абзац DESCRIPTION developer.apple.com/library/mac/#documentation/Darwin/Reference/…
anddam

33

Для цього можна використовувати sed

 sed -rn 's/.*abc([0-9]+)xyz.*/\1/gp'
  • -n не друкуйте отриманий рядок
  • -rце робить його таким чином, щоб у вас не було паролів групи захоплення ().
  • \1 матч групи захоплення
  • /g глобальний матч
  • /p роздрукувати результат

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

rip 'abc(\d+)xyz' '$1'

3
Це, безумовно, найкраща і найбільш добре пояснена відповідь досі!
Нік Рейман

З деяким поясненням краще зрозуміти, що не так у нашому питанні. Дякую !
r4phG

17

Я використовую perlдля полегшення цього для себе. напр

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/'

Це запускає Perl, -nопція вказує Perl читати в одному рядку одночасно зі STDIN та виконувати код. -eПараметр задає команду для запуску.

Інструкція виконує повторне вираження на прочитаному рядку, і якщо воно відповідає, виводить вміст першого набору дужок ( $1).

Ви можете зробити це також декількома іменами файлів в кінці. напр

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/' example1.txt example2.txt


Дякую, але у нас немає доступу до perl, саме тому я питав про sed / awk / gawk.
Стефан

5

Якщо ваша версія його grepпідтримує, ви можете скористатися -oопцією для друку лише тієї частини будь-якого рядка, яка відповідає вашому регулярному вираженню.

Якщо ні, то ось, що sedя міг би придумати:

sed -e '/[0-9]/!d' -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'

... який видаляє / пропускає без цифр, а для решти рядків видаляє всі провідні та кінцеві нецифрові символи. (Я лише здогадуюсь, що ваш намір - витягнути число з кожного рядка, який містить один).

Проблема з чимось на зразок:

sed -e 's/.*\([0-9]*\).*/&/' 

.... або

sed -e 's/.*\([0-9]*\).*/\1/'

... це sedлише те, що підтримує "жадібний" матч ... так що перший. * відповідатиме решті рядка. Якщо ми не можемо використати заперечений клас символів для досягнення не жадібного збігу ... або версії sedз сумісними Perl або іншими розширеннями до його регексів, ми не можемо витягнути точну відповідність шаблону з простору шаблону (рядок ).


Ви можете просто комбінувати дві свої sedкоманди таким чином:sed -n 's/[^0-9]*\([0-9]\+\).*/\1/p'
Призупинено до подальшого повідомлення.

Раніше не знав про -o варіант на grep. Добре знати. Але він друкує весь матч, а не "(...)". Отже, якщо ви співпадаєте з "abc ([[: digit:]] +) xyz", ви отримуєте "abc" і "xyz", а також цифри.
Стефан

Дякуємо, що нагадали про мене grep -o! Я намагався це зробити sedі боровся зі своєю потребою знайти кілька рядків у деяких рядках. Моє рішення stackoverflow.com/a/58308239/117471
Bruno Bronosky

3

Ви можете використовувати awkз , match()щоб отримати доступ до захопленої групі:

$ awk 'match($0, /abc([0-9]+)xyz/, matches) {print matches[1]}' file
12345

Це намагається відповідати шаблону abc[0-9]+xyz. Якщо це зробити, він зберігає свої фрагменти в масиві matches, першим елементом якого є блок [0-9]+. Оскільки match() повертає позицію символу або індекс, звідки починається ця підрядка (1, якщо вона починається на початку рядка) , вона запускає printдію.


За допомогою нього grepможна використовувати огляд і позаду:

$ grep -oP '(?<=abc)[0-9]+(?=xyz)' file
12345

$ grep -oP 'abc\K[0-9]+(?=xyz)' file
12345

Це перевіряє шаблон , [0-9]+коли це відбувається всередині abcі xyzі просто друкує цифри.


2

perl - це найчистіший синтаксис, але якщо у вас немає perl (не завжди там я розумію), то єдиний спосіб використовувати gawk та компоненти регулярного виразу - це використовувати функцію gensub.

gawk '/abc[0-9]+xyz/ { print gensub(/.*([0-9]+).*/,"\\1","g"); }' < file

вихід зразкового вхідного файлу буде

12345

Примітка: gensub замінює весь регулярний вираз (між //), тому вам потрібно поставити. * До і після ([0-9] +), щоб позбутися тексту перед і після числа в підстановці.


2
Розумне, працездатне рішення, якщо вам потрібно (або хочете) використовувати гаук. Ви це відзначили, але щоб бути зрозумілим: у awk, який не є GNU, немає gensub (), і тому це не підтримує.
cincodenada

Приємно! Однак, можливо, найкраще використовувати match()для доступу до захоплених груп. Дивіться мою відповідь на це.
fedorqui 'ТАК перестаньте шкодити'

1

Якщо ви хочете вибрати рядки, то викресліть біти, які вам не потрібні:

egrep 'abc[0-9]+xyz' inputFile | sed -e 's/^.*abc//' -e 's/xyz.*$//'

В основному він вибирає потрібні лінії, egrepа потім використовує sedдля викреслення бітів до і після числа.

Ви можете побачити це в дії тут:

pax> echo 'a
b
c
abc12345xyz
a
b
c' | egrep 'abc[0-9]+xyz' | sed -e 's/^.*abc//' -e 's/xyz.*$//'
12345
pax> 

Оновлення: очевидно, якщо ваша реальна ситуація є більш складною, ЗН потрібно буде мені змінити. Наприклад, якщо у вас на початку та в кінці завжди було поховано одне число у нульовому чи більше нечислових числах:

egrep '[^0-9]*[0-9]+[^0-9]*$' inputFile | sed -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'

Цікаво ... Тож не існує простого способу застосувати складний регулярний вираз і повернути лише те, що знаходиться в розділі (...)? Тому що я бачу, що ти тут робив спочатку з грепом, а потім з sed, наша реальна ситуація набагато складніша, ніж скидання "abc" та "xyz". Використовується регулярний вираз, оскільки багато різного тексту може з’являтися з будь-якої сторони тексту, який я хотів би витягти.
Стефан

Я впевнений, що є кращий спосіб, якщо ПН дійсно складні. Можливо, якщо ви надали ще кілька прикладів або більш детальний опис, ми могли б пристосувати наші відповіді до відповідності.
paxdiablo

0

Випадок ОП не вказує, що на одному рядку може бути декілька збігів, але для трафіку Google я також додаю приклад для цього.

Оскільки ОП потребує вилучення групи з шаблону, для використання grep -oзнадобиться 2 проходи. Але я все-таки вважаю це найбільш інтуїтивним способом зробити роботу.

$ cat > example.txt <<TXT
a
b
c
abc12345xyz
a
abc23451xyz asdf abc34512xyz
c
TXT

$ cat example.txt | grep -oE 'abc([0-9]+)xyz'
abc12345xyz
abc23451xyz
abc34512xyz

$ cat example.txt | grep -oE 'abc([0-9]+)xyz' | grep -oE '[0-9]+'
12345
23451
34512

Оскільки час процесора в основному вільний, але читабельність людини безцінна, я схильний переробляти свій код на основі запитання: «Через рік, що я думаю, що це робить?» Насправді, щодо коду, яким я маю намір поділитися публічно або зі своєю командою, я навіть відкриюсь, man grepщоб зрозуміти, що таке довгі варіанти, і замінити їх. Так:grep --only-matching --extended-regexp


-1

ви можете зробити це з оболонкою

while read -r line
do
    case "$line" in
        *abc*[0-9]*xyz* ) 
            t="${line##abc}"
            echo "num is ${t%%xyz}";;
    esac
done <"file"

-3

Для див. Я б використовував такий сценарій:

/.*abc([0-9]+)xyz.*/ {
            print $0;
            next;
            }
            {
            /* default, do nothing */
            }

Це не виводить числового значення ([0-9+]), воно виводить весь рядок.
Марк Лаката

-3
gawk '/.*abc([0-9]+)xyz.*/' file

2
Це, здається, не працює. Він друкує весь рядок замість відповідності.
Стефан

у вашому вхідному файлі зразка цей шаблон є цілим рядком. правильно ??? якщо ви знаєте , що картина буде в певній галузі: використовувати $ 1, $ 2 і т.д .. наприклад простак «$ 1 ~ /.*abc([0-9]+)xyz.*/» Файл
ghostdog74
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.