Sostituisci una stringa contenente caratteri di nuova riga


10

Con la bashshell, in un file con righe come le seguenti

first "line"
<second>line and so on

Vorrei sostituire una o più occorrenze di "line"\n<second>con other characterse ottenere ogni volta:

first other characters line and so on

Quindi devo sostituire una stringa sia con caratteri speciali come "e <sia con un carattere di nuova riga.

Dopo aver cercato tra le altre risposte, ho scoperto che sedpuò accettare newline nella parte destra del comando (quindi, la other charactersstringa), ma non a sinistra.

C'è un modo (più semplice di così ) per ottenere questo risultato con sedo grep?


stai lavorando con un mac? la \ndichiarazione ewline che fai è il motivo per cui chiedo. le persone raramente chiedono se possono fare ciò s//\n/che è possibile con GNU sed, anche se la maggior parte degli altri sedrifiuterà quella fuga sul lato destro. tuttavia, la \nfuga funzionerà a sinistra in qualsiasi POSIX sede puoi tradurli in modo portabile come y/c/\n/se avesse lo stesso effetto s/c/\n/ge quindi non è sempre così utile.
Mikeserv,

Risposte:


3

Tre diversi sedcomandi:

sed '$!N;s/"[^"]*"\n<[^>]*>/other characters /;P;D'

sed -e :n -e '$!N;s/"[^"]*"\n<[^>]*>/other characters /;tn'

sed -e :n -e '$!N;/"$/{$!bn' -e '};s/"[^"]*"\n<[^>]*>/other characters /g'

Tutti e tre si basano sul s///comando ubstitution di base :

s/"[^"]*"\n<[^>]*>/other characters /

Tentano anche tutti di occuparsi della gestione dell'ultima riga, poiché sedtendono a differire in termini di output nei casi limite. Questo è il significato di $!quale indirizzo corrisponde a ogni riga che !non è l' $ultima.

Usano anche tutti il Ncomando ext per aggiungere la riga di input successiva allo spazio del pattern seguendo un \ncarattere di ewline. Chiunque abbia lavorato sedper un po 'di tempo avrà imparato a fare affidamento sul \npersonaggio della ewline - perché l'unico modo per ottenerlo è metterlo esplicitamente lì.

Tutti e tre fanno un tentativo per leggere il minor input possibile prima di agire - sedagisce appena possibile e non è necessario leggere in un intero file di input prima di farlo.

Sebbene facciano tutti N, tutti e tre differiscono nei loro metodi di ricorsione.

Primo comando

Il primo comando utilizza un N;P;Dciclo molto semplice . Questi tre comandi sono integrati in qualsiasi POSIX compatibile sede si completano perfettamente.

  • N- come già accennato, accoda la Nlinea di input ext allo spazio-pattern seguendo un \ndelimitatore ewline inserito .
  • P- come p; esso Prints pattern-space - ma solo fino a che si verifica il primo \ncarattere ewline. E così, dato il seguente input / comando:

    • printf %s\\n one two | sed '$!N;P;d'
  • sed Pne strappa solo uno . Tuttavia, con ...

  • D- come d; Delimina lo spazio-modello e inizia un altro ciclo di linea. Diversamente d , Delimina solo fino alla prima \newline che si verifica nello spazio modello. Se c'è più spazio nel pattern dopo il \ncarattere di ewline, sedinizia il ciclo di riga successivo con ciò che rimane. Se l' dnell'esempio precedente sono stati sostituiti con D, per esempio, sedsarebbe PRint sia uno e due .

Questo comando ricorre solo per le righe che non corrispondono s///all'istruzione ubstitution. Poiché s///ubstitution rimuove la \newline aggiunta con N, non c'è mai nulla che rimanga quando sed Delimina lo spazio-modello.

Si potrebbero fare dei test per applicare il Pe / o Dselettivamente, ma ci sono altri comandi che si adattano meglio a quella strategia. Poiché il ricorsione è implementata per gestire linee consecutive che hanno solo una parte della regola di sostituzione, sequenze consecutive di linee corrispondenza a entrambe le estremità della s///ubstitution non funzionano bene .:

Dato questo input:

first "line"
<second>"line"
<second>"line"
<second>line and so on

... stampa ...

