Як вивести лише захоплені групи з sed?


277

Чи є спосіб сказати sedвиводити лише захоплені групи? Наприклад, дані:

This is a sample 123 text and some 987 numbers

та візерунок:

/([\d]+)/

Чи можу я отримати лише 123 і 987 вихід у форматі, який відформатується за допомогою зворотних посилань?


Зауважте, для захоплення групи потрібно sedввімкнути розширені регулярні вирази з -Eпрапором.
петерх

Відповіді:


333

Ключовим фактором для роботи є те, щоб sedвиключити те, що ви не хочете виводити, а також вказати, що ви хочете.

string='This is a sample 123 text and some 987 numbers'
echo "$string" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

Це говорить:

  • не за замовчуванням друкувати кожен рядок ( -n)
  • виключити нуль або більше нецифрових цифр
  • включають одну або кілька цифр
  • виключити одну або кілька нецифрових цифр
  • включають одну або кілька цифр
  • виключити нуль або більше нецифрових цифр
  • надрукувати заміну ( p)

Загалом, sedви збираєте групи за допомогою круглих дужок і виводите те, що ви збираєте, використовуючи зворотну посилання:

echo "foobarbaz" | sed 's/^foo\(.*\)baz$/\1/'

виведе "бар". Якщо ви використовуєте -r( -Eдля OS X) розширений регулярний вираз, вам не потрібно уникати дужок:

echo "foobarbaz" | sed -r 's/^foo(.*)baz$/\1/'

Тут може бути до 9 груп захоплення та їхніх посилань. Зворотні посилання нумеруються в тому порядку, в якому з’являються групи, але вони можуть бути використані в будь-якому порядку і можуть бути повторені:

echo "foobarbaz" | sed -r 's/^foo(.*)b(.)z$/\2 \1 \2/'

виводить "бар а".

Якщо у вас є GNU grep(він також може працювати в BSD, включаючи OS X):

echo "$string" | grep -Po '\d+'

або такі варіанти, як:

echo "$string" | grep -Po '(?<=\D )(\d+)'

Цей -Pпараметр дозволяє Perl сумісні регулярні вирази. Побачити man 3 pcrepatternабо man 3 pcresyntax.


24
Як зауважимо, OSX Mountain Lion більше не підтримує PCRE у форматі grep.
yincrash

