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

Я вижу множество примеров и справочных страниц о том, как выполнять такие операции, как поиск и замена с помощью 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

person Stéphane    schedule 14.11.2009    source источник


Ответы (13)


Мой 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
person mouviciel    schedule 14.11.2009
comment
Спасибо, у меня это тоже сработало, когда я использовал * вместо +. - person Stéphane; 14.11.2009
comment
... и опция p для печати совпадения, о которой я тоже не знал. Спасибо еще раз. - person Stéphane; 14.11.2009
comment
Мне пришлось сбежать из +, и тогда у меня это сработало: sed -n 's/^.*abc\([0-9]\+\)xyz.*$/\1/p' - person Dennis Williamson; 14.11.2009
comment
Это потому, что вы не используете современный формат RE, поэтому + является стандартным символом, и вы должны выражать это с помощью синтаксиса {,}. Вы можете добавить опцию use -E sed для запуска современного формата RE. Проверьте re_format (7), особенно последний абзац DESCRIPTION developer.apple.com/library/mac/#documentation/Darwin/Reference/ - person anddam; 03.03.2013
comment
Как и параметр -E, вы можете использовать \{1,\} (вместо * или +) для подсчета одного или нескольких повторов. Вы можете указать нижнюю или верхнюю границу, или и то, и другое. - person Jonathan Leffler; 04.02.2021

Вы можете использовать sed для этого

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

Я написал для себя инструмент, который упрощает эту задачу.

rip 'abc(\d+)xyz' '$1'
person Ilia Choly    schedule 03.02.2016
comment
Это, безусловно, лучший и наиболее хорошо объясненный ответ на данный момент! - person Nik Reiman; 18.08.2016
comment
После некоторого объяснения, так будет лучше понять, что не так с нашей проблемой. Спасибо ! - person r4phG; 11.10.2017

Я использую 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

person PP.    schedule 14.11.2009
comment
Спасибо, но у нас нет доступа к perl, поэтому я спрашивал о sed / awk / gawk. - person Stéphane; 14.11.2009

Если ваша версия grep поддерживает это, вы можете использовать параметр -o для печати только той части любой строки, которая соответствует вашему регулярному выражению.

Если нет, то вот лучшее, что sed я мог придумать:

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

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

Проблема с чем-то вроде:

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

.... or

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

... в том, что sed поддерживает только "жадное" совпадение ... поэтому первый. * будет соответствовать остальной части строки. Если мы не сможем использовать инвертированный символьный класс для достижения нежадного соответствия ... или версию sed с Perl-совместимыми или другими расширениями его регулярных выражений, мы не сможем извлечь точное совпадение с шаблоном из пространства шаблонов ( линия).

person Jim Dennis    schedule 14.11.2009
comment
Вы можете просто объединить две свои sed команды таким образом: sed -n 's/[^0-9]*\([0-9]\+\).*/\1/p' - person Dennis Williamson; 15.11.2009
comment
Раньше не знал о параметре -o в grep. Приятно знать. Но он печатает все совпадение, а не (...). Итак, если вы соответствуете abc ([[: digit:]] +) xyz, вы получите abc и xyz, а также цифры. - person Stéphane; 16.11.2009
comment
Спасибо, что напомнили мне grep -o! Я пытался сделать это с sed и боролся с моей необходимостью найти несколько совпадений в некоторых строках. Мое решение - stackoverflow.com/a/58308239/117471 - person Bruno Bronosky; 09.10.2019

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

person fedorqui 'SO stop harming'    schedule 22.08.2016

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

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

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

12345

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

person Mark Lakata    schedule 29.04.2013
comment
Умное, работоспособное решение, если вам нужно (или вы хотите) использовать gawk. Вы отметили это, но для ясности: у awk, отличного от GNU, нет функции gensub (), и поэтому она не поддерживается. - person cincodenada; 10.01.2014
comment
Отлично! Однако может быть лучше использовать match() для доступа к захваченным группам. См. мой ответ по этому поводу. - person fedorqui 'SO stop harming'; 22.08.2016

Если вы хотите выделить строки, удалите ненужные биты:

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]*$//'
person paxdiablo    schedule 14.11.2009
comment
Интересно ... Значит, нет простого способа применить сложное регулярное выражение и вернуть только то, что находится в разделе (...)? Потому что, хотя я вижу, что вы здесь сделали сначала с помощью grep, а затем с помощью sed, наша реальная ситуация намного сложнее, чем удаление abc и xyz. Регулярное выражение используется, потому что по обе стороны от текста, который я хотел бы извлечь, может появиться много разного текста. - person Stéphane; 14.11.2009
comment
Я уверен, что есть лучший способ, если RE действительно сложные. Возможно, если вы предоставите еще несколько примеров или более подробное описание, мы сможем скорректировать наши ответы в соответствии с вашими требованиями. - person paxdiablo; 14.11.2009

