Cambie limpiamente todas las ocurrencias de dos cadenas usando sed


13

Supongamos que tengo un archivo que contiene múltiples ocurrencias de StringA y StringB. Quiero reemplazar todas las apariciones de StringA con StringB, y (simultáneamente) todas las apariciones de StringB con StringA.

Ahora mismo estoy haciendo algo como

cat file.txt | sed 's/StringB/StringC/g' | sed 's/StringA/StringB/g' | sed 's/StringC/StringA/g'

El problema con este enfoque es que supone que StringC no ocurre en el archivo. Si bien esto no es un problema en la práctica, esta solución todavía se siente sucia, es decir, se siente como una oportunidad para aprender más magia de Unix. :)

Respuestas:


11

Si StringBy StringAno pueden aparecer en la misma línea de entrada, entonces usted puede decir sed para realizar la sustitución de una forma, y sólo tratar a la inversa, si no hay ocurrencias de la primera buscaron cadena.

<file.txt sed -e 's/StringA/StringB/g' -e t -e 's/StringB/StringA/g'

En el caso general, no creo que haya un método fácil en sed. Por cierto, nota que la especificación es ambigua si StringAy StringBpueden solaparse. Aquí hay una solución Perl, que reemplaza la aparición más a la izquierda de cualquiera de las cadenas, y se repite.

<file.txt perl -pe 'BEGIN {%r = ("StringA" => "StringB", "StringB" => "StringA")}
                    s/(StringA|StringB)/$r{$1}/ge'

Si desea seguir con las herramientas POSIX, awk es el camino a seguir. Awk no tiene una primitiva para reemplazos parametrizados generales, por lo que debe rodar la suya.

<file.txt awk '{
    while (match($0, /StringA|StringB/)) {
        printf "%s", substr($0, 1, RSTART-1);
        $0 = substr($0, RSTART);
        printf "%s", /^StringA/ ? "StringB" : "StringA";
        $0 = substr($0, 1+RLENGTH)
    }
    print
}'

Cuando ejecuto el primer comando, sed me dice sed: can't read s/StringB/StringA/g: No such file or directory. Parece -e t PATTERNque no se entiende bien.
Gyscos

1
@Gyscos Hubo una falta -eantes del segundo scomando. He arreglado mi respuesta.
Gilles 'SO- deja de ser malvado'

8

En este momento, estoy haciendo algo como
...............
El problema con este enfoque es que supone que StringC no aparece en el archivo.

Creo que su enfoque está bien, solo debe usar algo más en lugar de una cadena, algo que no puede ocurrir en una línea (en el espacio del patrón). El mejor candidato es el \newline.
Normalmente, ninguna línea de entrada en el espacio del patrón contendrá ese carácter, por lo que, para intercambiar todas las apariciones THISy THATen un archivo, puede ejecutar:

sed 's/THIS/\
/g
s/THAT/THIS/g
s/\n/THAT/g' infile

o, si su sed también es compatible \ncon el RHS:

sed 's/THIS/\n/g;s/THAT/THIS/g;s/\n/THAT/g' infile

1
Esto es hermoso. Lloré un poco Otra forma de hacer las nuevas líneas de RHS son las variables de shell: si el sedsoporte admite ciertos escapes o no se vuelve mucho menos importante si prepara algunas macros de antemano. Me gusta set /THIS /THAT "$(printf \\n/)"; sed "s/$2/\\$4g;s/$3$2/g;s/\\n$3/g", un poco estúpido aquí, sin duda, pero tiene mucho más sentido que en otras ocasiones, especialmente para clases de char y similares.
mikeserv

Bueno, ¿qué tal eso, hombre? Incluso hay una respuesta al respecto. ¿Estaba allí cuando hice el comentario? Acabo de ver que aparece en la lista recientemente editada (tal vez) y la línea superior de la respuesta superior estaba un poco apagada (si solo te importa Linux no incrustado, supongo) . Prefiero la sugerencia de Gilles allí: a menos que estés haciendo un largo recorrido sed, la bifurcación constante con la bifurcación ees una pesadilla kinduva. En una nota diferente: he estado jugando pastedurante todo un día. Hice un analizador de opciones, columnalgo así. Simplemente genera guiones para las cadenas de entrada y las cosas juntas.
mikeserv

3

Creo que es perfectamente válido usar una cadena "nonce" para intercambiar dos palabras. Si desea una solución más general, puede hacer algo como:

sed 's/_/__/g; s/you/x_x/g; s/me/you/g; s/x_x/me/g; s/__/_/g' <<<"say you say me"

Que rinde

say me say you

Tenga en cuenta que necesita las dos sustituciones adicionales aquí para evitar el reemplazo x_xsi tiene cadenas "x_x". Pero incluso eso todavía parece más simple que la awksolución para mí.


Eso parece ser lo que el Asker dijo que ya estaban haciendo.
roaima

1
Sí, lo pasé por alto al principio (ver el historial de edición) pero mi solución dada es diferente ya que funciona incluso cuando la cadena de reemplazo (aquí "x_x") aparece en la cadena original, por lo tanto, es más general.
David Ongaro,

Inteligente, pero hay una trampa. Si StringA o StringB contiene _, uno necesita ajustar el _sí mismo (elegir otro carácter) o la cadena problemática (ejecutar s/_/__/gde antemano, parece mejor). Su solución, tal como está, no se puede aplicar a ciegas para intercambiar cadenas arbitrarias.
Kamil Maciorowski

@KamilMaciorowski ¿No entiendo lo que quieres decir? De hecho, aplico una de s/_/__/gantemano. Tal vez solo muestre un caso de prueba que falla.
David Ongaro

@KamilMaciorowski ah Creo que lo entiendo ahora. Te refieres a si las cadenas de reemplazo en sí contienen un _, por ejemplo, reemplazando y_oucon me. Sí, es verdad, uno tiene que ser consciente de eso y ponerlo y__ouen la expresión. Un script que toma el reemplazo como parámetros de entrada también debe tenerlo en cuenta.
David Ongaro
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.