1
Як бічна примітка, опція grep -o не підтримується в Solaris 9. Також Solaris 9 не підтримує варіант sed -r. :(
Даніель Кац

7
Попросіть свого sysadmin встановити gsed. Ви були б здивовані тим, що вам дістанеться кілька пончиків ...
avgvstvs

3
Зауважте, що вам може знадобитися префікс '(' і ')' з '\', я не знаю чому.
Мастило

7
@lumbric: Якщо ви посилаєтесь на sedприклад, якщо ви використовуєте -rпараметр (або -Eдля OS X, IIRC), вам не потрібно уникати дужок. Різниця полягає в тому, що між основними регулярними виразами та розширеними регулярними виразами ( -r).
Призупинено до подальшого повідомлення.

55

Sed має до дев'яти запам'ятовуваних шаблонів, але вам потрібно використовувати скочені дужки, щоб запам'ятати частини регулярного виразу.

Дивіться тут приклади та більш детальну інформацію


58
sed -e 's/version=\(.+\)/\1/' input.txtце все одно виведе весь input.txt
Пабло

@Pablo, у своєму шаблоні ви повинні писати \+замість +. І я не розумію, чому люди використовують -eлише одну команду sed.
Фредрік Гаус

1
використання sed -e -n 's/version=\(.+\)/\1/p' input.txtдив .: mikeplate.com/2012/05/09/…
awattar

1
Я б запропонував sed -Eвикористовувати так звані "сучасні" або "розширені" регулярні вирази, які набагато ближче до Perl / Java / JavaScript / Go / будь-яких смаків. (Порівняйте з grep -Eабо egrep.) У синтаксисі за замовчуванням є ті дивні правила, що виходять, і вважається "застарілим". Щоб отримати докладнішу інформацію про відмінності між ними, запустіть man 7 re_format.
AndrewF

31

можна використовувати греп

grep -Eow "[0-9]+" file

4
@ ghostdog74: Цілком з вами згоден. Як я можу отримати Greo для виведення лише захоплених груп?
Пабло

1
@Michael - тому oопція є - unixhelp.ed.ac.uk/CGI/man-cgi?grep : -o, - тільки відповідна Показати лише ту частину лінії, що відповідає, що відповідає PATTERN
Bert F

14
@Bert F: Я розумію відповідні частини, але це не група захоплення. Я хочу, щоб це було так ([0-9] +). + ([Abc] {2,3}), тому є 2 групи захоплення. Я хочу виводити ТОЛЬКІ групи захоплення за допомогою зворотних посилань чи якимось іншим чином.
Пабло

Привіт Майкл. Чи вдалося вам видобути n-ту захоплену групу грепом?
doc_id

1
@Pablo: grep виводить лише те, що відповідає. Щоб надати йому кілька груп, використовуйте декілька виразів: grep -Eow -e "[0-9]+" -e "[abc]{2,3}"я не знаю, як ви могли б вимагати, щоб ці два вирази були в одному рядку, окрім конфігурації попереднього файлу grep (який все ще не може працювати, якщо будь-який візерунок збігається більше, ніж один раз у рядку ).
idbrii

13

запуск (-ів) цифр

Ця відповідь працює з будь-яким числом цифрних груп. Приклад:

$ echo 'Num123that456are7899900contained0018166intext' |
> sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

Розгорнута відповідь.

Чи є спосіб сказати sed для виведення тільки захоплених груп?

Так. замінити весь текст на групу захоплення:

$ echo 'Number 123 inside text' | sed 's/[^0-9]*\([0-9]\{1,\}\)[^0-9]*/\1/'
123

s/[^0-9]*                           # several non-digits
         \([0-9]\{1,\}\)            # followed by one or more digits
                        [^0-9]*     # and followed by more non-digits.
                               /\1/ # gets replaced only by the digits.

Або з розширеним синтаксисом (менше зворотних цитат і дозволяють використовувати +):

$ echo 'Number 123 in text' | sed -E 's/[^0-9]*([0-9]+)[^0-9]*/\1/'
123

Щоб уникнути друку оригінального тексту, коли його немає, використовуйте:

$ echo 'Number xxx in text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1/p'
  • (-n) Не друкуйте дані за замовчуванням.
  • (/ p) друкувати лише в тому випадку, якщо було здійснено заміну.

І щоб відповідати декілька чисел (а також надрукувати їх):

$ echo 'N 123 in 456 text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1 /gp'
123 456

Це працює для будь-якого числа пробігів цифр:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

Що дуже схоже на команду grep:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | grep -Po '\d+'
123
456
7899900
0018166

Про \ д

та візерунок: /([\d]+)/

Sed не розпізнає синтаксис '\ d' (ярлик). Еквівалент ascii, використаний вище [0-9], не зовсім еквівалентний. Єдине альтернативне рішення - використовувати клас символів: '[[: цифра:]] `.

Вибрана відповідь використовує такі "класи символів" для побудови рішення:

$ str='This is a sample 123 text and some 987 numbers'
$ echo "$str" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

Це рішення працює лише для (точно) двох циклів цифр.

Звичайно, оскільки відповідь виконується всередині оболонки, ми можемо визначити пару змінних для скорочення такої відповіді:

$ str='This is a sample 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D+($d+)$D*/\1 \2/p"

Але, як уже було пояснено, s/…/…/gpкраще використовувати команду:

$ str='This is 75577 a sam33ple 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D*/\1 /gp"
75577 33 123 987

Це охоплюватиме неодноразові запуски цифр та написання короткої (ер) команди.


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

9

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

Якщо у вас є sed з розширенням GNU, що дозволяє вставити нову рядок у простір шаблону, одна пропозиція:

> set string = "This is a sample 123 text and some 987 numbers"
>
> set pattern = "[0-9][0-9]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
123
987
> set pattern = "[a-z][a-z]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
his
is
a
sample
text
and
some
numbers

Ці приклади з tcsh (так, я знаю, що це неправильна оболонка) з CYGWIN. (Редагувати: для bash, видаліть набір та пробіли навколо =.)


@ Джозеф: однак, завдяки моєму завданню я відчуваю, що греп є більш природним, як запропонував ghostdog74. Потрібно лише розібратися, як зробити греп-виведення лише груп захоплення, а не всієї відповідності.
Пабло

2
Лише зауваження, але знак плюс "+" означає "один або декілька", який би усунув необхідність повторення у шаблонах. Отже, "[0-9] [0-9] *" стане "[0-9] +"
RandomInsano

4
@RandomInsano: Для того, щоб скористатися +, вам потрібно буде залишити його або скористатися -rопцією ( -Eдля OS X). Ви також можете використовувати \{1,\}( -rабо -Eбез втечі).
Призупинено до подальшого повідомлення.

9

Відмовтеся від використання Perl

Оскільки sedне ріжемо його, давайте просто кидаємо рушник і використовуємо Perl, принаймні це LSB, тоді як grepрозширення GNU не :-)

  • Роздрукуйте всю відповідну частину, не потрібно відповідати групам або шукати позаду:

    cat <<EOS | perl -lane 'print m/\d+/g'
    a1 b2
    a34 b56
    EOS

    Вихід:

    12
    3456
  • Один матч на рядок, часто структуровані поля даних:

    cat <<EOS | perl -lape 's/.*?a(\d+).*/$1/g'
    a1 b2
    a34 b56
    EOS

    Вихід:

    1
    34

    З огляду на:

    cat <<EOS | perl -lane 'print m/(?<=a)(\d+)/'
    a1 b2
    a34 b56
    EOS
  • Кілька полів:

    cat <<EOS | perl -lape 's/.*?a(\d+).*?b(\d+).*/$1 $2/g'
    a1 c0 b2 c0
    a34 c0 b56 c0
    EOS

    Вихід:

    1 2
    34 56
  • Кілька збігів на рядок, часто неструктуровані дані:

    cat <<EOS | perl -lape 's/.*?a(\d+)|.*/$1 /g'
    a1 b2
    a34 b56 a78 b90
    EOS

    Вихід:

    1 
    34 78

    З огляду на:

    cat EOS<< | perl -lane 'print m/(?<=a)(\d+)/g'
    a1 b2
    a34 b56 a78 b90
    EOS

    Вихід:

    1
    3478

1
Що ви не отримали з кінцем питання: "з sed"?
Moonchild

@Moonchild Googlers не хвилює.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
я вважав це корисним. не всі проблеми з регулярним вираженням командного рядка потрібно вирішувати sed.
PPPaul

5

Спробуйте

sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

Я отримав це під cygwin:

$ (echo "asdf"; \
   echo "1234"; \
   echo "asdf1234adsf1234asdf"; \
   echo "1m2m3m4m5m6m7m8m9m0m1m2m3m4m5m6m7m8m9") | \
  sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

1234
1234 1234
1 2 3 4 5 6 7 8 9
$

2

ОП не вимагало (захоплення груп), але ви можете витягувати номери, використовуючи:

S='This is a sample 123 text and some 987 numbers'
echo "$S" | sed 's/ /\n/g' | sed -r '/([0-9]+)/ !d'

Дає наступне:

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