Grep RegEx'ten Grup Yakalama


380

Bu küçük komut dosyasını sh(Mac OSX 10.6) bir dizi dosyaya bakmak için aldım . Google bu noktada yardımcı olmayı bıraktı:

files="*.jpg"
for f in $files
    do
        echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
        name=$?
        echo $name
    done

Şimdiye kadar (açıkçası, size gurular için) dosya adının sağlanan konuyla eşleştiğine $namebağlı olarak sadece 0, 1 veya 2 tutar grep. İstediğim şey, ebeveynlerin içindekileri yakalamak ([a-z]+)ve bunu bir değişkene depolamak .

Mümkünse yalnızca kullanmakgrep istiyorum . Değilse, lütfen Python veya Perl, vb. sedVeya bunun gibi bir şey yok - Kabuk için yeniyim ve buna * nix purist açıdan saldırmak istiyorum.

Ayrıca, süper serin bir bonu olarak, kabuğundaki dizeyi nasıl birleştirebileceğimi merak ediyorum? Yakaladığım grup $ sinde saklanan "somename" dizesi miydi ve sonuna ".jpg" dizesini eklemek istedim, yapabilir miyim cat $name '.jpg'?

Eğer vaktiniz varsa lütfen neler olduğunu açıklayın.


30
Grep gerçekten sed'den daha saf mı ?
martin clayton

3
Ah, bunu önermek istememiştim. Sadece burada öğrenmeye çalıştığım bir araçla bir çözüm bulunmasını umuyordum. Eğer kullanarak çözmek mümkün değilse grep, o sedzaman kullanarak çözmek mümkün olsaydı, harika olurdu sed.
Isaac

2
Ben o btw bir :) koymalıydım ...
martin clayton

Psh, beynim bugün çok kızarmış haha.
Isaac

2
@martinclayton Bu ilginç bir argüman olurdu. Gerçekten sed, (veya kesin olarak ed) eski (ve bu nedenle daha saf? Belki?) Unix olacağını düşünüyorum çünkü grep adını ed ifadesi g (lobal) / re (gular expression) / p (rint) türetir.
13'te

Yanıtlar:


499

Bash kullanıyorsanız, aşağıdakileri bile kullanmanıza gerek yoktur grep:

files="*.jpg"
regex="[0-9]+_([a-z]+)_[0-9a-z]*"
for f in $files    # unquoted in order to allow the glob to expand
do
    if [[ $f =~ $regex ]]
    then
        name="${BASH_REMATCH[1]}"
        echo "${name}.jpg"    # concatenate strings
        name="${name}.jpg"    # same thing stored in a variable
    else
        echo "$f doesn't match" >&2 # this could get noisy if there are a lot of non-matching files
    fi
done

Normal ifadeyi bir değişkene koymak daha iyidir. Bazı desenler kelimenin tam anlamıyla dahil edilirse çalışmaz.

=~Bash'ın normal ifade eşleştirme operatörü olan bu kullanır . Maçın sonuçları adlı bir diziye kaydedilir $BASH_REMATCH. İlk yakalama grubu dizin 1'de, ikincisi (varsa) dizin 2'de vb. Saklanır. İndeks sıfır tam eşleşmedir.

Çapa olmadan, bu normal ifadenin (ve kullananın grep) aşağıdaki örneklerden herhangi biriyle ve daha fazlasıyla eşleşeceğini bilmelisiniz; bu , aradığınız şey olmayabilir:

123_abc_d4e5
xyz123_abc_d4e5
123_abc_d4e5.xyz
xyz123_abc_d4e5.xyz

İkinci ve dördüncü örnekleri ortadan kaldırmak için normal ifadenizi şu şekilde yapın:

^[0-9]+_([a-z]+)_[0-9a-z]*

dizenin bir veya daha fazla basamakla başlaması gerektiğini belirtir . Karat, ipin başlangıcını temsil eder. Normal ifadenin sonuna bir dolar işareti eklerseniz, şöyle:

^[0-9]+_([a-z]+)_[0-9a-z]*$

nokta, normal ifadedeki karakterler arasında olmadığından ve dolar işareti dizenin sonunu temsil ettiğinden üçüncü örnek de ortadan kaldırılır. Dördüncü örneğin de bu eşleşmeyi geçemediğini unutmayın.

GNU'nuz varsa grep(yaklaşık 2.5 veya üstü, bence, \Koperatör eklendiğinde):

name=$(echo "$f" | grep -Po '(?i)[0-9]+_\K[a-z]+(?=_[0-9a-z]*)').jpg

\KOperatör (değişken uzunlukta olan ileriye dönük) maça önceki deseni neden olur, ancak sonuç maçı içermez. Sabit uzunluklu eşdeğer (?<=)- desen, kapanış parantezinden önce dahil edilir. Sen kullanmalıdır \Knicelik farklı uzunlukta dizeleri eşleşebilir eğer (örneğin +, *, {2,4}).

