Come sostituire solo l'ennesima occorrenza di un pattern in un file?


10

Come sostituire la terza occorrenza della stringa nel file usando il sedcomando.

Esempio:

Modifica solo la terza occorrenza di isto usnel file.

Il mio file di input contiene:

hai this is linux.
hai this is unix.
hai this is mac.
hai this is unchanged.

Mi aspetto che l'output sia:

hai this is linux.
hai thus is unix.
hai this is mac.
hai this is unchanged.

3
Ingresso e uscita sono uguali.
Hauke ​​Laging,

4
sednon è lo strumento giusto per il lavoro.
Choroba,

@don_crissti L'ho risolto. L'OP non aveva usato gli strumenti di formattazione (a proposito, Sureshkumar, vedi qui per aiuto sulla modifica delle tue domande) e i redattori successivi avevano frainteso ciò che si voleva.
terdon

Risposte:


11

È molto più facile perl.

Per modificare la terza occorrenza:

perl -pe 's{is}{++$n == 3 ? "us" : $&}ge'

Per cambiare ogni 3 ° occorrenza:

perl -pe 's{is}{++$n % 3 ? $& : "us"}ge'

3

Quando la stringa di sostituzione si verifica una sola volta per riga, è possibile combinare diverse utilità.
Quando l'input è nel file "input" e si sta sostituendo "is" con "us", è possibile utilizzare

LINENR=$(cat input | grep -n " is " | head -3 | tail -1 | cut -d: -f1)
cat input | sed ${LINENR}' s/ is / us /'

Nell'esempio nella domanda, ce ne sono più di uno isper riga.
terdon

Pensavo stessi cercando "is" con spazi. Potrei modificare la mia risposta con il comando tr come usava @jimmij, ma la mia soluzione sarebbe diventata molto inferiore alla sua.
Walter A,

Non sono il richiedente :). Ho pensato la stessa cosa, motivo per cui avevo votato a fondo la tua risposta, ma se guardi la versione originale della domanda (fai clic sul link "Modificato X minuti fa") vedrai che l'OP si aspettava che fosse in questo essere cambiato in così . A proposito, non c'è bisogno di un gatto lì.
terdon

2

Lo script seguente (usando la sintassi GNU sed ) è utilizzabile per la modifica sul posto non per l'output perché interrompe le linee di stampa dopo la sostituzione desiderata:

sed -i '/is/{: 1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; q}' text.file

Se la tua decisione sulla choroba ti piace puoi modificare sopra

sed '/is/{:1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; :2 ; n ; $!b2}' text.file

che genera tutte le linee

Oppure devi mettere tutte le linee nello spazio del modello (in memoria, quindi fai attenzione con la limitazione delle dimensioni) e fai la sostituzione

sed ': 1 ; N ; $!b1 ; s/is/us/3 ' text.file

2

Puoi usarlo sedse in precedenza le nuove righe vengono sostituite con altri caratteri, ad esempio:

tr '\n' '\000' | sed 's/is/us/3' | tr '\000' '\n'

E lo stesso con pure (GNU) sed:

sed ':a;N;$!ba;s/\n/\x0/g;s/is/us/3;s/\x0/\n/g'

( sedsostituzione newline spudoratamente rubata da https://stackoverflow.com/a/1252191/4488514 )


Se stai per usare la sedsintassi specifica GNU , potresti anche usarla sed -z 's/is/us/3'.
Stéphane Chazelas,

@ StéphaneChazelas -zdeve essere una nuova funzionalità, il mio GNU sed version 4.2.1non sa nulla di questa opzione.
Jimmij,

1
Aggiunto in 4.2.2 (2012). Nella tua seconda soluzione, non hai bisogno della conversione per \x0passare.
Stéphane Chazelas,

Mi dispiace per la modifica. Non avevo visto la versione originale della domanda e qualcuno l'aveva fraintesa e aveva modificato la riga sbagliata. Sono tornato alla versione precedente.
terdon

1
p='[:punct:]' s='[:space:]'
sed -Ee'1!{/\n/!b' -e\}            \
     -e's/(\n*)(.*)/ \2 \1/'       \
     -e"s/is[$p]?[$s]/\n&/g"       \
     -e"s/([^$s])\n/\1/g;1G"       \
-e:c -e"s/\ni(.* )\n{3}/u\1/"      \
     -e"/\n$/!s/\n//g;/\ni/G"      \
     -e's//i/;//tc'                \
     -e's/^ (.*) /\1/;P;$d;N;D'

Quel po ' sedporta solo un conteggio delle isoccorrenze da una riga all'altra. Dovrebbe gestire in modo affidabile tutti gli ises per riga che ci si lancia, e non ha bisogno di bufferizzare le vecchie linee mentre lo fa - mantiene solo un singolo carattere di nuova riga per ogni cosa ische incontra che non fa parte di un'altra parola.

Il risultato è che modificherà solo la terza occorrenza in un file e conterrà conteggi per riga. Quindi se un file appare come:

1. is is isis
2. is does

... stamperà ...

1. is is isis
2. us does

Per prima cosa gestisce i casi limite inserendo uno spazio nella testa e nella coda di ogni linea. Ciò rende un po 'più facile accertare i confini delle parole.

