Wyodrębnianie wyrażenia regularnego dopasowanego do „sed” bez drukowania otaczających znaków


24

Do wszystkich „sed” lekarzy:

Jak uzyskać „sed”, aby wyodrębnić wyrażenie regularne, które pasowało do linii?

Innymi słowy, chcę tylko ciąg odpowiadający wyrażeniu regularnemu ze wszystkimi niepasującymi znakami z linii zawierającej.

Próbowałem użyć funkcji odsyłacza wstecznego, jak poniżej

regular expression to be isolated 
         gets `inserted` 
              here     
               |
               v  
 sed -n 's/.*\( \).*/\1/p 

działa to w przypadku niektórych wyrażeń takich jak

 sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p 

który starannie wyodrębnia wszystkie nazwy makr zaczynające się od „CONFIG_ ....” (znalezione w pliku „* .h”) i drukuje je wszystkie wiersz po wierszu

          CONFIG_AT91_GPIO
          CONFIG_DRIVER_AT91EMAC
                   .
                   .   
          CONFIG_USB_ATMEL
          CONFIG_USB_OHCI_NEW
                   .
                 e.t.c. 

ALE powyższe dzieli się na coś takiego

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

to zawsze zwraca pojedyncze cyfry jak

                 7
                 9
                 .
                 .  
                 6

zamiast wyodrębniać ciągłe pole liczbowe, takie jak.

              8908078
              89670890  
                 .
                 .  
                 .
               23019   
                 .
               e.t.c.  

PS: Byłbym wdzięczny za informację zwrotną na temat tego, jak można to osiągnąć w „sed”. Wiem, jak to zrobić z „grep” i „awk” Chciałbym się dowiedzieć, czy moje - choć ograniczone - rozumienie „sed” posiada otwory w nim i jeśli istnieje sposób, aby to zrobić w „sed”, który ja
mam po prostu przeoczyłem.

Odpowiedzi:


22

Gdy wyrażenie regularne zawiera grupy, może istnieć więcej niż jeden sposób dopasowania ciągu do niego: wyrażenia regularne z grupami są niejednoznaczne. Na przykład rozważ wyrażenie regularne ^.*\([0-9][0-9]*\)$i ciąg a12. Istnieją dwie możliwości:

  • Dopasuj aprzeciw .*i 2przeciw [0-9]*; 1jest dopasowany przez [0-9].
  • Dopasuj a1do .*i pusty ciąg przeciw [0-9]*; 2jest dopasowany przez [0-9].

Sed, podobnie jak wszystkie inne narzędzia regexp, stosuje najwcześniejszą zasadę najdłuższego dopasowania: najpierw próbuje dopasować pierwszą część o zmiennej długości do łańcucha, który jest tak długi, jak to możliwe. Jeśli znajdzie sposób, aby dopasować resztę ciągu do reszty wyrażenia regularnego, dobrze. W przeciwnym razie sed próbuje następnego najdłuższego dopasowania dla pierwszej części o zmiennej długości i próbuje ponownie.

Tutaj dopasowanie do najdłuższego ciągu jest najpierw a1przeciw .*, więc grupa dopasowuje tylko 2. Jeśli chcesz, aby grupa zaczęła wcześniej, niektóre silniki wyrażeń regularnych pozwalają ci być .*mniej chciwym, ale sed nie ma takiej funkcji. Musisz więc usunąć niejednoznaczność za pomocą dodatkowej kotwicy. Określ, że wiodący .*nie może kończyć się cyfrą, aby pierwsza cyfra w grupie była pierwszym możliwym dopasowaniem.

  • Jeśli grupa cyfr nie może znajdować się na początku wiersza:

    sed -n 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p'
    
  • Jeśli grupa cyfr może znajdować się na początku wiersza, a Twój sed obsługuje \?operatora dla opcjonalnych części:

    sed -n 's/^\(.*[^0-9]\)\?\([0-9][0-9]*\).*/\1/p'
    
  • Jeśli grupa cyfr może znajdować się na początku wiersza, trzymaj się standardowych konstrukcji wyrażeń regularnych:

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

Nawiasem mówiąc, to ta sama najwcześniejsza najdłuższa reguła dopasowania, która powoduje [0-9]*dopasowanie cyfr po pierwszej, a nie następnej .*.

Zauważ, że jeśli w linii znajduje się wiele sekwencji cyfr, Twój program zawsze wyodrębni ostatnią sekwencję cyfr, ponownie ze względu na najwcześniejszą najdłuższą regułę dopasowania zastosowaną do początkowej .*. Jeśli chcesz wyodrębnić pierwszą sekwencję cyfr, musisz określić, że to, co ma miejsce wcześniej, to sekwencja niecyfrowa.

sed -n 's/^[^0-9]*\([0-9][0-9]*\).*$/\1/p'