(?=)Operatör maçları sabit veya değişken uzunlukta desen ve "ileriye bakma" denir. Sonuçta eşleşen dizeyi de içermez.

Eşleşmeyi büyük / küçük harfe duyarsız hale getirmek için (?i)operatör kullanılır. Onu takip eden kalıpları etkiler, böylece konumu önemlidir.

Normal adın, dosya adında başka karakterler olup olmadığına bağlı olarak ayarlanması gerekebilir. Bu durumda, alt dizenin yakalandığı sırada bir dizeyi bitiştirmeye bir örnek gösterdiğimi göreceksiniz.


48
Bu cevapta, "Normal ifadeyi bir değişkene koymak daha iyi. Bazı desenler kelimenin tam anlamıyla dahil edilirse çalışmaz" diyen belirli satırı kaldırmak istiyorum.
Brandin

5
@FrancescoFrassinelli: Bir örnek, beyaz boşluk içeren bir modeldir. Kaçmak garip ve tırnak işareti kullanamazsınız, çünkü bu onu normal ifadeden sıradan bir dizeye zorlar. Bunu yapmanın doğru yolu bir değişken kullanmaktır. Teklifler ödev sırasında kullanılabilir ve işleri kolaylaştırır.
sonraki duyuruya kadar duraklatıldı.

5
/Koperatör kayalar.
razz

2
@Brandon: İşe yarıyor. Hangi Bash sürümünü kullanıyorsunuz? Bana ne yaptığını göster, işe yaramaz ve belki de nedenini söyleyebilirim.
sonraki duyuruya kadar duraklatıldı.

2
@mdelolmo: Cevabım hakkında bilgiler içeriyor grep. OP tarafından da kabul edildi ve oldukça fazla oy kullanıldı. Downvote için teşekkürler.
sonraki duyuruya kadar duraklatıldı.

145

Bu saf ile gerçekten mümkün değil grep, en azından genel olarak değil.

Ancak, deseniniz uygunsa, grepönce çizginizi bilinen bir biçime azaltmak ve ardından yalnızca istediğiniz biti çıkarmak için bir boru hattı içinde birden çok kez kullanabilirsiniz . (Araçlar gibi cutve bu sedkonuda çok daha iyi olmasına rağmen ).

Diyelim ki deseninizin biraz daha basit olduğu iddiası için: [0-9]+_([a-z]+)_Bunu şu şekilde çıkarabilirsiniz:

echo $name | grep -Ei '[0-9]+_[a-z]+_' | grep -oEi '[a-z]+'

Birincisi grep, genel babanızla eşleşmeyen satırları kaldıracak, ikincisi grep( --only-matchingbelirtilen) adın alfa kısmını görüntüleyecektir. Bu sadece desen uygun olduğu için çalışır: "alfa kısmı" ne istediğinizi ortaya çıkaracak kadar spesifiktir.

