Sed ile sadece yakalanan gruplar nasıl çıkarılır?


277

sedSadece yakalanan grupların çıktılarını söylemenin bir yolu var mı ? Örneğin, girdi verildiğinde:

This is a sample 123 text and some 987 numbers

ve desen:

/([\d]+)/

Geri referanslarla biçimlendirilmiş şekilde yalnızca 123 ve 987 çıktı alabilir miyim?


Grup yakalamanın sed, işaretli genişletilmiş düzenli ifadeleri açması gerektiğini unutmayın -E.
peterh - Monica'yı

Yanıtlar:


333

Bunu işe almanın anahtarı, çıktı almak sedistemediğiniz şeyi hariç tutmanın yanı sıra ne istediğinizi belirtmektir.

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

Bu diyor ki:

  • her satırı varsayılan olarak yazdırma ( -n)
  • sıfır veya daha fazla rakam içermeyenleri hariç tut
  • bir veya daha fazla hane ekle
  • bir veya daha fazla rakam içermeyen
  • bir veya daha fazla hane ekle
  • sıfır veya daha fazla rakam içermeyenleri hariç tut
  • ikameyi yazdır ( p)

Genel olarak, sedparantez kullanarak grupları yakalar ve geri referans kullanarak yakaladığınız şeyi çıkarırsınız:

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

"bar" çıktısı verecektir. Genişletilmiş regex için -r( -EOS X için) kullanıyorsanız , parantezlerden kaçmanız gerekmez:

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

9 adede kadar yakalama grubu ve bunların geri referansları olabilir. Arka referanslar, grupların göründüğü sıraya göre numaralandırılır, ancak herhangi bir sırayla kullanılabilir ve tekrarlanabilir:

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

"a a" çıktılar.

GNU'nuz varsa grep(OS X dahil BSD'de de çalışabilir):

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

veya aşağıdakiler gibi varyasyonlar:

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

Bu -Pseçenek Perl Uyumlu Düzenli İfadeleri etkinleştirir. Bkz. man 3 pcrepatternVeya man 3 pcresyntax.


24
Not olarak, OSX Mountain Lion artık grep'te PCRE'yi desteklemiyor.
yincrash

1
Bir yan not olarak grep -o seçeneği Solaris 9'da desteklenmez. Ayrıca Solaris 9 sed -r seçeneğini desteklemez. :(
Daniel Kats

7
Sistem yöneticinizden gsed yüklemesini isteyin. Birkaç çörek size ne alacak şaşıracaksınız ...
avgvstvs

3
'(' Ve ')' ön ekini '\' ile eklemeniz gerekebileceğini unutmayın, nedenini bilmiyorum.
lumbric

7
@lumbric: Örneğe başvuruyorsanız sed, -rseçeneği (veya -EOS X, IIRC için) kullanıyorsanız, parantezlerden kaçmanız gerekmez. Aradaki fark, temel düzenli ifadeler ile genişletilmiş düzenli ifadeler ( -r) arasındadır.
sonraki duyuruya kadar duraklatıldı.

55

Sed'de dokuz hatırlanmış desen var, ancak normal ifadenin bölümlerini hatırlamak için kaçan parantezleri kullanmanız gerekiyor.

Örnekler ve daha fazla ayrıntı için buraya bakın


58
sed -e 's/version=\(.+\)/\1/' input.txtbu yine de tüm girişin çıktısını verecektir. txt
Pablo

@Pablo, Deseninizde \+bunun yerine yazmak zorundasınız +. Ve insanların neden -esadece bir sed komutu için kullandıklarını anlamıyorum .
Fredrick Gauss

1
kullanımı sed -e -n 's/version=\(.+\)/\1/p' input.txtbkz: mikeplate.com/2012/05/09/…
awattar

1
sed -EPerl / Java / JavaScript / Go / her türlü aromaya çok daha yakın görünen "modern" veya "genişletilmiş" düzenli ifadeleri kullanmanızı öneririm . ( grep -EVeya ile karşılaştır egrep.) Varsayılan sözdiziminde bu garip kaçış kuralları vardır ve "eski" kabul edilir. İkisi arasındaki farklar hakkında daha fazla bilgi için çalıştırın man 7 re_format.
AndrewF

31

grep kullanabilirsiniz

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

4
@ ghostdog74: Kesinlikle sana katılıyorum. Greo'yu yalnızca yakalanan grupların çıktısını nasıl alabilirim?
Pablo

1
@Michael - bu yüzden oseçenek var - unixhelp.ed.ac.uk/CGI/man-cgi?grep : -o, --sadece eşleme Yalnızca eşleşen bir satırın PATTERN ile eşleşen kısmını göster
Bert F

14
@Bert F: Eşleşen kısmı anlıyorum, ancak grup yakalamıyor. İstediğim şu şekilde olmaktır ([0-9] +). + ([Abc] {2,3}) böylece 2 yakalama grubu vardır. SADECE grupları geri bildirimlerle ya da başka bir şekilde yakalayarak çıktı almak istiyorum.
Pablo

Merhaba Michael. Yakalanan n. Grubu grep ile çıkarmayı başardınız mı?
doc_id

1
@Pablo: grep sadece eşleşenleri çıkarıyor. Birden çok grup vermek için, birden çok ifade kullanın: Önceki grep -Eow -e "[0-9]+" -e "[abc]{2,3}"iki grepten gelen borular dışında, bu iki ifadenin bir satırda nasıl olmasını gerektirebileceğinizi bilmiyorum (eğer bir kalıp bir satırda birden fazla eşleşirse yine de çalışmayabilir) ).
idbrii

