Confuso dall'output sed quando si utilizza N. Qualcuno può spiegare questi risultati?


8

Sto imparando sed. Tutto sembrava andare bene fino a quando non ho incontrato la N (multi-linea successiva). Ho creato questo file (guide.txt) per scopi di pratica / comprensione / contesto. Ecco il contenuto di detto file ...

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

Quindi il mio obiettivo è sostituire TUTTE le istanze di "Amministratore di rete" con "Utente di sistema". Poiché la prima istanza di "Amministratore di rete" è separata da una nuova riga (\ n) ho bisogno dell'operatore successivo multilinea (N) per aggiungere la riga che inizia con "Amministratore" con la riga precedente che termina con "Rete \ n" . Nessun problema. Ma voglio anche catturare tutte le altre istanze a linea singola "Network Administrator".

Dalla mia ricerca, ho imparato che avrò bisogno di due comandi di sostituzione; uno per la stringa separata newline e uno per gli altri. Inoltre, sta succedendo un po 'di jive a causa dell'ultima riga contenente la partita di sostituzione e la successiva multi-linea. Quindi ho creato questo ...

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

Questo restituisce questi risultati ...

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

Ho pensato che la sostituzione a riga singola avrebbe catturato tutte le istanze "normali" di "Amministratore di rete" e l'avrebbe scambiato con "Utente di sistema", mentre l'istruzione multilinea avrebbe fatto la sua magia sull'istanza separata da nuova riga, ma mentre tu posso vederlo restituito, ciò che considero, risultati inaspettati.

Dopo aver giocherellato, sono atterrato su questo ...

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

E voilà, ottengo l'output desiderato di ...

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

Perché questo funziona e l'originale sed script no? Voglio davvero capirlo.

Grazie in anticipo per qualsiasi aiuto.


Buon per te per imparare Sed! Ho usato Sed per risolvere una domanda molto simile precedenza su questo sito; potrebbe essere di interesse.
Wildcard l'

Risposte:


6

Mentre stai imparando sed, mi prenderò il tempo di aggiungere alla risposta di @ John1024:

1) Si noti che si sta utilizzando \nla stringa di sostituzione. Funziona in GNU sed, ma non fa parte di POSIX, quindi inserirà una barra rovesciata e una nin molte altre seds (usare \nnel modello è portatile, btw).

Invece di questo suggerisco di fare s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g: [[:space:]]abbinerà newline o spazi bianchi, quindi non sono necessari due scomandi, ma combinali in uno solo. Circondandolo con \(...\)è possibile fare riferimento ad esso nella sostituzione: \1verrà sostituito da qualsiasi cosa corrisponda nella prima coppia di \(\).

2) Per abbinare correttamente i motivi su due linee, dovresti conoscere il N;P;Dmotivo:

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

Il N è sempre di aggiunta alla riga successiva (tranne che per l'ultima riga, è per questo che la "rivolta" con $!(= se non ultima riga, si dovrebbe sempre prendere in considerazione per precedere Ncon $!per evitare accidentalmente finire lo script) Poi, dopo la sostituzione dei. PSolo stampe la prima riga nello spazio del pattern Delimina questa riga e inizia il ciclo successivo con i resti dello spazio del pattern (senza leggere la riga successiva), probabilmente è quello che intendevi inizialmente.

Ricorda questo schema, spesso ne avrai bisogno.

3) Un altro modello utile per l'editing multilinea, specialmente quando sono coinvolte più di due righe: Tenere la raccolta dello spazio, come ho suggerito a John:

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

Lo ripeto per spiegarlo: Haccoda ogni riga allo spazio di attesa. Poiché ciò comporterebbe una nuova riga aggiuntiva prima della prima riga, è necessario spostare la prima riga anziché accodarla 1h. Il seguente$!d significa "per tutte le righe tranne l'ultima, eliminare lo spazio del motivo e ricominciare". Pertanto, il resto dello script viene eseguito solo per l'ultima riga. A questo punto, l'intero file viene raccolto nello spazio di attesa (quindi non utilizzarlo per file molto grandi!) E lo gsposta nello spazio modello, in modo da poter eseguire tutte le sostituzioni contemporaneamente come puoi con l' -zopzione di GNU sed.