(Kenara: Şahsen ben sonradan ne elde etmek için grep+ cutkullanırdım: echo $name | grep {pattern} | cut -d _ -f 2Bu cut, sınırlayıcı üzerinde bölünerek hattı alanlara ayrıştırmak alır _ve sadece alan 2 döndürür (alan numaraları 1'den başlar).

Unix felsefesi, bir şeyi yapan ve iyi yapan araçlara sahip olmak ve önemsiz olmayan görevler elde etmek için bunları birleştirmektir, bu yüzden grep+ sedetc'nin bir şeyler yapmanın daha Unixy bir yolu olduğunu iddia ediyorum :-)


3
for f in $files; do name=echo $ f | grep -oEi '[0-9] + _ ([az] +) _ [0-9a-z] *' | kes -d _ -f 2 ;Aha!
Isaac

2
bu "felsefeye" katılmıyorum. dış komutları çağırmadan kabuğun yerleşik yeteneklerini kullanabiliyorsanız, komut dosyanız performansta çok daha hızlı olacaktır. işlevinde örtüşen bazı araçlar vardır. örneğin grep ve sed ve awk. hepsi dize manipülasyonları yapıyor, ama awk hepsinin üstünde öne çıkıyor çünkü çok daha fazlasını yapabilir. Pratik olarak, yukarıdaki çift açılımlar veya grep + sed gibi komutların zincirlenmesi, tek bir garip işlemle kısaltılabilir.
ghostdog74

7
@ ghostdog74: Burada birçok küçük operasyonu zincirlemenin genellikle hepsini tek bir yerde yapmaktan daha az verimli olduğu iddiası yok, ancak Unix felsefesinin birlikte çalışan birçok araç olduğunu iddia ediyorum. Örneğin, tar sadece dosyaları arşivler, sıkıştırmaz ve varsayılan olarak STDOUT'a çıktığı için ağ üzerinden netcat ile borulandırabilir veya bzip2 vb. İle sıkıştırabilirsiniz. Aklımda konvansiyonu ve genel Unix araçlarının borularda birlikte çalışması gerekir.
RobM

kesim harika - bahşiş için teşekkürler! Takımlara karşı verimlilik argümanı gelince, zincirleme araçlarının sadeliğini seviyorum.
ether_joe


96

Bunun için bir cevabın zaten kabul edildiğini anlıyorum, ancak "kesinlikle * nix saflık açısından" iş için doğru araç gibi görünüyor pcregrep, ki bu henüz belirtilmemiş gibi görünüyor. Çizgileri değiştirmeyi deneyin:

    echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
    name=$?

Aşağıdakilere

    name=$(echo $f | pcregrep -o1 -Ei '[0-9]+_([a-z]+)_[0-9a-z]*')

sadece yakalama grubunun içeriğini almak için 1.

pcregrepAracı zaten kullandım aynı sözdizimi tüm kullanır grep, ancak uygular işlevselliği gerektiğini.

Parametre , çıplaksa sürüm -ogibi çalışır grep, ancak pcregrephangi yakalama grubunu göstermek istediğinizi belirten sayısal bir parametreyi de kabul eder .

Bu çözümle, komut dosyasında minimum bir değişiklik gerekir. Sadece bir modüler yardımcı programı diğeriyle değiştirir ve parametreleri düzenlersiniz.

İlginç Not: Birden çok yakalama grubunu satırda göründükleri sırayla döndürmek için birden çok -o bağımsız değişkenlerini kullanabilirsiniz.


3
pcregrepMac OS XOP'nin kullandığı varsayılan olarak mevcut değildir
grebneke

4
Benim pcregrepsonra rakamı anlamak görünmüyor -o: '-O1 "bilinmeyen seçenek harfi '1'' Ayrıca bu functionaliy hiç söz bakarken.pcregrep --help
Peter Herdenborg

1
@WAF üzgünüm, sanırım bu bilgiyi yorumuma eklemeliydim. Ben CentOS 6.5 çıkıyorum ve pcregrep versiyonu görünüşte çok eskidir: 7.8 2008-09-05.
Peter Herdenborg

2
evet, çok yardım, örneğinecho 'r123456 foo 2016-03-17' | pcregrep -o1 'r([0-9]+)' 123456
zhuguowei

5
pcregrep8.41 ( apt-get install pcregrepon ile kurulmuş Ubuntu 16.03) -Eianahtarı tanımıyor . Olmadan mükemmel çalışır. MacOS ile pcregrepüzerinden yüklenen homebrew(aynı zamanda 8.41) @anishpatel yukarıdaki bahseder gibi High Sierra en azından -Eanahtarı da tanınmaz.
Ville

27

Sadece grep'te mümkün değil inanıyorum

sed için:

name=`echo $f | sed -E 's/([0-9]+_([a-z]+)_[0-9a-z]*)|.*/\2/'`

Yine de bonusu bir bıçakla alacağım:

echo "$name.jpg"

2
Ne yazık ki, bu sedçözüm işe yaramıyor. Sadece dizinimdeki her şeyi yazdırır.
Isaac

güncellendi, bir eşleşme yoksa boş bir satır
çıkarır

Artık sadece boş satırlar çıkıyor!
Isaac

bu sed'in bir sorunu var. İlk yakalama parantez grubu her şeyi kapsar. Tabii ki \ 2 hiçbir şey olmayacak.
ghostdog74

bazı basit test
senaryoları

16

Bu gawk kullanan bir çözümdür. Sık kullanmam gereken bir şey buldum, bu yüzden bunun için bir işlev oluşturdum

function regex1 { gawk 'match($0,/'$1'/, ary) {print ary['${2:-'1'}']}'; }

kullanmak sadece yapmak

$ echo 'hello world' | regex1 'hello\s(.*)'
world

Harika bir fikir, ancak normal ifadedeki boşluklarla çalışmıyor gibi görünüyor - bunların değiştirilmesi gerekiyor \s. Nasıl düzelteceğinizi biliyor musunuz?
Adam Ryczkowski

4

Sizin için bir öneri - adın bir kısmını son alt çizgiden itibaren ve benzer şekilde başlangıçta kaldırmak için parametre genişletmeyi kullanabilirsiniz:

f=001_abc_0za.jpg
work=${f%_*}
name=${work#*_}

Sonra namedeğeri olacaktır abc.

Bkz. Apple geliştirici belgeleri , 'Parametre Genişletme' için arama yapın.


bu ([az] +) öğesini kontrol etmez.
ghostdog74

@ levislevis - bu doğru, ancak OP tarafından yorumlandığı gibi, gerekli olanı yapıyor.
martin clayton

2

eğer bashınız varsa, genişletilmiş globbing kullanabilirsiniz

shopt -s extglob
shopt -s nullglob
shopt -s nocaseglob
for file in +([0-9])_+([a-z])_+([a-z0-9]).jpg
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done

veya

ls +([0-9])_+([a-z])_+([a-z0-9]).jpg | while read file
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done

İlginç görünüyor. Belki biraz açıklama yapabilir misiniz? Ya da, eğer bu kadar eğimliyseniz, açıklayan özellikle anlayışlı bir kaynağa bağlanın? Teşekkürler!
Isaac
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.