Sed - Sostituisce le prime k istanze di una parola nel file


24

Voglio sostituire solo le prime kistanze di una parola.

Come posso fare questo?

Per esempio. Supponiamo che il file foo.txtcontenga occorrenze di 100 istanze della parola "linux".

Devo sostituire solo le prime 50 occorrenze.


1
Puoi fare riferimento a questo: unix.stackexchange.com/questions/21178/…
cuonglm,

Hai bisogno di sed specificamente o sono accettabili altri strumenti? Devi lavorare sulla riga di comando o è accettabile un editor di testo?
evilsoup,

Tutto ciò che funziona sulla riga di comando è accettabile.
narendra-choudhary,

Risposte:


31

La prima sezione di seguito descrive l'uso sedper modificare le prime occorrenze k su una linea. La seconda sezione estende questo approccio per modificare solo le prime occorrenze k in un file, indipendentemente dalla riga su cui appaiono.

Soluzione orientata alla linea

Con sed standard, esiste un comando per sostituire la ricorrenza k-esima di una parola su una riga. Se kè 3, ad esempio:

sed 's/old/new/3'

Oppure, è possibile sostituire tutte le occorrenze con:

sed 's/old/new/g'

Nessuno di questi è quello che vuoi.

GNU sedoffre un'estensione che cambierà la ricorrenza k-esima e tutto il resto. Se k è 3, ad esempio:

sed 's/old/new/g3'

Questi possono essere combinati per fare quello che vuoi. Per modificare le prime 3 occorrenze:

$ echo old old old old old | sed -E 's/\<old\>/\n/g4; s/\<old\>/new/g; s/\n/old/g'
new new new old old

dove \nè utile qui perché possiamo essere sicuri che non si verifichi mai su una linea.

Spiegazione:

Utilizziamo tre sedcomandi di sostituzione:

  • s/\<old\>/\n/g4

    Questa è l'estensione GNU per sostituire la quarta e tutte le occorrenze successive di oldcon \n.

    La funzione regex estesa \<viene utilizzata per abbinare l'inizio di una parola e \>per abbinare la fine di una parola. Questo assicura che solo le parole complete siano abbinate. Regex esteso richiede l' -Eopzione per sed.

  • s/\<old\>/new/g

    Rimangono solo le prime tre occorrenze olde questo le sostituisce tutte con new.

  • s/\n/old/g

    La quarta e tutte le occorrenze rimanenti di oldsono state sostituite con \nnella prima fase. Questo li riporta al loro stato originale.

Soluzione non GNU

Se GNU sed non è disponibile e si desidera modificare le prime 3 occorrenze di olda new, utilizzare tre scomandi:

$ echo old old old old old | sed -E -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
new new new old old

Funziona bene quando si ktratta di un numero ridotto ma si ridimensiona in modo mediocre k.

Dato che alcune sed non GNU non supportano la combinazione di comandi con punti e virgola, ogni comando qui viene introdotto con la propria -eopzione. Potrebbe anche essere necessario verificare che sedsupporti i simboli di confine di parola \<e \>.

Soluzione orientata ai file

Possiamo dire a sed di leggere l'intero file e quindi eseguire le sostituzioni. Ad esempio, per sostituire le prime tre occorrenze olddell'uso di una sed in stile BSD:

sed -E -e 'H;1h;$!d;x' -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'

I comandi sed H;1h;$!d;xleggono l'intero file.

Poiché quanto sopra non utilizza alcuna estensione GNU, dovrebbe funzionare su BSD (OSX) sed. Si noti, pensato, che questo approccio richiede un in sedgrado di gestire le linee lunghe. GNU seddovrebbe andare bene. Coloro che usano una versione non GNU di seddovrebbero testare la sua capacità di gestire le linee lunghe.

Con una GNU sed, possiamo usare ulteriormente il gtrucco descritto sopra, ma con \nsostituito con \x00, per sostituire le prime tre occorrenze:

sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g'

Questo approccio si ridimensiona e kdiventa grande. Ciò presuppone, tuttavia, che \x00non si trova nella stringa originale. Dal momento che è impossibile mettere il personaggio \x00in una stringa bash, questo è di solito un presupposto sicuro.


5
Questo funziona solo per le linee e cambierà le prime 4 occorrenze in ogni linea

