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
a
contro .*
e 2
contro [0-9]*
; 1
è abbinato da [0-9]
.
- Match
a1
contro .*
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 è a1
contro .*
, 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 \n
per sed significhi una nuova riga nel s
testo 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 T
comando dice di arrendersi se il s
comando 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