13

basamak sayısı

Bu cevap herhangi bir sayı grubu ile çalışır. Misal:

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

Genişletilmiş cevap.

Sed'e yalnızca yakalanan grupları vermesini söylemenin bir yolu var mı?

Evet. tüm metni yakalama grubuyla değiştir:

$ 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.

Veya genişletilmiş sözdizimi ile (daha az geri tırnak ve + kullanımına izin ver):

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

Numara olmadığında orijinal metnin yazdırılmasını önlemek için şunu kullanın:

$ echo 'Number xxx in text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1/p'
  • (-n) Girişi varsayılan olarak yazdırmayın.
  • (/ p) yalnızca değiştirme yapıldıysa yazdırın.

Ve birkaç sayıyı eşleştirmek (ve ayrıca yazdırmak):

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

Herhangi bir sayı çalıştırması için çalışır:

$ 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

Hangi grep komutuna çok benzer:

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

Hakkında \ d

ve desen: /([\d]+)/

Sed, '\ d' (kısayol) sözdizimini tanımıyor. Yukarıda kullanılan ascii eşdeğeri [0-9]tam olarak eşdeğer değildir. Tek alternatif çözüm bir karakter sınıfı kullanmaktır: '[[: digit:]] `.

Seçilen cevap bir çözüm oluşturmak için bu tür "karakter sınıflarını" kullanır:

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

Bu çözüm yalnızca (tam olarak) iki basamaklı basamak için geçerlidir.

Elbette, cevap kabuğun içinde yürütülürken, bu cevabı kısaltmak için birkaç değişken tanımlayabiliriz:

$ 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"

Ancak, daha önce açıklandığı gibi, bir s/…/…/gpkomut kullanmak daha iyidir:

$ 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

Bu, hem tekrarlanan basamak çalışmalarını hem de bir kısa (er) komutu yazmayı kapsayacaktır.


Yüksek oylanan kabul edilen cevabı okuduktan sonra şaşırdım, dar kapsamı hakkında yazmak ve aslında sorunun ruhunu ele almak için aşağı kaydırdım. Birinin yıllar önce yapmış olacağını tahmin etmeliydim. Bu çok iyi açıklanmıştır ve gerçek doğru cevaptır.
Amit Naidu

9

Soruda verilen paternin sadece örnek olması gerektiğine ve amacın herhangi bir paternle eşleşmek olduğuna inanıyorum .

Desen alanına yeni satırın eklenmesine izin veren GNU uzantısına sahip bir sed varsa , bir öneri:

> 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

Bu örnekler CYGWIN ile tcsh ile (evet, yanlış kabuk olduğunu biliyorum ). (Düzenleme: Bash için, grubu ve çevresindeki boşlukları kaldırın =.)


@Joseph: teşekkürler, ancak, görevime dayanarak grepdog74'ün önerdiği gibi grep'in daha doğal olduğunu hissediyorum. Sadece grep çıktısının sadece yakalama gruplarının nasıl yapılacağını anlamanız gerekiyor, tüm maç değil.
Pablo

2
Sadece bir not, ancak '+' artı işareti 'bir veya daha fazla' anlamına gelir ve bu da kendinizi kalıplarda tekrarlama ihtiyacını ortadan kaldıracaktır. Yani, "[0-9] [0-9] *" "[0-9] +" olur
RandomInsano

4
@RandomInsano: Kullanmak için +, bundan kaçmanız veya -rseçeneği kullanmanız gerekir ( -EOS X için). Ayrıca kullanabilirsiniz \{1,\}(ya -rya -Ekaçması olmadan).
sonraki duyuruya kadar duraklatıldı.

9

Vazgeç ve Perl kullan

Yana sedkesmek yok, 'adil öyle en azından, Perl havlu atmak ve kullanmasına izin LSB ise grepGNU uzantıları değildir :-)

  • Eşleşen parçanın tamamını yazdırın, eşleşen grup veya gözetleme gerekmez:

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

    Çıktı:

    12
    3456
  • Satır başına tek eşleme, genellikle yapılandırılmış veri alanları:

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

    Çıktı:

    1
    34

    Lookbehind ile:

    cat <<EOS | perl -lane 'print m/(?<=a)(\d+)/'
    a1 b2
    a34 b56
    EOS
  • Birden çok alan:

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

    Çıktı:

    1 2
    34 56
  • Satır başına birden fazla eşleşme, genellikle yapılandırılmamış veriler:

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

    Çıktı:

    1 
    34 78

    Lookbehind ile:

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

    Çıktı:

    1
    3478

1
Sorunun sonunda ne elde etmediniz: "sed ile"?
Moonchild

@ Moonchild Google çalışanları umursamıyor.
Ciro Santilli 法轮功 13 病 六四 事件 法轮功

1
bunu yararlı buldum. Tüm komut satırı normal ifadesi sorunlarının sed ile çözülmesi gerekmez.
PPPaul

5

Deneyin

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"

Bunu cygwin'in altına aldım:

$ (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

OP'nin istediği şey bu değil (grupları yakalama) ancak sayıları kullanarak şunları elde edebilirsiniz:

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

Aşağıdakileri verir:

123
987
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.