Mówiąc bardziej ogólnie, aby wyodrębnić pierwsze dopasowanie wyrażenia regularnego, musisz obliczyć negację tego wyrażenia regularnego. Chociaż zawsze jest to teoretycznie możliwe, rozmiar negacji rośnie wykładniczo wraz z wielkością wyrażenia regularnego, którego negujesz, więc często jest to niepraktyczne.

Rozważ swój inny przykład:

sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p'

Ten przykład faktycznie pokazuje ten sam problem, ale nie widać go na typowych danych wejściowych. Jeśli go nakarmisz hello CONFIG_FOO_CONFIG_BAR, to powyższe polecenie zostanie wydrukowane CONFIG_BAR, a nie CONFIG_FOO_CONFIG_BAR.

Jest sposób na wydrukowanie pierwszego meczu za pomocą sed, ale jest to trochę trudne:

sed -n -e 's/\(CONFIG_[a-zA-Z0-9_]*\).*/\n\1/' -e T -e 's/^.*\n//' -e p

(Zakładając, że twój sed obsługuje \noznaczenie nowego wiersza w stekście zastępującym). Działa to, ponieważ sed szuka najwcześniejszego dopasowania wyrażenia regularnego, a my nie próbujemy dopasować tego, co poprzedza CONFIG_…bit. Ponieważ w linii nie ma nowego wiersza, możemy go użyć jako znacznika tymczasowego. TKomenda mówi zrezygnować jeśli poprzedzający skomenda nie pasuje.

Kiedy nie możesz wymyślić, jak coś zrobić w sed, przejdź do awk. Następujące polecenie wypisuje najwcześniejsze dopasowanie wyrażenia regularnego:

awk 'match($0, /[0-9]+/) {print substr($0, RSTART, RLENGTH)}'

A jeśli masz ochotę to uprościć, użyj Perla.

perl -l -ne '/[0-9]+/ && print $&'       # first match
perl -l -ne '/^.*([0-9]+)/ && print $1'  # last match

22

Chociaż nie sed, jedną z rzeczy często pomijanych w tym jest grep -o, która moim zdaniem jest lepszym narzędziem do tego zadania.

Na przykład, jeśli chcesz uzyskać wszystkie CONFIG_parametry z konfiguracji jądra, użyj:

# grep -Eo 'CONFIG_[A-Z0-9_]+' config
CONFIG_64BIT
CONFIG_X86_64
CONFIG_X86
CONFIG_INSTRUCTION_DECODER
CONFIG_OUTPUT_FORMAT

Jeśli chcesz uzyskać ciągłe ciągi liczb:

$ grep -Eo '[0-9]+' foo

7
sed '/\n/P;//!s/[0-9]\{1,\}/\n&\n/;D'

... zrobi to bez żadnego zamieszania, chociaż możesz potrzebować dosłownych znaków nowej linii zamiast ns w polu zastępowania po prawej stronie. Nawiasem mówiąc, .*CONFIGrzecz działałaby tylko, gdyby na linii był tylko jeden mecz - w przeciwnym razie zawsze byłaby tylko ostatnia.

Widać to na opis jak to działa, ale to będzie drukować na osobnej linii tylko dopasować tak wiele razy, jak to występuje na linii.

Możesz użyć tej samej strategii, aby uzyskać [num]wystąpienie na linii. Na przykład, jeśli chcesz wydrukować dopasowanie CONFIG tylko wtedy, gdy było to trzecie takie w linii:

sed '/\n/P;//d;s/CONFIG[[:alnum:]]*/\n&\n/3;D'

... choć zakłada to, że CONFIGłańcuchy są oddzielone przez co najmniej jeden znak alfanumeryczny dla każdego wystąpienia.

Przypuszczam, że - jeśli chodzi o liczbę - działałoby to również:

sed -n 's/[^0-9]\{1,\}/\n/g;s/\n*\(.*[0-9]\).*/\1/p

... z takim samym zastrzeżeniem jak poprzednio, dotyczącym prawej ręki \n. Ten byłby nawet szybszy niż pierwszy, ale oczywiście nie można go stosować ogólnie.

Dla CONFIG możesz użyć P;...;Dpowyższej pętli ze swoim wzorem lub możesz:

sed -n 's/[^C]*\(CONFIG[[:alnum:]]*\)\{0,1\}C\{0,1\}/\1\n/g;s/\(\n\)*/\1/g;/C/s/.$//p'

... który jest tylko trochę bardziej zaangażowany i działa poprzez prawidłowe zamówienie sedreferencyjnego priorytetu. Izoluje również wszystkie dopasowania CONFIG na linii za jednym razem - choć przyjmuje to samo założenie, jak wcześniej - że każde dopasowanie CONFIG będzie oddzielone co najmniej jednym znakiem niealfanumerycznym. Z GNU sedmożesz to napisać:

sed -En 's/[^C]*(CONFIG\w*)?C?/\1\n/g;s/(\n)*/\1/g;/C/s/.$//p'
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.