Confundido por la producción de sed cuando se usa N. ¿Alguien puede explicar estos resultados?

8

Estoy aprendiendo sed. Todo parecía ir bien hasta que me encontré con la N (varias líneas a continuación). Creé este archivo (guide.txt) para fines de práctica / comprensión / contexto. Aquí está el contenido de dicho archivo ...

This guide is meant to walk you through a day as a Network
Administrator. By the end, hopefully you will be better
equipped to perform your duties as a Network Administrator
and maybe even enjoy being a Network Administrator that much more.
Network Administrator
Network Administrator
I'm a Network Administrator

Por lo tanto, mi objetivo es sustituir TODAS las instancias de "Administrador de red" con "Usuario del sistema". Debido a que la primera instancia de "Administrador de red" está separada por una nueva línea (\ n), necesito el siguiente operador de varias líneas (N) para agregar la línea que comienza con "Administrador" con la línea anterior que termina con "Red \ n" . No hay problema. Pero también quiero capturar todas las demás instancias de una sola línea de "Administrador de red".

De mi investigación, aprendí que necesitaré dos comandos de sustitución; uno para la cadena separada de nueva línea y uno para los demás. Además, hay algunos cambios debido a la última línea que contiene la coincidencia de sustitución y la línea múltiple siguiente. Así que creo esto ...

$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> ' guide.txt

Esto devuelve estos resultados ...

This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a Network Administrator that much more.
System User
Network Administrator
I'm a System User

Pensé que la sustitución de una sola línea capturaría todas las instancias "normales" de "Administrador de red" y lo cambiaría por "Usuario del sistema", mientras que la instrucción de varias líneas funcionaría su magia en la instancia separada de nueva línea, pero como usted Puedo verlo devuelto, lo que considero, resultados inesperados.

Después de un poco de violín, aterricé en esto ...

$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> s/Network Administrator/System User/
> ' guide.txt

Y voilà, obtengo la salida deseada de ...

This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

¿Por qué funciona esto y el script sed original no? Realmente quiero entender esto.

Gracias de antemano por cualquier ayuda.

dlowrie290
fuente
Bien por aprender Sed! Utilicé Sed para resolver una pregunta muy similar anteriormente en este sitio; puede ser de interés
Comodín el
Y otros dos ejemplos difíciles de Sed: unix.stackexchange.com/a/277375/135943 , unix.stackexchange.com/a/257913/135943
Comodín el

Respuestas:

6

Mientras aprende sed, me tomaré el tiempo para agregar a la respuesta de @ John1024:

1) Tenga en cuenta que está utilizando \nla cadena de reemplazo. Esto funciona en GNU sed, pero no es parte de POSIX, por lo que insertará una barra diagonal inversa y una nen muchos otros seds (el uso \nen el patrón es portátil, por cierto).

En lugar de esto, sugiero que hagas s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g: [[:space:]]coincidirá con la nueva línea o el espacio en blanco, por lo que no necesitas dos scomandos, sino combinarlos en uno. Al rodearlo con \(...\)puede referirse a él en el reemplazo: El \1será reemplazado por lo que coincida en el primer par de \(\).

2) Para hacer coincidir correctamente los patrones en dos líneas, debe conocer el N;P;Dpatrón:

 sed '$!N;s/Network\([[:space:]]\)Administrator/System\1User/g;P;D'

El Nsiempre es añadir una línea al lado (a excepción de la última línea, es por eso que es "dirigido" con $!(= si no es la última línea, siempre se debe tener en cuenta a preceder Na $!evitar terminar el guión) accidentalmente Luego, después de la sustitución de los. PImprime sólo la primera línea en el espacio del patrón y luego Delimina esta línea y comienza el siguiente ciclo con los restos del espacio del patrón (sin leer la siguiente línea). Esto es probablemente lo que originalmente pretendía.

Recuerde este patrón, a menudo lo necesitará.

3) Otro patrón útil para la edición multilínea, especialmente cuando hay más de dos líneas involucradas: Mantenga la recopilación de espacio, como le sugerí a John:

sed 'H;1h;$!d;g;s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g'

Lo repito para explicarlo: Hagrega cada línea al espacio de espera. Como esto daría como resultado una nueva línea adicional antes de la primera línea, la primera línea debe moverse en lugar de agregarse 1h. Lo siguiente $!dsignifica "para todas las líneas excepto la última, elimine el espacio del patrón y comience de nuevo". Por lo tanto, el resto del script solo se ejecuta para la última línea. En este punto, todo el archivo se recopila en el espacio de retención (¡así que no lo use para archivos muy grandes!) Y lo gmueve al espacio del patrón, por lo que puede hacer todos los reemplazos a la vez como puede con la -zopción de GNU sed.