first other characters "line"
<second>other characters line and so on

Tuttavia, gestisce

first "line"
second "line"
<second>line

...va bene.

Secondo comando

Questo comando è molto simile al terzo. Entrambi impiegano un'etichetta :branch / test (come è dimostrato anche nella risposta di Joeseph R. qui ) e lo ricontattano a determinate condizioni.

  • -e :n -e- gli sedscript portatili delimiteranno una :definizione di etichetta con una \newline o una nuova -eistruzione xecution inline .
    • :n- definisce un'etichetta denominata n. Questo può essere restituito in qualsiasi momento con bno tn.
  • tn- il tcomando est ritorna a un'etichetta specificata (o, se non ne viene fornita nessuna, chiude lo script per il ciclo di riga corrente) se si verifica un s///ubstitution dal momento che l'etichetta è stata definita o dall'ultima volta in cui l' est è stato chiamato tcon successo.

In questo comando si verifica la ricorsione per le righe corrispondenti. Se sedsostituisce correttamente il motivo con altri caratteri , sedtorna :nall'etichetta e riprova. Se un s///ubstitution non viene eseguito, stampa automaticamente lo sedspazio-motivo e inizia il ciclo di linea successivo.

Questo tende a gestire meglio le sequenze consecutive. Dove l'ultimo fallito, questo stampa:

first other characters other characters other characters line and so on

Terzo comando

Come accennato, la logica qui è molto simile alla precedente, ma il test è più esplicito.

  • /"$/bn- questo è un sedtest. Poiché il bcomando ranch è una funzione di questo indirizzo, sedtornerà a branch :ndopo che \nè stata aggiunta una ewline e lo spazio del modello termina ancora con una "virgoletta doppia.

C'è poco da fare tra Ne il bpiù possibile - in questo modo è sedpossibile raccogliere molto rapidamente esattamente tutti gli input necessari per garantire che la seguente riga non corrisponda alla regola. L' s///ubstitution differisce qui in quanto impiega la gbandiera lobal - e quindi farà tutte le sostituzioni necessarie in una sola volta. Dato un input identico, questo comando restituisce identico all'ultimo.


DATACi scusiamo per la banale domanda, ma qual è il significato e come si riceve l'inserimento di testo?
BowPark,

@BowPark - In questo esempio <<\DATA\ntext input\nDATA\nè inserito , ma questo è solo il testo passato seddalla shell in un documento qui . Funzionerebbe bene come sed 'script' filenameo process that writes to stdout | sed 'script'. Questo aiuta?
Mikeserv,

Sì, grazie! Perché senza Dogni linea modificata è doppia? (L'hai usato come è necessario; forse non lo so sedmolto bene)
BowPark

1
@BowPark - si ottiene il doppio quando si omette il Dperché Daltrimenti si Delimina dall'output ciò che ora si vede raddoppiato. Ho appena apportato una modifica e presto potrò ampliarla.
Mikeserv,

1
@BowPark - ok, l'ho aggiornato e fornito opzioni. Potrebbe essere un po 'più facile da leggere / capire ora. Ho anche affrontato esplicitamente la Dcosa.
Mikeserv,

7

Bene, posso pensare a un paio di modi semplici ma nessuno dei due coinvolge grep(che comunque non sostituisce) o sed.

  1. Perl

    Per sostituire ogni occorrenza di "line"\n<second>con other characters, utilizzare:

    $ perl -00pe 's/"line"\n<second>/other characters /g' file
    first other characters line and so on
    

    Oppure, per trattare più ricorrenze consecutive di "line"\n<second>una come una e sostituirle tutte con una sola other characters, utilizzare:

    perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    

    Esempio:

    $ cat file
    first "line"
    <second>"line"
    <second>"line"
    <second>line and so on
    $ perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    first other characters line and so on
    

    Questo -00fa sì che Perl legga il file in "modalità paragrafo", il che significa che le "linee" sono definite \n\ninvece che \n, essenzialmente, ogni paragrafo è trattato come una linea. La sostituzione corrisponde quindi a una nuova riga.

  2. awk

    $  awk -v RS="\n\n" -v ORS="" '{
          sub(/"line"\n<second>/,"other characters ", $0)
          print;
        }' file 
    first other characters line and so on
    

    La stessa idea di base, impostiamo il separatore di record ( RS) su \n\nper slurpare l'intero file, quindi il separatore di record di output su nulla (altrimenti viene stampata una nuova riga aggiuntiva) e quindi utilizziamo la sub()funzione per effettuare la sostituzione.