Quindi cerca ises validi inserendo una \newline prima di tutte le occorrenze ische precedono immediatamente zero o un carattere di punteggiatura seguito da uno spazio. \nEsegue un altro passaggio e rimuove tutte le ewline immediatamente precedute da un carattere non spaziale. Questi marcatori lasciati indietro corrisponderanno is.e isma non thiso ?is.

Successivamente raccoglie ciascun marcatore alla coda della stringa: per ogni \nicorrispondenza su una linea, aggiunge una \newline alla coda della stringa e la sostituisce con una io u. Se ci sono 3 \newline di fila raccolte nella coda della stringa, allora usa u - altrimenti l'i. La prima volta che si usa au è anche l'ultimo: la sostituzione avvia un ciclo infinito che si riduce a get line, print line, get line, print line,così via.

Alla fine di ogni ciclo del ciclo di prova, pulisce gli spazi inseriti, stampa solo fino alla prima nuova riga presente nello spazio del motivo e riparte.

Aggiungerò un lcomando ook in testa al ciclo come:

l; s/\ni(.* )\n{9}/u\1/...

... e dai un'occhiata a cosa fa mentre funziona con questo input:

hai this is linux.
hai this is unix.


hai this is mac.
hai this is unchanged is.

... quindi ecco cosa fa:

 hai this \nis linux. \n$        #behind the scenes
hai this is linux.               #actually printed
 hai this \nis unix. \n\n$       #it builds the marker string
hai this is unix.
  \n\n\n$                        #only for lines matching the

  \n\n\n$                        #pattern - and not otherwise.

 hai this \nis mac. \n\n\n$      #here's the match - 3 ises so far in file.
hai this us mac.                 #printed
hai this is unchanged is.        #no look here - this line is never evaled

Ha più senso forse con più ises per riga:

nthword()(  p='[:punct:]' s='[:space:]'         
    sed -e '1!{/\n/!b' -e\}             \
        -e 's/\(\n*\)\(.*\)/ \2 \1/'    \
        -e "s/$1[$p]\{0,1\}[$s]/\n&/g"  \
        -e "s/\([^$s]\)\n/\1/g;1G;:c"   \
        -e "${dbg+l;}s/\n$1\(.* \)\n\{$3\}/$2\1/" \
        -e '/\n$/!s/\n//g;/\n'"$1/G"    \
        -e "s//$1/;//tc" -e 's/^ \(.*\) /\1/'     \
        -e 'P;$d;N;D'
)        

È praticamente la stessa cosa, ma scritto con POSIX BRE e una rudimentale gestione degli argomenti.

 printf 'is is. is? this is%.0s\n' {1..4}  | nthword is us 12

...prende...

is is. is? this is
is is. is? this is
is is. is? this us
is is. is? this is

... e se abilito ${dbg}:

printf 'is is. is? this is%.0s\n' {1..4}  | 
dbg=1 nthword is us 12

... possiamo vederlo iterare ...

 \nis \nis. \nis? this \nis \n$
 is \nis. \nis? this \nis \n\n$
 is is. \nis? this \nis \n\n\n$
 is is. is? this \nis \n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n\n\n\n\n$
is is. is? this us
is is. is? this is

Hai capito che il tuo esempio dice "isis"?
flarn2006,

@ flarn2006 - Sono abbastanza sicuro che lo sia.
Mikeserv,

0

Ecco una soluzione logica che utilizza sede trche deve essere scritta in uno script affinché funzioni. Il codice seguente sostituisce ogni terza occorrenza della parola specificata nel sedcomando. Sostituisci i=3con i=nper far funzionare tutto questo n.

Codice:

# replace new lines with '^' character to get everything onto a single line
tr '\n' '^' < input.txt > output.txt

# count number of occurrences of the word to be replaced
num=`grep -o "apple" "output.txt" | wc -l`

# in successive iterations, replace the i + (n-1)th occurrence
n=3
i=3
while [ $i -le $num ]
do
    sed -i '' "s/apple/lemon/${i}" 'output.txt'
    i=$(( i + (n-1) ))
done

# replace the '^' back to new line character
tr '^' '\n' < output.txt > tmp && mv tmp output.txt


Perché questo funziona:

Supponiamo che il file di testo sia a b b b b a c a d a b b b a b e b z b s b a b.

  • Quando n = 2: vogliamo sostituire ogni seconda occorrenza di b.

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . ^ . ^ . . . . . . ^ . . ^ . . . ^ . ^ . ^
    • In primo luogo sostituiamo la seconda occorrenza, quindi la terza occorrenza, quindi la quarta, la quinta e così via. Conta nella sequenza mostrata sopra per vederlo da solo.
  • Quando n = 3: vogliamo sostituire ogni terza occorrenza di b.

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . . ^ . . . . . . . ^ . . . . ^ . . . . . ^
    • In primo luogo sostituiamo la terza occorrenza, quindi la quinta, quindi la settima, la nona, l'undicesima e così via.
  • Quando n = 4: vogliamo sostituire ogni terza occorrenza di b.

    • In primo luogo sostituiamo la 4a occorrenza, quindi la 7a, poi la 10a, 13a e così via.
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.