Este es otro patrón útil que sugiero tener en cuenta.

Philippos
fuente
¡Guauu! ¡Gran explicación! Esto, junto con la respuesta de John, realmente me dio una mejor idea de este problema y me sentí en general. Parece que tengo mucho más que aprender. Desearía poder comprobar sus dos soluciones como respuestas. Muchas gracias por sus dos esfuerzos. Son muy apreciados
dlowrie290
7

Primero, tenga en cuenta que su solución realmente no funciona. Considere este archivo de prueba:

$ cat test1
Network
Administrator Network
Administrator

Y luego ejecuta el comando:

$ sed '
 s/Network Administrator/System User/
 N
 s/Network\nAdministrator/System\nUser/
 s/Network Administrator/System User/
 ' test1
System
User Network
Administrator

El problema es que el código no sustituye al último Network\nAdministrator.

Esta solución funciona:

$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' test1
System
User System
User

También podemos aplicar esto a su guide.txt:

$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' guide.txt 
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

La clave es seguir leyendo en líneas hasta que encuentre una que no termine Network. Cuando eso se logra, las sustituciones se pueden hacer.

Nota de compatibilidad: Todo el uso anterior \nen el texto de reemplazo. Esto requiere GNU sed. No funcionará en BSD / OSX sed.

[Punta de sombrero para Philippos .]

Versión multilínea

Si ayuda a aclarar, aquí está el mismo comando dividido en varias líneas:

$ sed ':a
    /Network$/{
       $!{
           N
           ba
       }
    }
    s/Network\nAdministrator/System\nUser/g
    s/Network Administrator/System User/g
    ' filename

Cómo funciona

  1. :a

    Esto crea una etiqueta a.

  2. /Network$/{ $!{N;ba} }

    Si esta línea termina con Network, entonces, si esta no es la última línea ( $!), lea y agregue la siguiente línea ( N) y vuelva a la etiqueta a( ba).

  3. s/Network\nAdministrator/System\nUser/g

    Realice la sustitución con la nueva línea intermedia.

  4. s/Network Administrator/System User/g

    Realice la sustitución con el espacio intermedio en blanco.

Solución más simple (solo GNU)

Con GNU sed ( no BSD / OSX), solo necesitamos un comando sustituto:

$ sed -zE 's/Network([[:space:]]+)Administrator/System\1User/g' test1
System
User System
User

Y en el guide.txtarchivo:

$ sed -zE 's/Network([[:space:]]+)Administrator/System\1User/g' guide.txt 
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User

En este caso, -zle dice a sed que lea hasta el primer carácter NUL. Como los archivos de texto nunca tienen un carácter nulo, esto tiene el efecto de leer todo el archivo a la vez. Luego podemos hacer la sustitución sin preocuparnos por perder una línea.

Este método no es bueno si el archivo es enorme (generalmente significa gigabytes). Si es tan grande, entonces leerlo todo a la vez podría dañar la RAM del sistema.

Solución que funciona tanto en sed GNU como BSD

Como sugirió Phillipos , la siguiente es una solución portátil:

sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g'
John1024
fuente
1
Excelente información, John! Gracias por arrojar algo de luz sobre esto y su solución alternativa es muy buena. Dicho esto, todavía no entiendo por qué mi solución no es una solución. Parece funcionar, pero con su archivo test.txt no lo hace. ¿Por qué mi solución parece funcionar, pero en realidad no? Muchísimas gracias por la ayuda.
dlowrie290
1
@ dlowrie290 Su solución se lee en líneas en pares. Si Network Administratorse divide entre la primera y la segunda línea de ese par, su solución realiza la sustitución con éxito. Luego imprime esas dos líneas y lee en el siguiente par. Sin embargo, si la segunda línea del primer par termina con Networky la primera línea del segundo par comienza con Administrator, el código lo pierde. Mi código lo evita leyendo en líneas hasta que encuentra uno que no termina con Network.
John1024
2
Tenga en cuenta que su primera solución multilínea también depende de las extensiones de GNU para sed: El \nreemplazo no está definido en el estándar. sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1User/g'Es una forma portátil de hacerlo.
Philippos
@Philippos Excelentes puntos. Respuesta actualizada para incluir la solución portátil.
John1024
1
Gracias por la aclaración, John! Una vez más, ¡se aprecian mucho tus cosas y tu tiempo / esfuerzo!
dlowrie290