Alternanza / operatore Regex (foo | bar) in GNU o BSD Sed


28

Non riesco a farlo funzionare. La documentazione di GNU sed dice di fuggire dalla pipa, ma ciò non funziona, né usare una pipa diritta senza la fuga. L'aggiunta di parentesi non fa differenza.

$ echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat|dog/Bear/g'
cat
dog
pear
banana
cat
dog

$ echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat\|dog/Bear/g'
cat
dog
pear
banana
cat
dog

Risposte:


33

Per impostazione predefinitased utilizza le espressioni regolari di base POSIX , che non includono l' |operatore di alternanza. Molte versioni di sed, tra cui GNU e FreeBSD, supportano il passaggio a espressioni regolari estese , che includono l' |alternanza. Come lo fai varia: GNU sed usa-r , mentre FreeBSD , NetBSD , OpenBSD e OS X sed usano -E. Altre versioni per lo più non lo supportano affatto. Puoi usare:

echo 'cat dog pear banana cat dog' | sed -E -e 's/cat|dog/Bear/g'

e funzionerà su quei sistemi BSD e sed -rcon GNU.


sedSembra che GNU abbia un supporto totalmente privo di documenti ma funzionante -E, quindi se hai uno script multipiattaforma limitato a quanto sopra è l'opzione migliore. Dal momento che non è documentato, probabilmente non puoi davvero fare affidamento su di esso, però.

Un commento osserva che le versioni di BSD supportano anche -rcome alias non documentato. OS X non lo è ancora oggi e neanche i vecchi computer NetBSD e OpenBSD a cui ho accesso, ma quello NetBSD 6.1. Gli Unici commerciali che posso raggiungere universalmente non lo fanno. Quindi, nonostante tutto, la domanda sulla portabilità sta diventando piuttosto complicata a questo punto, ma la semplice risposta è passare aawk se ne hai bisogno, che utilizza ERE ovunque.


I tre BSD citati supportano tutti l' -ropzione come sinonimo di -Ecompatibilità con GNU sed. OpenBSD e OS X sed -Einterpreteranno la pipe di escape come una pipe letterale, non come un operatore di alternanza. Ecco un link funzionante alla pagina man di NetBSD ed eccone uno per OpenBSD che non ha dieci anni.
Damiano,



9

Ciò accade perché (a|b)è un'espressione regolare estesa, non un'espressione regolare di base. Utilizzare l' -Eopzione per affrontare questo.

echo 'cat
dog
pear
banana
cat
dog'|sed -E 's/cat|dog/Bear/g'

Dalla sedpagina man:

 -E      Interpret regular expressions as extended (modern) regular
         expressions rather than basic regular expressions (BRE's).

Si noti che -rè un altro flag per la stessa cosa, ma -Eè più portatile e sarà anche nella prossima versione delle specifiche POSIX.


6

Il modo portatile per farlo - e il modo più efficiente - è con gli indirizzi. Puoi farlo:

printf %s\\n cat dog pear banana cat dog |
sed -e '/cat/!{/dog/!b' -e '};cBear'

In questo modo, se la linea non contiene la stringa gatto e non contiene la stringa cane sed b ranch fuori dello script, autoprints la sua linea attuale e tira nel prossimo per iniziare il ciclo successivo. Pertanto non esegue le istruzioni successive, che in questo esempio cimpiccano l'intera riga per leggere Orso ma potrebbe fare qualsiasi cosa.

Probabilmente vale la pena notare anche che qualsiasi istruzione che segue il comando !bin quel sedcomando può corrispondere solo su una riga contenente la stringa dogo cat- quindi è possibile eseguire ulteriori test senza il pericolo di abbinare una riga che non lo fa - il che significa che ora è possibile applicare le regole anche solo l'uno o l'altro.

Ma questo è il prossimo. Ecco l'output del comando sopra:

###OUTPUT###
Bear
Bear
pear
banana
Bear
Bear

È inoltre possibile implementare in modo portabile una tabella di ricerca con riferimenti indietro.

printf %s\\n cat dog pear banana cat dog |
sed '1{x;s/^/ cat dog /;x
};G;s/^\(.*\)\n.* \1 .*/Bear/;P;d'

È molto più complicato da configurare per questo semplice esempio, ma seda lungo termine può rendere gli script molto più flessibili .

Nella prima riga xcambio spazio di mantenimento e spazio modello, quindi inserisco il cane <space>gatto<space><space> stringa nello spazio di attesa prima di xcambiarlo indietro.

Da quel momento in poi e su ogni riga successiva I Get hold spazio aggiunto allo spazio pattern, quindi controlla per vedere se tutti i caratteri dall'inizio della riga fino alla nuova riga che ho appena aggiunto alla fine corrispondono a una stringa circondata da spazi dopo di essa. In tal caso, sostituisco l'intero lotto con Orso e, in caso contrario, non viene fatto alcun danno, poiché successivamente Print solo fino alla prima nuova riga presente nello spazio modello, delimino tutto.

###OUTPUT###
Bear
Bear
pear
banana
Bear
Bear

E quando dico flessibile, intendo. Qui sta sostituendo il gatto con BrownBear e il cane con BlackBear :

printf %s\\n cat dog pear banana cat dog |
sed '1{x;s/^/ 1cat Brown 2dog Black /;x
};G;s/^\(.*\)\n.* [0-9]\1 \([^ ]*\) .*/\2Bear/;P;d'

###OUTPUT###
BrownBear
BlackBear
pear
banana
BrownBear
BlackBear

Ovviamente puoi espandere molto il contenuto della tabella di ricerca: ho preso l'idea dalle e -mail usenet di Greg Ubben sull'argomento quando, negli anni '90, ha descritto come ha costruito un calcolatore grezzo da una singola sed s///affermazione.


1
phew, +1. Hai un debole per pensare fuori dagli schemi, devo dire
iruvar,

@ 1_CR - Vedi la mia ultima modifica - non la mia idea - il che non vuol dire che non lo apprezzo e lo considero un complimento. Ma mi piace dare credito dove è dovuto.
Mikeserv,

1

questa è una domanda piuttosto vecchia, ma nel caso qualcuno voglia provare, c'è un modo abbastanza basso per farlo in sed con file sed. Ogni opzione può essere elencata su una riga separata e sed valuterà ciascuna. È un equivalente logico di o. Ad esempio, per rimuovere le righe che contengono un determinato codice:

si può dire : sed -E '/^\/\*!(40103|40101|40111).*\/;$/d'

o inseriscilo nel tuo file sed:

/^\/\*!40103.*\/;$/d
/^\/\*!40101.*\/;$/d
/^\/\*!40111.*\/;$/d

0

Qui è una tecnica che non fa uso di opzioni di implementazione specifici sed(ad esempio -E, -r). Invece di descrivere il modello come una singola regex cat|dog, possiamo semplicemente eseguire seddue volte:

echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat/Bear/g' | sed 's/dog/Bear/g'

È davvero una soluzione ovvia, ma vale la pena condividerla. Si generalizza naturalmente a più di due stringhe di pattern, sebbene una catena molto lunga sednon sia troppo bella.

Uso spesso sed -i(che funziona allo stesso modo in tutte le implementazioni) per apportare modifiche ai file. Qui, un lungo elenco di stringhe di pattern può essere incorporato correttamente, poiché ogni risultato temporaneo viene salvato nel file:

for pattern in cat dog owl; do
    sed -i "s/${pattern}/Bear/g" myfile
done
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.