1
@mikeserv Ottima idea! Risposta aggiornata
Giovanni 1024,

(1) Citi GNU e non GNU sed e suggerisci tr '\n' '|' < input_file | sed …. Ma, naturalmente, ciò converte l'intero input in una riga e alcune sed non non GNU non possono gestire linee arbitrariamente lunghe. (2) Dici: "... sopra, la stringa tra virgolette '|'deve essere sostituita da qualsiasi carattere, o stringa di caratteri, ..." Ma non puoi usare trper sostituire un carattere con una stringa (di lunghezza> 1). (3) Nel tuo ultimo esempio, dici -e 's/\<old\>/new/' -e 's/\<old\>/w/' | tr '\000' '\n'\>/new. Questo sembra essere un refuso per -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' | tr '\000' '\n'.
G-Man dice "Ripristina Monica" il

@ G-Man Grazie mille! Ho aggiornato la risposta.
Giovanni 1024,

è così brutto
Louis Maddox,

8

Usando Awk

I comandi awk possono essere usati per sostituire le prime N occorrenze della parola con la sostituzione.
I comandi sostituiranno solo se la parola è una corrispondenza completa.

Negli esempi seguenti, sto sostituendo le prime 27occorrenze di oldconnew

Usando il sub

awk '{for(i=1;i<=NF;i++){if(x<27&&$i=="old"){x++;sub("old","new",$i)}}}1' file

Questo comando scorre in ogni campo fino a quando non corrisponde old, controlla che il contatore sia inferiore a 27, incrementa e sostituisce la prima corrispondenza sulla linea. Quindi passa al campo / riga successivo e si ripete.

Sostituzione manuale del campo

awk '{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file

Simile al comando precedente ma poiché ha già un marcatore su quale campo è impostato ($i), cambia semplicemente il valore del campo da olda new.

Esecuzione di un controllo prima

awk '/old/&&x<27{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file

Controllare che la linea contenga una vecchia e che il contatore sia inferiore a 27 SHOULDfornisce un piccolo aumento di velocità in quanto non elaborerà le linee quando queste sono false.

RISULTATI

Per esempio

old bold old old old
old old nold old old
old old old gold old
old gold gold old old
old old old man old old
old old old old dog old
old old old old say old
old old old old blah old

a

new bold new new new
new new nold new new
new new new gold new
new gold gold new new
new new new man new new
new new new new dog new
new new old old say old
old old old old blah old

Il primo (usando sub) fa la cosa sbagliata se la stringa “vecchio” precede la * parola vecchio; ad esempio, “Dare un po 'di oro per il vecchio.” → “Dare qualche GNEW al vecchio.”
G-uomo dice 'Ripristinare Monica'

@G-Man Sì, ho dimenticato il $ipezzo, è stato modificato, grazie :)

7

Supponiamo che tu voglia sostituire solo le prime tre istanze di una stringa ...

seq 11 100 311 | 
sed -e 's/1/\
&/g'              \ #s/match string/\nmatch string/globally 
-e :t             \ #define label t
-e '/\n/{ x'      \ #newlines must match - exchange hold and pattern spaces
-e '/.\{3\}/!{'   \ #if not 3 characters in hold space do
-e     's/$/./'   \ #add a new char to hold space
-e      x         \ #exchange hold/pattern spaces again
-e     's/\n1/2/' \ #replace first occurring '\n1' string w/ '2' string
-e     'b t'      \ #branch back to label t
-e '};x'          \ #end match function; exchange hold/pattern spaces
-e '};s/\n//g'      #end match function; remove all newline characters

nota: probabilmente quanto sopra non funzionerà con commenti incorporati
... o nel mio caso di esempio, di un "1" ...

PRODUZIONE:

22
211
211
311

Lì uso due tecniche notevoli. In primo luogo, ogni occorrenza di 1su una riga viene sostituita da \n1. In questo modo, mentre eseguo le sostituzioni ricorsive successive, posso essere sicuro di non sostituire due volte la ricorrenza se la mia stringa di sostituzione contiene la mia stringa di sostituzione. Per esempio, se sostituisco hecon heyesso continuerà a funzionare.

Lo faccio come:

s/1/\
&/g

