Sostituisci stringa con indice sequenziale


10

Qualcuno può suggerire un modo elegante per raggiungere questo obiettivo?

Ingresso:

test  instant  ()

test  instant  ()

...
test  instant  ()    //total 1000 lines

l'output dovrebbe essere:

test      instant1  ()

test      instant2  ()

test      instant1000()

Le righe vuote sono nei miei file di input e ci sono molti file nella stessa directory che devo elaborare contemporaneamente.

Ho provato questo per sostituire molti file nella stessa directory e non ha funzionato.

for file in ./*; do perl -i -000pe 's/instance$& . ++$n/ge' "$file"; done

errori:

Substitution replacement not terminated at -e line 1.
Substitution replacement not terminated at -e line 1.

e ho anche provato questo:

perl -i -pe 's/instant/$& . ++$n/ge' *.vs

Funzionava ma l'indice continuava a incrementare da un file all'altro. Vorrei ripristinarlo a 1 quando si cambia in un nuovo file. Qualche buon suggerimento?

find . -type f -exec perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' {} +

funziona ma ha sostituito tutti gli altri file non dovrebbe essere sostituito. Preferisco solo sostituire i file con *.txt.


E sono tutti costituiti esclusivamente da righe vuote o test instant ()?
terdon

Ricolloco le linee a doppia spaziatura, spesso sono un segno di nuovi utenti che non sanno come usare il markup di questo sito, ecco perché terdon le ha rimosse mentre indentava correttamente il blocco del contenuto del file in modo che venga visualizzato come contenuto del file. Spero sia ok adesso.
Timo,

Risposte:


14
perl -pe 's/instant/$& . ++$n/ge'

o con GNU awk:

awk -vRS=instant '{$0=n$0;ORS=RT}++n'

Per modificare i file sul posto, aggiungi l' -iopzione a perl:

perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' ./*.vs

O ricorsivamente:

find . -name '*.vs' -type f -exec perl -pi -e '
  s/instant/$& . ++$n{$ARGV}/ge' {} +

spiegazioni

perl -pe 's/instant/$& . ++$n/ge'

-pè elaborare l'input riga per riga, valutare l'espressione passata -eper ogni riga e stamparla. Per ogni riga, sostituiamo (usando l' s/re/repl/flagsoperatore) instantper sé ( $&) e il valore incrementato di una variabile ++$n. La gbandiera è rendere la sostituzione globale (non solo una volta), e ein modo che la sostituzione viene interpretato come codice Perl e valuate (non una stringa fissa).

Per la modifica sul posto in cui una chiamata perl elabora più di un file, vogliamo $nreimpostare su ciascun file. Invece, usiamo $n{$ARGV}(dove si $ARGVtrova il file attualmente elaborato).

Il awkmerita un po 'di spiegazione.

awk -vRS=instant '{$0=n$0;ORS=RT}++n'

Stiamo sfruttando la capacità di GNU awkdi separare i record su stringhe arbitrarie (anche regexps). Con -vRS=instant, impostiamo il registratore vocale su instant. RTè la variabile che contiene ciò che è stato abbinato RS, quindi in genere, ad instanteccezione dell'ultimo record in cui sarà la stringa vuota. Nell'input sopra i record ( $0) e i terminatori di record ( RT) sono ( [$0|RT]):

[test  |instant][  ()
test  |instant][  ()
...
test  |instant][  ()    //total 1000 lines|]

Quindi tutto ciò che dobbiamo fare è inserire un numero crescente all'inizio di ogni record tranne il primo.

Questo è quello che facciamo sopra. Per il primo record, nsarà vuoto. Impostiamo ORS (il dispositivo di output del registro esterno ) su RT, in modo che le awk stampe n $0 RT. Lo fa sulla seconda espressione ( ++n) che è una condizione che viene sempre valutata come vera (un numero diverso da zero), quindi l'azione predefinita (di stampa $0 ORS) viene eseguita per ogni record.



5

sednon è davvero lo strumento migliore per il lavoro, vuoi qualcosa con migliori capacità di scripting. Ecco alcune scelte:

  • perl

    perl -00pe 's/instant/$& . $./e' file 

    Il -pmezzo "stampa ogni riga" dopo aver applicato lo script fornito -e. L' -00attivazione della "modalità paragrafo" in modo che i record (linee) siano definiti da caratteri newline consecutivi ( \n), ciò consente di gestire correttamente le linee a doppia spaziatura. $&è l'ultimo modello corrispondente ed $.è il numero di riga corrente del file di input. In ein s///emi consente di valutare le espressioni nell'operatore di sostituzione.

  • awk (questo presuppone che i tuoi dati siano esattamente come mostrato, con tre campi separati da spazio)

    awk '{if(/./) print $1,$2 ++k,$3; else print}' file 

    Qui, incrementiamo la kvariabile ksolo se la riga corrente non è vuota, /./nel qual caso stampiamo anche le informazioni necessarie. Le righe vuote vengono stampate così come sono.

  • varie conchiglie

     n=0; while read -r a b c; do 
       if [ "$a" ] ; then 
          (( n++ ))
          printf "%s %s%s %s\n" "$a" "$b" "$n" "$c"
       else
          printf "%s %s %s\n" "$a" "$b" "$c"
       fi
     done < file 

    Qui, ogni riga di input viene automaticamente suddivisa in spazi bianchi e i campi vengono salvati come $a, $be $c. Poi, all'interno del ciclo, $cè aumentata di uno per ogni linea per cui $anon è vuoto e il suo valore corrente viene stampata accanto al secondo campo, $b.

NOTA: tutte le soluzioni di cui sopra presuppongono che tutte le righe nel file siano dello stesso formato. In caso contrario, la risposta di @ Stephane è la strada da percorrere.


Per gestire molti file e supporre che si desideri eseguire questa operazione su tutti i file nella directory corrente, è possibile utilizzare questo:

for file in ./*; do perl -i -00pe 's/instant/$& . $./e' "$file"; done

ATTENZIONE: Che assume i nomi di file semplici senza spazi, se necessario trattare con qualcosa di più complesso, andare per (assumendo ksh93, zsho bash):

find . -type f -print0 | while IFS= read -r -d ''; do
    perl -i -00pe 's/instant/$& . $./e' "$file"
done

lo script perl funziona. tuttavia c'è un piccolo problema se le linee sono a doppio spazio.
user3342338,

@ user3342338 sì, ciò aumenterà il contatore poiché sto usando il numero di riga corrente. Questo è un approccio molto ingenuo, come ho detto che Stephane è più robusto. Nessuno di questi funziona se hai linee vuote o se una qualsiasi delle tue linee si discosta da ciò che mostri.
terdon

@ user3342338 vedi risposta aggiornata. Ora dovrebbero funzionare tutti per file con spaziatura doppia.
terdon

Ottima risposta e l'opzione di metodi alternativi !! Grazie
Madivad,

0

Se vuoi risolvere questo problema, sedpuoi usare qualcosa del genere (in bash):

i=0
while read -r line; do
  sed "s/\(instant\)/\1${i}/" <<< "${line}"
  [[ ${line} =~ instant ]] && i=$(( i + 1 ))
done < file

o una soluzione più portatile sarebbe:

i=0
while read -r line; do
  echo "${line}" | sed "s/\(instant\)/\1${i}/"
  if echo "${line}" | grep -q inst; then
    i=$(( i + 1 ))
  fi
done < 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.