2
@mikeserv? Quale? Il secondo dovrebbe, l'OP ha affermato di voler "sostituire una o più occorrenze di", quindi mangiare il paragrafo potrebbe essere quello che si aspettano.
terdon

ottimo punto. Immagino di essermi concentrato di più su e ottenere ogni volta , ma immagino non sia chiaro se dovrebbe essere una sostituzione per occorrenza o una sostituzione per sequenza di occorrenze ... @BowPark?
Mikeserv,

È necessaria una sostituzione per occorrenza.
BowPark,

@BowPark OK, allora il primo approccio perl o il awk dovrebbero funzionare entrambi. Non ti danno l'output desiderato?
terdon

Funziona, grazie, ma la terza riga awkdovrebbe essere print;}' file. Devo evitare Perl e usare preferibilmente sed, comunque hai suggerito buone alternative.
BowPark,

6

leggi l'intero file ed esegui una sostituzione globale:

sed -n 'H; ${x; s/"line"\n<second>/other characters /g; p}' <<END
first "line"
<second> line followed by "line"
<second> and last
END
first other characters  line followed by other characters  and last

Sì. Funziona, ma cosa succede se ho più ricorrenze?
BowPark,

Eh, giusto. Risolto
glenn jackman,

1
mi dispiace di nuovo nitpick, ma ${cmds}è specifico per GNU - la maggior parte degli altri sedrichiederà una \newline o -eun'interruzione tra pe }. Puoi evitare del tutto le parentesi - e portabile - e persino evitare di inserire un \ncarattere di ewline extra sulla prima riga come:sed 'H;1h;$!d;x;s/"line"\n<second>/other characters /g'
mikeserv

L'ho provato e non sembra portatile. Stampa una nuova riga in più all'inizio dell'output, ma il risultato è corretto su GNU.
BowPark,

Per rimuovere la nuova riga principale: sed -n '1{h;n};H; ${x; s/"line"\n<second>/other characters /g; p}'- tuttavia ciò sta diventando non mantenibile.
Glenn Jackman,

3

Ecco una variante della risposta di Glenn che funzionerà se hai più occorrenze consecutive (funziona sedsolo con GNU ):

sed ':x /"line"/N;s/"line"\n<second>/other characters/;/"line"/bx' your_file

L' :xè solo un'etichetta per ramificazione. Fondamentalmente, ciò che fa è che controlla la riga dopo la sostituzione e, se corrisponde ancora "line", si ramifica :xall'etichetta (è quello che bxfa) e aggiunge un'altra riga al buffer e inizia l'elaborazione.


@mikeserv Per favore, sii specifico su cosa intendi. Ha funzionato per me.
Joseph R.,

@mikeserv Mi dispiace, davvero non so di cosa stai parlando. Ho copiato la riga di codice sopra nel mio terminale e ha funzionato correttamente.
Joseph R.,

1
retratto - apparentemente funziona in GNU sedche porta la sua gestione di etichette non POSIX abbastanza lontano da accettare uno spazio come delimitatore per la dichiarazione di etichette. Dovresti notare, tuttavia, che qualsiasi altro sedfallirà lì - e fallirà N. GNU sedinfrange le linee guida POSIX per stampare pattern-space prima di uscire da Na sull'ultima riga, ma POSIX chiarisce che se un Ncomando viene letto sull'ultima riga non dovrebbe essere stampato nulla .
Mikeserv,

Se modifichi il post per specificare GNU, annullerò il mio voto ed eliminerò questi commenti. Inoltre, potrebbe valere la pena di conoscere il vcomando di GNU che si interrompe a vicenda sedma è una no-op nelle versioni GNU 4 e successive.
mikeserv,

1
in quel caso mi offrirà un altro - questo può essere fatto in modo portabile come: sed -e :x -e '/"line"/{$!N' -e '};s/"line"\n<second>/other characters/;/"line"/bx'.
Mikeserv,
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.