Questo è un altro modello utile che suggerisco di tenere a mente.


Wow! Grande spiegazione! Questo, unito alla risposta di John, mi ha davvero dato una visione migliore di questo problema e sed in generale. Sembra che ho molto altro da imparare. Vorrei poter controllare entrambe le tue soluzioni come risposte. Grazie mille per entrambi i tuoi sforzi. Sono molto apprezzati
dlowrie290,

7

Innanzitutto, nota che la tua soluzione non funziona davvero. Considera questo file di test:

$ cat test1
Network
Administrator Network
Administrator

E quindi esegui il comando:

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

Il problema è che il codice non sostituisce l'ultimo Network\nAdministrator.

Questa soluzione funziona:

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

Possiamo anche applicare questo al vostro 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 chiave è continuare a leggere in righe fino a quando non ne trovi uno che non terminaNetwork . Quando ciò viene realizzato, è possibile effettuare le sostituzioni.

Nota di compatibilità: tutto quanto sopra utilizzato \nnel testo di sostituzione. Ciò richiede GNU sed. Non funzionerà su BSD / OSX sed.

[Punta del cappello a Philippos .]

Versione multilinea

Se aiuta a chiarire, ecco lo stesso comando suddiviso su più righe:

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

Come funziona

  1. :a

    Questo crea un'etichetta a.

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

    Se questa riga termina con Network, quindi, se questa non è l'ultima riga ( $!), leggi e aggiungi la riga successiva ( N) e ricomincia da capo a label a( ba).

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

    Effettua la sostituzione con la nuova riga intermedia.

  4. s/Network Administrator/System User/g

    Effettua la sostituzione con il bianco intermedio.

Soluzione più semplice (solo GNU)

Con GNU sed ( non BSD / OSX), abbiamo solo bisogno di un comando sostitutivo:

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

E sul guide.txtfile:

$ 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

In questo caso, -zdice a sed di leggere fino al primo carattere NUL. Poiché i file di testo non hanno mai un carattere nullo, questo ha l'effetto di leggere l'intero file in una volta. Possiamo quindi effettuare la sostituzione senza preoccuparci di perdere una linea.

Questo metodo non è utile se il file è enorme (in genere significa gigabyte). Se è così grande, la lettura di tutto in una volta può mettere a dura prova la RAM di sistema.

Soluzione che funziona sia su GNU che su BSD sed

Come suggerito da Phillipos , la seguente è una soluzione portatile:

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

1
Ottima informazione, John! Grazie per far luce su questo e la tua soluzione alternativa è molto bella. Detto questo, non capisco ancora perché la mia soluzione non sia una soluzione. Sembra funzionare, ma con il tuo file test.txt non funziona. Perché la mia soluzione sembra funzionare, ma in realtà no? Grazie mille per l'aiuto.
dlowrie290,

1
@ dlowrie290 La tua soluzione si legge in righe in coppie. Se Network Administratorviene diviso tra la prima e la seconda riga di quella coppia, la soluzione effettua correttamente la sostituzione. Quindi stampa quelle due righe e legge nella coppia successiva. Se, tuttavia, la seconda riga della prima coppia termina con Networke la prima riga della seconda coppia inizia con Administrator, il codice la manca. Il mio codice lo evita leggendo le righe fino a quando non ne trova uno che non termina Network.
Giovanni 1024

2
Nota che la tua prima soluzione multilinea dipende anche dalle estensioni GNU sed: La \nsostituzione non è definita nello standard. sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1User/g'è un modo portatile per farlo.
Filippos,

@Philippos Punti eccellenti. Risposta aggiornata per includere la soluzione portatile.
Giovanni 1024

1
Grazie per il chiarimento, John! Ancora una volta, le cose fantastiche e il tuo tempo / sforzi sono molto apprezzati!
dlowrie290,
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.