2013-05-23 12 views
5

Ho due stringhe, ad esempio foo_bar e foo_abc_bar. Vorrei abbinare entrambi, e se il primo è abbinato vorrei sottolineare con il segno =. Quindi, la mia ipotesi era:Perché sed non stampa un gruppo opzionale?

echo 'foo_abc_bar' | sed -r 's/(foo).*(abc)?.*(bar)/\1=\2=\3/g' 
> foo==bar 

o

echo 'foo_abc_bar' | sed -r 's/(foo).*((abc)?).*(bar)/\1=\2=\3/g' 
> foo== 

Ma, come output mostra sopra nessuno di loro lavoro.

Come è possibile specificare un gruppo opzionale che corrisponderà se la stringa lo contiene o se non lo si salta?

+0

Perché stai usando '. *' Nell'espressione, i caratteri di sottolineatura possono essere sostituiti con qualsiasi stringa arbitraria? –

risposta

8

La soluzione:

echo 'foo_abc_bar' | sed -r 's/(foo)_((abc)_)?(bar)/\1=\3=\4/g' 

Perché i tuoi tentativi precedenti non ha funzionato:

.* è avido, così per la regex (foo).*(abc)?.*(bar) tentativo di abbinare il 'foo_abc_bar'(foo) corrisponderà 'foo', e quindi .* corrisponderà inizialmente al resto della stringa ('_abc_bar'). La regex continuerà fino a raggiungere il gruppo richiesto (bar) e ciò non riuscirà, a quel punto la regex tornerà indietro rinunciando ai caratteri che erano stati abbinati dallo .*. Ciò avverrà fino a quando il primo .* corrisponde solo a '_abc_', a quel punto il gruppo finale può corrispondere a 'bar'. Pertanto, anziché lo 'abc' nella stringa corrispondente al gruppo di acquisizione, viene confrontato con il codice non acquisibile .*.

Spiegazione della mia soluzione:

La prima cosa e più importante è quello di sostituire il .* con _, non v'è alcuna necessità di adattarsi a qualsiasi stringa arbitraria se si sa cosa il separatore sarà. La prossima cosa che dobbiamo fare è capire esattamente quale parte della stringa è opzionale. Se le stringhe 'foo_abc_bar' e 'foo_bar' sono entrambe valide, lo 'abc_' nel mezzo è facoltativo. Possiamo metterlo in un gruppo opzionale usando (abc_)?. L'ultimo passaggio consiste nell'assicurarsi di avere ancora la stringa 'abc' in un gruppo di acquisizione, che possiamo eseguire avvolgendo quella parte in un altro gruppo, quindi finiamo con ((abc)_)?. Abbiamo quindi bisogno di regolare la sostituzione perché c'è un gruppo in più, quindi invece di \1=\2=\3 usiamo \1=\3=\4, \2 sarebbe la stringa 'abc_' (se corrisponde). Si noti che nella maggior parte delle implementazioni di espressioni regolari si poteva anche usare un gruppo non catturante e continuare a usare \1=\2=\3, ma sed non supporta i gruppi non di cattura.

Un'alternativa:

penso che l'espressione regolare di cui sopra è la soluzione migliore, perché è più esplicita (che corrisponderà solo le stringhe esatte che vi interessano). Tuttavia si potrebbe anche evitare il problema descritto sopra usando la ripetizione lazy (corrisponde al minor numero possibile di caratteri) invece di una ripetizione avida (corrisponde al maggior numero possibile di caratteri).È possibile farlo modificando il .*-.*?, così la vostra espressione sarebbe simile a questa:

echo 'foo_abc_bar' | sed -r 's/(foo).*?(abc).*?(bar)/\1=\2=\3/g' 
1

forse si potrebbe usare semplicemente:

echo 'foo_abc_bar' | sed -r 's/(foo|bar|abc)_?/\1=/g' 
echo 'foo_bar' | sed -r 's/(foo|bar|abc)_?/\1=/g' 

> foo=abc=bar= 
> foo=bar= 

Questo evita la foo==bar si ottiene con foo_bar ed io Ho trovato un po 'strano mostrare enfasi mettendo a volte lo = prima della partita, a volte dopo la partita.

Problemi correlati