Случай 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

person Bruno Bronosky    schedule 09.10.2019

зачем вообще нужна группа совпадений

gawk/mawk/mawk2 'BEGIN{ FS="(^.*abc|xyz.*$)" } ($2 ~ /^[0-9]+$/) {print $2}'

Пусть FS соберет оба конца линии.

Если $ 2, остаток, не проглоченный FS, не содержит нечисловых символов, это ваш ответ, который нужно распечатать.

Если вы проявляете особую осторожность, подтвердите, что длина 1 и 3 доллара равна нулю.

** отредактированный ответ после осознания нулевой длины $ 2 отключит мое предыдущее решение

person RARE Kpop Manifesto    schedule 04.02.2021

есть стандартный фрагмент кода из канала awk под названием FindAllMatches, но он все еще очень ручной, буквально, просто длинные циклы while(), match(), substr(), еще substr(), затем промойте и повторите.

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

mawk/mawk2/gawk 'BEGIN { srand(); for(x = 0; x < 128; x++ ) { 

    alnumstr = sprintf("%s%c", alnumstr , x) 
 }; 
 gsub(/[^[:alnum:]_=]+|[AEIOUaeiou]+/, "", alnumstr) 
                       
                    # resulting str should be 44-chars long :
                    # all digits, non-vowels, equal sign =, and underscore _

 x = 10; do { nonceFS = nonceFS substr(alnumstr, 1 + int(44*rand()), 1)

 } while ( --x );   # you can pick any level of precision you need.
                    # 10 chars randomly among the set is approx. 54-bits 
                    #
                    # i prefer this set over all ASCII being these 
                    # just about never require escaping 
                    # feel free to skip the _ or = or r/t/b/v/f/0 if you're concerned.
                    #
                    # now you've made a random nonce that can be 
                    # inserted right in the middle of just about ANYTHING
                    # -- ASCII, Unicode, binary data -- (1) which will always fully
                    # print out, (2) has extremely low chance of actually
                    # appearing inside any real word data, and (3) even lower chance
                    # it accidentally alters the meaning of the underlying data.
                    # (so intentionally leaving them in there and 
                    # passing it along unix pipes remains quite harmless)
                    #
                    # this is essentially the lazy man's approach to making nonces
                    # that kinda-sorta have some resemblance to base64
                    # encoded, without having to write such a module (unless u have
                    # one for awk handy)


    regex1 = (..);  # build whatever regex you want here

    FS = OFS = nonceFS;

 } $0 ~ regex1 { 

    gsub(regex1, nonceFS "&" nonceFS); $0 = $0;  

                   # now you've essentially replicated what gawk patsplit( ) does,
                   # or gawk's split(..., seps) tracking 2 arrays one for the data
                   # in between, and one for the seps.
                   #
                   # via this method, that can all be done upon the entire $0,
                   # without any of the hassle (and slow downs) of 
                   # reading from associatively-hashed arrays,
                   # 
                   # simply print out all your even numbered columns
                   # those will be the parts of "just the match"

если вы также запустите еще один OFS = ""; $1 = $1;, теперь вместо того, чтобы требовать 4-аргумента split() или patsplit(), оба из которых специфичны для gawk, чтобы увидеть, каковы были разделители регулярных выражений, теперь все поля $0 находятся в data1-sep1-data2-sep2 -... . pattern, ..... все время $0 будет выглядеть ТОЧНО так же, как когда вы впервые читаете строку. прямой print будет побайтно идентично печати сразу после чтения.

Как только я протестировал его до крайности, используя регулярное выражение, которое представляет действительные символы UTF8 на этом. Потребовалось около 30 секунд, чтобы mawk2 обработал текстовый файл размером 167 МБ с большим количеством юникода CJK повсюду, все прочитал сразу в 0 долларов и запустил эту логику разделения, в результате чего NF составил около 175000000, и каждое поле было однозначным. символ ASCII или многобайтового Unicode UTF8.

person RARE Kpop Manifesto    schedule 05.05.2021

ты можешь сделать это с оболочкой

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

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

/.*abc([0-9]+)xyz.*/ {
            print $0;
            next;
            }
            {
            /* default, do nothing */
            }
person Pierre    schedule 14.11.2009
comment
Это не выводит числовое значение ([0-9+]), это выводит всю строку. - person Mark Lakata; 30.04.2013

person    schedule
comment
Похоже, это не работает. Он печатает всю строку вместо совпадения. - person Stéphane; 14.11.2009
comment
в вашем образце входного файла этот шаблон представляет собой всю строку. Правильно??? если вы знаете, что шаблон будет в определенном поле: используйте $ 1, $ 2 и т.д. - person ghostdog74; 14.11.2009