¿Cómo reemplazar múltiples patrones a la vez con sed?


231

Supongamos que tengo una cadena 'abbc' y quiero reemplazar:

  • ab -> bc
  • bc -> ab

Si intento dos reemplazos, el resultado no es lo que quiero:

echo 'abbc' | sed 's/ab/bc/g;s/bc/ab/g'
abab

Entonces, ¿qué comando sed puedo usar para reemplazar como se muestra a continuación?

echo abbc | sed SED_COMMAND
bcab

EDITAR : En realidad, el texto podría tener más de 2 patrones y no sé cuántos reemplazos necesitaré. Dado que hubo una respuesta que decía que sedes un editor de flujo y sus reemplazos son codiciosos, creo que necesitaré usar un lenguaje de script para eso.


¿Necesita hacer múltiples reemplazos en la misma línea? Si no, simplemente suelte la gbandera de ambos s///comandos y eso funcionará.
Etan Reisner

Te perdiste el punto de mi pregunta. Quiero decir, ¿necesitas hacer cada reemplazo más de una vez en la misma línea? ¿Hay más de una coincidencia para ab o bc en la entrada original?
Etan Reisner

Lo siento @EtanReisner, he entendido mal, la respuesta es sí. El texto puede tener múltiples reemplazos.
DaniloNC

Respuestas:


342

Tal vez algo como esto:

sed 's/ab/~~/g; s/bc/ab/g; s/~~/bc/g'

Reemplace ~con un carácter que sepa que no estará en la cadena.


99
GNU sed maneja nuls, por lo que puede usar \x0para ~~.
2014

3
¿Es gnecesario y qué hace?
Lee

12
@Lee ges para global: reemplaza todas las instancias del patrón en cada línea, en lugar de solo la primera (que es el comportamiento predeterminado).
naught101

1
Consulte mi respuesta stackoverflow.com/a/41273117/539149 para ver una variación de la respuesta de ooga que puede reemplazar múltiples combinaciones simultáneamente.
Zack Morris

3
que sabe que no estará en la cadena Para el código de producción, no haga nunca ninguna suposición sobre la entrada. Para las pruebas, bueno, las pruebas nunca prueban la corrección, pero una buena idea para una prueba es: usar el script como entrada.
hagello

33

Siempre uso múltiples declaraciones con "-e"

$ sed -e 's:AND:\n&:g' -e 's:GROUP BY:\n&:g' -e 's:UNION:\n&:g' -e 's:FROM:\n&:g' file > readable.sql

Esto agregará un '\ n' antes de todos los AND, GROUP BY, UNION y FROM, mientras que '&' significa la cadena coincidente y '\ n &' significa que desea reemplazar la cadena coincidente con un '\ n' antes del 'coincidente '


14

Aquí hay una variación en la respuesta de ooga que funciona para múltiples pares de búsqueda y reemplazo sin tener que verificar cómo se pueden reutilizar los valores:

sed -i '
s/\bAB\b/________BC________/g
s/\bBC\b/________CD________/g
s/________//g
' path_to_your_files/*.txt

Aquí hay un ejemplo:

antes de:

some text AB some more text "BC" and more text.

después:

some text BC some more text "CD" and more text.

Tenga en cuenta que \bdenota los límites de palabras, que es lo que impide que ________interfieran con la búsqueda (estoy usando GNU sed 4.2.2 en Ubuntu). Si no está utilizando una búsqueda de límites de palabras, entonces esta técnica puede no funcionar.

También tenga en cuenta que esto da los mismos resultados que eliminar s/________//gy agregar && sed -i 's/________//g' path_to_your_files/*.txtal final del comando, pero no requiere especificar la ruta dos veces.

Una variación general de esto sería usar \x0o _\x0_reemplazar ________si sabe que no aparecen valores nulos en sus archivos, como sugirió jthill .


Estoy de acuerdo con el comentario anterior de hagello sobre no hacer suposiciones de lo que puede contener la entrada. Por lo tanto, personalmente creo que esta es la solución más confiable, aparte de los tubos de tubería uno encima del otro ( sed 's/ab/xy/' | sed 's/cd/ab/' .....)
leetbacoon

12

sedes un editor de stream. Busca y reemplaza con avidez. La única forma de hacer lo que solicitó es usar un patrón de sustitución intermedio y volver a cambiarlo al final.

echo 'abcd' | sed -e 's/ab/xy/;s/cd/ab/;s/xy/cd/'


4

Esto podría funcionar para usted (GNU sed):

sed -r '1{x;s/^/:abbc:bcab/;x};G;s/^/\n/;:a;/\n\n/{P;d};s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/;ta;s/\n(.)/\1\n/;ta' file

Esto utiliza una tabla de búsqueda que se prepara y se mantiene en el espacio de espera (HS) y luego se agrega a cada línea. Un marcador único (en este caso \n) se antepone al comienzo de la línea y se utiliza como un método para avanzar en la búsqueda a lo largo de la línea. Una vez que el marcador llega al final de la línea, el proceso finaliza y se imprime la tabla de búsqueda y los marcadores que se descartan.

NB La tabla de búsqueda se prepara desde el principio y se :elige un segundo marcador único (en este caso ) para no chocar con las cadenas de sustitución.

Con algunos comentarios:

sed -r '
  # initialize hold with :abbc:bcab
  1 {
    x
    s/^/:abbc:bcab/
    x
  }

  G        # append hold to patt (after a \n)

  s/^/\n/  # prepend a \n

  :a

  /\n\n/ {
    P      # print patt up to first \n
    d      # delete patt & start next cycle
  }

  s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/
  ta       # goto a if sub occurred

  s/\n(.)/\1\n/  # move one char past the first \n
  ta       # goto a if sub occurred
'

La tabla funciona así:

   **   **   replacement
:abbc:bcab
 **   **     pattern

3

Puede ser un enfoque más simple para la ocurrencia de un solo patrón que puede probar de la siguiente manera: echo 'abbc' | sed 's / ab / bc /; s / bc / ab / 2'

Mi salida:

 ~# echo 'abbc' | sed 's/ab/bc/;s/bc/ab/2'
 bcab

Para múltiples ocurrencias de patrón:

sed 's/\(ab\)\(bc\)/\2\1/g'

Ejemplo

~# cat try.txt
abbc abbc abbc
bcab abbc bcab
abbc abbc bcab

~# sed 's/\(ab\)\(bc\)/\2\1/g' try.txt
bcab bcab bcab
bcab bcab bcab
bcab bcab bcab

Espero que esto ayude !!


2

Tcl tiene un incorporado para esto

$ tclsh
% string map {ab bc bc ab} abbc
bcab

Esto funciona caminando la secuencia de un carácter a la vez haciendo comparaciones de cadenas comenzando en la posición actual.

En perl:

perl -E '
    sub string_map {
        my ($str, %map) = @_;
        my $i = 0;
        while ($i < length $str) {
          KEYS:
            for my $key (keys %map) {
                if (substr($str, $i, length $key) eq $key) {
                    substr($str, $i, length $key) = $map{$key};
                    $i += length($map{$key}) - 1;
                    last KEYS;
                }
            }
            $i++;
        }
        return $str;
    }
    say string_map("abbc", "ab"=>"bc", "bc"=>"ab");
'
bcab

0

Aquí hay una awkbase de oogassed

echo 'abbc' | awk '{gsub(/ab/,"xy");gsub(/bc/,"ab");gsub(/xy/,"bc")}1'
bcab
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.