Voglio sostituire solo le prime k
istanze di una parola.
Come posso fare questo?
Per esempio. Supponiamo che il file foo.txt
contenga occorrenze di 100 istanze della parola "linux".
Devo sostituire solo le prime 50 occorrenze.
Voglio sostituire solo le prime k
istanze di una parola.
Come posso fare questo?
Per esempio. Supponiamo che il file foo.txt
contenga occorrenze di 100 istanze della parola "linux".
Devo sostituire solo le prime 50 occorrenze.
Risposte:
La prima sezione di seguito descrive l'uso sed
per 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.
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 sed
offre 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.
Utilizziamo tre sed
comandi di sostituzione:
s/\<old\>/\n/g4
Questa è l'estensione GNU per sostituire la quarta e tutte le occorrenze successive di old
con \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' -E
opzione per sed
.
s/\<old\>/new/g
Rimangono solo le prime tre occorrenze old
e questo le sostituisce tutte con new
.
s/\n/old/g
La quarta e tutte le occorrenze rimanenti di old
sono state sostituite con \n
nella prima fase. Questo li riporta al loro stato originale.
Se GNU sed non è disponibile e si desidera modificare le prime 3 occorrenze di old
a new
, utilizzare tre s
comandi:
$ 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 k
tratta 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 -e
opzione. Potrebbe anche essere necessario verificare che sed
supporti i simboli di confine di parola \<
e \>
.
Possiamo dire a sed di leggere l'intero file e quindi eseguire le sostituzioni. Ad esempio, per sostituire le prime tre occorrenze old
dell'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;x
leggono 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 sed
grado di gestire le linee lunghe. GNU sed
dovrebbe andare bene. Coloro che usano una versione non GNU di sed
dovrebbero testare la sua capacità di gestire le linee lunghe.
Con una GNU sed, possiamo usare ulteriormente il g
trucco descritto sopra, ma con \n
sostituito 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 k
diventa grande. Ciò presuppone, tuttavia, che \x00
non si trova nella stringa originale. Dal momento che è impossibile mettere il personaggio \x00
in una stringa bash, questo è di solito un presupposto sicuro.
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 tr
per 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'
.
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 27
occorrenze di old
connew
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 daold
anew
.
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
SHOULD
fornisce 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
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" ...
22
211
211
311
Lì uso due tecniche notevoli. In primo luogo, ogni occorrenza di 1
su 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 he
con hey
esso continuerà a funzionare.
Lo faccio come:
s/1/\
&/g
In secondo luogo, conto i rimpiazzi aggiungendo un carattere al h
vecchio 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 -e
cose 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
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:
new
per old
( s/old/new/
) e ogni volta che può, incrementa la variabile $i
( ++$i
).1 while ...
) fintanto che ha effettuato meno di $n
sostituzioni in totale e può effettuare almeno una sostituzione su quella linea.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
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.
Con GNU awk
è possibile impostare il separatore di record RS
sulla 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 k
record 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