Alternancia / operador Regex (foo | bar) en GNU o BSD Sed

28

Parece que no puedo hacer que funcione. La documentación de GNU sed dice escapar de la tubería, pero eso no funciona, ni usar una tubería recta sin la fuga. Agregar padres no hace ninguna diferencia.

$ 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
Gregg Leventhal
fuente

Respuestas:

33

Por defectosed usa expresiones regulares básicas POSIX , que no incluyen el |operador de alternancia. Muchas versiones de sed, incluyendo GNU y FreeBSD, admiten el cambio a Expresiones regulares extendidas , que incluyen |alternancia. Cómo lo hace varía: GNU sed usa-r , mientras que FreeBSD , NetBSD , OpenBSD y OS X sed usan -E. Otras versiones en su mayoría no lo admiten en absoluto. Puedes usar:

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

y funcionará en esos sistemas BSD y sed -rcon GNU.


GNU sedparece tener un soporte totalmente indocumentado pero funcional -E, por lo que si tiene un script multiplataforma limitado a lo anterior, esa es su mejor opción. Sin embargo, dado que no está documentado, probablemente no pueda confiar en él.

Un comentario señala que las versiones BSD también son compatibles -rcon un alias no documentado. OS X todavía no lo hace hoy y las máquinas más antiguas de NetBSD y OpenBSD a las que tengo acceso tampoco, pero la NetBSD 6.1 sí. Los Unices comerciales que puedo alcanzar universalmente no lo hacen. Entonces, con todo eso, la pregunta de portabilidad se está volviendo bastante complicada en este momento, pero la respuesta simple es cambiar aawk si lo necesita, que usa ERE en todas partes.

Michael Homer
fuente
Los tres BSD se mencionan todo el apoyo de la -ropción como sinónimo de -Ela compatibilidad con sed de GNU. OpenBSD y OS X sed -Einterpretarán la tubería escapada como una tubería literal, no como un operador de alternancia. Aquí hay un enlace de trabajo a la página de manual de NetBSD y aquí hay uno para OpenBSD que no tiene diez años.
damien
9

Esto sucede porque (a|b)es una expresión regular extendida, no una expresión regular básica. Use la -Eopción para lidiar con esto.

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

Desde la sedpágina del manual:

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

Tenga en cuenta que -res otro indicador para lo mismo, pero -Ees más portátil e incluso estará en la próxima versión de las especificaciones POSIX.

Networker
fuente
6

La forma portátil de hacer esto, y la forma más eficiente, es con direcciones. Puedes hacerlo:

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

De esta manera, si la línea no contiene el gato de cadena y no contiene los rangos del perro de cadena sed bfuera de la secuencia de comandos, imprime automáticamente su línea actual y tira de la siguiente para comenzar el siguiente ciclo. Por lo tanto, no realiza la siguiente instrucción, que en este ejemplo ccuelga toda la línea para leer Bear pero podría hacer cualquier cosa.

Probablemente valga la pena señalar también que cualquier instrucción que siga al comando !ben ese sedcomando solo puede coincidir en una línea que contenga la cadena dogo cat, por lo que puede realizar más pruebas sin peligro de coincidir con una línea que no lo hace, lo que significa que ahora puede aplicar reglas solo a uno u otro también.

Pero eso es lo siguiente. Aquí está la salida del comando anterior:

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

También puede implementar de forma portátil una tabla de búsqueda con referencias posteriores.

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

Es mucho más trabajo configurarlo para este caso de ejemplo simple, pero puede hacer sedscripts mucho más flexibles a largo plazo.

En la primera línea, xcambio el espacio de espera y el espacio de patrón, luego inserto el perro <space>gato de<space><space> cuerda en el espacio de espera antes de xvolver a cambiarlos.

De ahí en adelante y en cada línea siguiente, mantengo el Gespacio agregado al espacio del patrón, luego verifico si todos los caracteres desde el comienzo de la línea hasta la nueva línea que acabo de agregar al final coinciden con una cadena rodeada de espacios después. Si es así, reemplazo todo el lote con Bear y, si no, no hay ningún daño porque la próxima vez que llevo Psolo hasta la primera línea nueva en el espacio del patrón, delijo todo.

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

Y cuando digo flexible, lo digo en serio. Aquí está reemplazando el gato con BrownBear y el perro 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

Por supuesto, puede ampliar mucho el contenido de la tabla de búsqueda: tomé la idea de los correos electrónicos de Usenet de Greg Ubben sobre el tema cuando, en los años 90, describió cómo construyó una calculadora cruda a partir de una sola sed s///declaración.

mikeserv
fuente
1
phew, +1. Tengo una inclinación por pensar fuera de la caja, debo decir
iruvar
@ 1_CR - Vea mi última edición, no es mi idea, lo que no quiere decir que no aprecio eso y lo considero un cumplido. Pero me gusta dar crédito donde es debido.
mikeserv
1

Esta es una pregunta bastante antigua, pero en caso de que alguien quiera intentarlo, hay una forma bastante baja de hacerlo en sed con archivos sed. Cada opción se puede enumerar en una línea separada, y sed evaluará cada una. Es un equivalente lógico de o. Por ejemplo, para eliminar líneas que contienen un código determinado:

puedes decir : sed -E '/^\/\*!(40103|40101|40111).*\/;$/d'

o pon esto en tu archivo sed:

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

Aquí hay una técnica que no utiliza ninguna opción específica de implementación para sed(por ejemplo -E, -r). En lugar de describir el patrón como una única expresión regular cat|dog, simplemente podemos ejecutar seddos veces:

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

Es una solución obvia realmente, pero vale la pena compartirla. Naturalmente, se generaliza a más de dos cadenas de patrones, aunque una cadena muy larga de sed's' no es demasiado atractiva.

A menudo uso sed -i(que funciona igual en todas las implementaciones) para hacer cambios en los archivos. Aquí, se puede incorporar una larga lista de cadenas de patrones, ya que cada resultado temporal se guarda en el archivo:

for pattern in cat dog owl; do
    sed -i "s/${pattern}/Bear/g" myfile
done
jmd_dk
fuente