In secondo luogo, conto i rimpiazzi aggiungendo un carattere al hvecchio spazio per ogni ricorrenza. Una volta che raggiungo tre non si verificano più. Se lo applichi ai tuoi dati e modifichi il \{3\}totale delle sostituzioni che desideri e gli /\n1/indirizzi in qualunque cosa intendi sostituire, dovresti sostituirle solo quante ne desideri.

Ho fatto tutte le -ecose solo per leggibilità. POSIXly Potrebbe essere scritto così:

nl='
'; sed "s/1/\\$nl&/g;:t${nl}/\n/{x;/.\{3\}/!{${nl}s/$/./;x;s/\n1/2/;bt$nl};x$nl};s/\n//g"

E con GNU sed:

sed 's/1/\n&/g;:t;/\n/{x;/.\{3\}/!{s/$/./;x;s/\n1/2/;bt};x};s/\n//g'

Ricorda anche che sedè orientato alla linea: non legge l'intero file e quindi tenta di ricollegarlo come spesso accade in altri editor. sedè semplice ed efficiente. Detto questo, è spesso conveniente fare qualcosa di simile al seguente:

Ecco una piccola funzione shell che la raggruppa in un comando semplicemente eseguito:

firstn() { sed "s/$2/\
&/g;:t 
    /\n/{x
        /.\{$(($1))"',\}/!{
            s/$/./; x; s/\n'"$2/$3"'/
            b t
        };x
};s/\n//g'; }

Quindi con ciò posso fare:

seq 11 100 311 | firstn 7 1 5

...e prendi...

55
555
255
311

...o...

seq 10 1 25 | firstn 6 '\(.\)\([1-5]\)' '\15\2'

...ottenere...

10
151
152
153
154
155
16
17
18
19
20
251
22
23
24
25

... o, per abbinare il tuo esempio (su un ordine di grandezza inferiore) :

yes linux | head -n 10 | firstn 5 linux 'linux is an os kernel'
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux
linux
linux
linux
linux

4

Una breve alternativa in Perl:

perl -pe 'BEGIN{$n=3} 1 while s/old/new/ && ++$i < $n' your_file

Cambia il valore di `$ n $ a tuo piacimento.

Come funziona:

  • Per ogni riga, si continua a cercare di sostituire newper old( s/old/new/) e ogni volta che può, incrementa la variabile $i( ++$i).
  • Continua a lavorare sulla linea ( 1 while ...) fintanto che ha effettuato meno di $nsostituzioni in totale e può effettuare almeno una sostituzione su quella linea.

4

Usa un loop di shell e ex!

{ for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt

Sì, è un po 'sciocco.

;)

Nota: ciò potrebbe non riuscire se nel file sono presenti meno di 50 istanze old. (Non l'ho provato.) In tal caso, il file rimarrebbe non modificato.


Meglio ancora, usa Vim.

vim file.txt
qqgg/old<CR>:s/old/new/<CR>q49@q
:x

Spiegazione:

q                                # Start recording macro
 q                               # Into register q
  gg                             # Go to start of file
    /old<CR>                     # Go to first instance of 'old'
            :s/old/new/<CR>      # Change it to 'new'
                           q     # Stop recording
                            49@q # Replay macro 49 times

:x  # Save and exit

: s // new <CR> dovrebbe funzionare anche perché una regex vuota riutilizza l'ultima ricerca utilizzata
esempio

3

Una soluzione semplice, ma non molto veloce, è quella di eseguire il loop dei comandi descritti in /programming/148451/how-to-use-sed-to-replace-only-the-first-occurrence-in-a -file

for i in $(seq 50) ; do sed -i -e "0,/oldword/s//newword/"  file.txt  ; done

Questo particolare comando sed probabilmente funziona solo per GNU sed e se newword non fa parte di oldword . Per i non GNU sed vedere qui come sostituire solo il primo modello in un file.


+1 per identificare che la sostituzione di "vecchio" con "grassetto" può causare problemi.
G-Man dice "Ripristina Monica" il

2

Con GNU awkè possibile impostare il separatore di record RSsulla parola da sostituire delimitata da limiti di parola. Quindi si tratta di impostare il separatore di record sull'output sulla parola sostitutiva per i primi krecord mantenendo il separatore di record originale per il resto

awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, NR <= limit? replacement: RT}' file

O

awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, limit--? replacement: RT}' file
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.