Estrarre una regex abbinata a 'sed' senza stampare i personaggi circostanti


24

A tutti i dottori "sed" là fuori:

Come puoi ottenere 'sed' per estrarre un'espressione regolare che ha trovato una riga?

In altre parole, voglio solo la stringa corrispondente all'espressione regolare con tutti i caratteri non corrispondenti della riga contenente eliminati.

Ho provato a utilizzare la funzione di riferimento posteriore come di seguito

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

questo funziona per alcune espressioni come

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

che estrae ordinatamente tutti i nomi di macro che iniziano con 'CONFIG_ ....' (presente in alcuni file '* .h') e li stampa tutti riga per riga

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

MA quanto sopra si rompe per qualcosa di simile

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

questo restituisce sempre cifre singole come

                 7
                 9
                 .
                 .  
                 6

anziché estrarre un campo numerico contiguo come.

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

PS: Sarei grato di dare un feedback su come questo si ottiene in "sed". Io so come farlo con 'grep' e 'awk' Vorrei sapere se la mia - seppur limitato - la comprensione di 'sed' ha buchi in essa e se non v'è modo per fare questo in 'sed' che io
ho semplicemente trascurato.

Risposte:


22

Quando una regexp contiene gruppi, potrebbe esserci più di un modo per far corrispondere una stringa con essa: le regexps con i gruppi sono ambigue. Ad esempio, considera regexp ^.*\([0-9][0-9]*\)$e la stringa a12. Vi sono due possibilità:

  • Partita acontro .*e 2contro [0-9]*; 1è abbinato da [0-9].
  • Match a1contro .*e stringa vuota contro [0-9]*; 2è abbinato da [0-9].

Sed, come tutti gli altri strumenti regexp disponibili, applica la prima regola di corrispondenza più lunga: cerca innanzitutto di far corrispondere la prima porzione di lunghezza variabile a una stringa il più lunga possibile. Se trova un modo per abbinare il resto della stringa al resto della regexp, va bene. Altrimenti, sed prova la corrispondenza più lunga successiva per la prima porzione di lunghezza variabile e riprova.

Qui, la corrispondenza con la stringa più lunga prima è a1contro .*, quindi il gruppo corrisponde solo 2. Se vuoi che il gruppo inizi prima, alcuni motori regexp ti rendono .*meno avido, ma sed non ha una tale funzionalità. Quindi è necessario rimuovere l'ambiguità con qualche ancoraggio aggiuntivo. Specifica che il primo .*non può terminare con una cifra, in modo che la prima cifra del gruppo sia la prima corrispondenza possibile.

  • Se il gruppo di cifre non può essere all'inizio della riga:

    sed -n 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p'
    
  • Se il gruppo di cifre può essere all'inizio della riga e sed sed supporta l' \?operatore per parti opzionali:

    sed -n 's/^\(.*[^0-9]\)\?\([0-9][0-9]*\).*/\1/p'
    
  • Se il gruppo di cifre può essere all'inizio della riga, attenersi ai costrutti regexp standard:

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

A proposito, è la stessa prima regola di corrispondenza più lunga che fa [0-9]*corrispondere le cifre dopo la prima, piuttosto che la successiva .*.

Nota che se ci sono più sequenze di cifre su una riga, il tuo programma estrae sempre l'ultima sequenza di cifre, sempre a causa della prima regola di corrispondenza più lunga applicata all'iniziale .*. Se si desidera estrarre la prima sequenza di cifre, è necessario specificare che ciò che precede è una sequenza di non cifre.

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

Più in generale, per estrarre la prima corrispondenza di una regexp, è necessario calcolare la negazione di quella regexp. Mentre questo è sempre teoricamente possibile, la dimensione della negazione cresce esponenzialmente con la dimensione della regexp che stai negando, quindi questo è spesso poco pratico.

Considera il tuo altro esempio:

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

Questo esempio mostra in realtà lo stesso problema, ma non lo vedi su input tipici. Se si alimentano hello CONFIG_FOO_CONFIG_BAR, quindi il comando precedente stampe fuori CONFIG_BAR, non è CONFIG_FOO_CONFIG_BAR.

C'è un modo per stampare la prima partita con sed, ma è un po 'complicato:

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

(Supponendo che il tuo supporto \nper sed significhi una nuova riga nel stesto di sostituzione.) Questo funziona perché sed cerca la prima corrispondenza del regexp e non cerchiamo di abbinare ciò che precede il CONFIG_…bit. Dal momento che non ci sono newline all'interno della linea, possiamo usarla come marker temporaneo. Il Tcomando dice di arrendersi se il scomando precedente non corrisponde.

Quando non riesci a capire come fare qualcosa in sed, passa a awk. Il comando seguente stampa la prima corrispondenza più lunga di una regexp:

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

E se hai voglia di mantenerlo semplice, usa Perl.

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

22

Mentre no sed, una delle cose spesso trascurate per questo è grep -o, che secondo me è lo strumento migliore per questo compito.

Ad esempio, se vuoi ottenere tutti i CONFIG_parametri da una configurazione del kernel, dovresti usare:

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

Se vuoi ottenere sequenze contigue di numeri:

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

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

... lo farà senza problemi, anche se potresti aver bisogno di nuove righe letterali al posto della ns nel campo di sostituzione di destra. E, a proposito, la .*CONFIGcosa funzionerebbe solo se ci fosse solo una partita sulla linea - altrimenti otterrebbe sempre solo l'ultima.

Puoi vedere questo per una descrizione di come funziona, ma questo verrà stampato su una riga separata solo la corrispondenza quante volte si verifica su una riga.

Puoi usare la stessa strategia per ottenere la [num]ricorrenza su una linea. Ad esempio, se si desidera stampare la corrispondenza CONFIG solo se era la terza su una riga:

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

... anche se ciò presuppone che le CONFIGstringhe siano separate da almeno un carattere non alfanumerico per ogni occorrenza.

Suppongo - per la cosa dei numeri - questo funzionerebbe anche:

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

... con lo stesso avvertimento di prima sulla mano destra \n. Questo sarebbe anche più veloce del primo, ma non può applicarsi come generalmente, ovviamente.

Per la cosa CONFIG potresti usare il P;...;Dloop sopra con il tuo pattern, oppure puoi fare:

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

... che è solo un po 'più coinvolto e funziona ordinando correttamente sedla priorità di riferimento. Inoltre, isola tutte le corrispondenze CONFIG su una riga in una volta sola - sebbene faccia lo stesso presupposto di prima - che ogni corrispondenza CONFIG sarà separata da almeno un carattere non alfanumerico. Con GNU sedpotresti scriverlo:

sed -En 's/[^C]*(CONFIG\w*)?C?/\1\n/g;s/(\n)*/\1/g;/C/s/.$//p'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.