как использовать sed, awk или gawk для печати только того, что совпадает?

100

Я вижу множество примеров и справочных страниц о том, как выполнять такие вещи, как поиск и замена с помощью sed, awk или gawk.

Но в моем случае у меня есть регулярное выражение, которое я хочу применить к текстовому файлу для извлечения определенного значения. Я не хочу выполнять поиск и замену. Это вызывается из bash. Возьмем пример:

Пример регулярного выражения:

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

Пример входного файла:

a
b
c
abc12345xyz
a
b
c

Как бы просто это ни звучало, я не могу понять, как правильно вызвать sed / awk / gawk. То, что я надеялся сделать, находится в моем сценарии bash:

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, может быть больше похоже на программирование, чем просто использование какого-то старого приложения. ИМО это принадлежит на SO больше, чем SU.
Дата выпуска
Возможно, он был отклонен, потому что в своей первоначальной форме он четко не определял некоторые из своих требований. Это все еще не так, если вы не прочитаете комментарии OP к ответам (включая тот, который я удалил, когда все пошло не так).
павиум

Ответы:

43

Моя 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
Mouviciel
источник
Спасибо, у меня это тоже сработало, когда я использовал * вместо +.
Стефан
2
... и опция "p" для печати совпадения, о которой я тоже не знал. Еще раз спасибо.
Стефан
2
Мне пришлось сбежать, +и тогда это сработало для меня:sed -n 's/^.*abc\([0-9]\+\)xyz.*$/\1/p'
Приостановлено до дальнейшего уведомления.
3
Это потому, что вы не используете современный формат RE, поэтому + - стандартный символ, и вы должны выражать это с помощью синтаксиса {,}. Вы можете добавить параметр use -E sed для запуска современного формата RE. Проверьте re_format (7), особенно последний абзац
ОПИСАНИЯ
34

Вы можете использовать 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

PP.
источник
Спасибо, но у нас нет доступа к 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
Бруно Броноски,
3

Вы можете использовать awkwith 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и просто печатает цифры.

fedorqui 'ТАК, хватит вредить'
источник
2

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

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

вывод образца входного файла будет

12345

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

Марк Лаката
источник
2
Умное, работоспособное решение, если вам нужно (или вы хотите) использовать gawk. Вы отметили это, но для ясности: у awk, отличного от GNU, нет gensub (), и поэтому он не поддерживает его.
cincodenada 09
Ницца! Однако может быть лучше использовать match()для доступа к захваченным группам. См. Мой ответ по этому поводу .
fedorqui 'SO, перестаньте вредить'
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> 

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

egrep '[^0-9]*[0-9]+[^0-9]*$' inputFile | sed -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'
Paxdiablo
источник
Интересно ... Значит, нет простого способа применить сложное регулярное выражение и вернуть только то, что находится в разделе (...)? Потому что, хотя я вижу, что вы здесь сделали сначала с grep, а затем с sed, наша реальная ситуация намного сложнее, чем отбрасывание «abc» и «xyz». Регулярное выражение используется, потому что по обе стороны от текста, который я хотел бы извлечь, может появиться много разного текста.
Стефан
Я уверен, что есть способ лучше, если RE действительно сложны. Возможно, если вы предоставите еще несколько примеров или более подробное описание, мы сможем скорректировать наши ответы в соответствии с вашими требованиями.
paxdiablo
0

Случай OP не указывает, что в одной строке может быть несколько совпадений, но для трафика Google я также добавлю пример для этого.

Поскольку OP необходимо извлечь группу из шаблона, для использования 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"
призрачная собака74
источник
-3

Для awk. Я бы использовал следующий сценарий:

/.*abc([0-9]+)xyz.*/ {
            print $0;
            next;
            }
            {
            /* default, do nothing */
            }
Пьер
источник
Это не выводит числовое значение ([0-9+]), это выводит всю строку.
Марк Лаката
-3
gawk '/.*abc([0-9]+)xyz.*/' file
призрачная собака74
источник
2
Похоже, это не работает. Он печатает всю строку вместо совпадения.
Стефан
в вашем образце входного файла этот шаблон представляет собой всю строку. право??? если вы знаете , что картина будет в определенной области: использовать $ 1, $ 2 и т.д .. например простак «$ 1 ~ /.*abc([0-9]+)xyz.*/» Файл
ghostdog74