2014-08-29 16 views
5

Ho un file contenente un elenco di coppie di sostituzione (circa 100 di esse) che vengono utilizzate da sed per sostituire le stringhe nei file.Ottimizza script shell per più sostituzioni sed

Le coppie vanno come:

old|new 
tobereplaced|replacement 
(stuffiwant).*(too)|\1\2 

e il mio codice attuale è:

cat replacement_list | while read i 
do 
    old=$(echo "$i" | awk -F'|' '{print $1}') #due to the need for extended regex 
    new=$(echo "$i" | awk -F'|' '{print $2}') 
    sed -r "s/`echo "$old"`/`echo "$new"`/g" -i file 
done 

non posso fare a meno di pensare che ci sia un modo più ottimale di effettuare le sostituzioni. Ho provato a girare il ciclo in modo da scorrere prima le righe del file, ma si è rivelato molto più costoso.

Esistono altri modi per velocizzare questo script?

EDIT

Grazie per tutte le risposte rapide. Lasciami provare i vari suggerimenti prima di scegliere una risposta.

Una cosa da chiarire: Ho anche bisogno di funzionalità sottoespressioni/gruppi. Per esempio, una sostituzione potrei avere bisogno è:

([0-9])U|\10 #the extra brackets and escapes were required for my original code 

alcuni dettagli sui miglioramenti (da aggiornare):

  • Metodo: il tempo di elaborazione
  • sceneggiatura originale: 0.85s
  • cut anziché awk: 0,71s
  • metodo di anubhava: 0,18s
  • Metodo
  • di chthonicdaemon: 0.01s
+0

Questa domanda ha avuto risposte [qui] (http://stackoverflow.com/questions/25329309). Sì, stai cercando la velocità, ma per favore, perché due domande. – martin

+1

A dire il vero, questa domanda non porta realmente l'elemento della velocità né quello delle sottoespressioni. Le risposte fornite qui sono state molto più utili. –

+1

Ok, quindi chiarisci la tua domanda in relazione alle sottoespressioni inserendole nei dati e fornendo input e output desiderati, che miglioreranno notevolmente la tua domanda e la distingueranno chiaramente dalle altre. – martin

risposta

7

È possibile utilizzare sed per produrre correttamente formattato per sed ingresso:

sed -e 's/^/s|/; s/$/|g/' replacement_list | sed -r -f - file 
+1

hmmmm 'sed: -e espressione # 1, char 17: opzione sconosciuta a 's''. il carattere 17 sembra essere il | delimitatore nel mio file di sostituzione –

+0

detto questo, ottengo il concetto ora e sto cercando di provarlo. –

+1

il problema è con la virgola (typo?). ma in ogni caso, una velocità assolutamente esplosiva e abbastanza parsimoniosa! Grazie! –

3

Recentemente ho benchmark vari metodi di sostituzione della stringa, tra i quali un programma personalizzato, sed -e, perl -lnpe e una probabilmente non che molto conosciuto utility a riga di comando MySQL, replace. replace ottimizzato per le sostituzioni di stringa era quasi un ordine di grandezza più veloce di sed. I risultati sembravano qualcosa di simile (più lento prima):

custom program > sed > LANG=C sed > perl > LANG=C perl > replace 

Se si desidera prestazioni, utilizzare replace. Per averlo a disposizione sul tuo sistema, dovrai installare qualche distribuzione MySQL, comunque.

Da replace.c:

sostituzione di stringhe in file di testo

Questo programma sostituisce le stringhe nei file o da stdin stdout. Accetta un elenco di coppie da stringa/stringa e sostituisce ogni occorrenza di una stringa con la stringa corrispondente. La prima occorrenza di una stringa trovata viene confrontata. Se c'è più di una possibilità per la stringa di sostituire, le partite più lunghe sono preferite prima delle partite più brevi.

...

I programmi creano un DFA-state-machine delle stringhe e la velocità non dipende dal conteggio delle stringhe di sostituzione (solo del numero di sostituzioni). Si presume che una linea finisca con \ n o \ 0. Non ci sono limiti di memoria sulla lunghezza delle stringhe.


Altro su sed. È possibile utilizzare più core con sed, suddividendo le vostre sostituzioni in gruppi #cpus e poi tubo di loro attraverso sed comandi, qualcosa di simile:

$ sed -e 's/A/B/g; ...' file.txt | \ 
    sed -e 's/B/C/g; ...' | \ 
    sed -e 's/C/D/g; ...' | \ 
    sed -e 's/D/E/g; ...' > out 

Inoltre, se si utilizza sed o perl e il sistema ha un UTF- 8 setup, allora aumenta anche le prestazioni di inserire un LANG=C davanti ai comandi:

$ LANG=C sed ... 
+0

Su quell'argomento, sed corre più veloce con N numero di '-e' o N numero di comandi sed singolari? Quando N> 100. –

+0

IIRC, era un po 'più veloce usare un 'N' di rimpiazzi in un singolo comando' sed' rispetto ai comandi 'N' numero' sed'. Ricordo di essere un po 'sorpreso, che eseguire alcune centinaia di processi in parallelo non ha degradato troppo le prestazioni. – miku

1

è possibile ridurre le invocazioni awk inutili e utilizzare BASH per rompere coppie nome-valore:

while IFS='|' read -r old new; do 
    # echo "$old :: $new" 
    sed -i "s~$old~$new~g" file 
done < replacement_list 

IFS = '|' darà abilita read per popolare il nome-valore in 2 variabili shell diverse old e new.

Si presume che ~ non sia presente nelle coppie nome-valore. Se questo non è il caso, sentiti libero di usare un delimitatore sed alternativo.

+1

Sembra molto veloce, ma ho problemi con le sottoespressioni. Invece di restituire i valori memorizzati nei gruppi, li sto ottenendo letteralmente (ad es. \ 1 \ 2, ecc.). –

+0

Puoi dirmi alcune righe di esempio con quelle sottoespressioni in modo che io possa riprodurle e suggerirti una correzione. – anubhava

+0

Grazie per la risposta, un esempio è '([0-9]) U | \\ 10'. –

0

si può provare questo.

pattern='' 
cat replacement_list | while read i 
do 
    old=$(echo "$i" | awk -F'|' '{print $1}') #due to the need for extended regex 
    new=$(echo "$i" | awk -F'|' '{print $2}') 
    pattern=${pattern}"s/${old}/${new}/g;" 
done 
sed -r ${pattern} -i file 

Questo eseguirà il comando sed solo una volta sul file con tutte le sostituzioni. Si consiglia inoltre di sostituire awk con cut. cut potrebbe essere più ottimizzato di awk, anche se non ne sono sicuro.

old=`echo $i | cut -d"|" -f1` 
new=`echo $i | cut -d"|" -f2` 
+0

miglioramento di 0,3 secondi. Non male. –

+0

Mi sono sbagliato, il 'cut' ha accelerato il processo ma il bit del pattern non ha funzionato. Per qualche motivo, il primo carattere del nome file inviato a 'sed' è stato cancellato. Cercando di capire perché. –

0

Si potrebbe desiderare di fare il tutto in awk:

awk -F\| 'NR==FNR{old[++n]=$1;new[n]=$2;next}{for(i=1;i<=n;++i)gsub(old[i],new[i])}1' replacement_list file 

creare un elenco di vecchie e nuove parole dal primo file. Lo next assicura che il resto dello script non venga eseguito sul primo file. Per il secondo file, scorrere l'elenco delle sostituzioni ed eseguirle singolarmente. Il 1 alla fine significa che la linea è stampata.

+0

Un problema per me è che utilizzo i gruppi (ad es. \ 1) nelle sostituzioni 'sed'. –

+0

Stai usando gawk? Se è così, questo potrebbe essere adattato per usare 'gensub' –

1

Ecco quello che vorrei provare:

  1. negozio tua sed di ricerca-sostituzione coppia in un array Bash simili;
  2. crea il tuo comando sed in base a questo array utilizzando parameter expansion
  3. comando di esecuzione.
patterns=(
    old new 
    tobereplaced replacement 
) 
pattern_count=${#patterns[*]} # number of pattern 
sedArgs=() # will hold the list of sed arguments 

for ((i=0 ; i<$pattern_count ; i=i+2)); do # don't need to loop on the replacement… 
    search=${patterns[i]}; 
    replace=${patterns[i+1]}; # … here we got the replacement part 
    sedArgs+=" -e s/$search/$replace/g" 
done 
sed ${sedArgs[@]} file 

Questo risultato in questo comando:

-es sed/vecchio file/new/g -es/tobereplaced/sostituzione/g

0
{ cat replacement_list;echo "-End-"; cat YourFile; } | sed -n '1,/-End-/ s/$/³/;1h;1!H;$ {g 
t again 
:again 
    /^-End-³\n/ {s///;b done 
     } 
    s/^\([^|]*\)|\([^³]*\)³\(\n\)\(.*\)\1/\1|\2³\3\4\2/ 
    t again 
    s/^[^³]*³\n// 
    t again 
:done 
    p 
    }' 

Altro per divertimento da codificare tramite sed. Prova forse per un periodo di perfomance perché questo avvia solo 1 sed che è ricorsivo.

per POSIX sed (così --posix con GNU sed)

spiegazione lista sostituzione

  • copia davanti contenuto del file con un delimitatore (per la linea con ³ e per la lista con -End-) per un trattamento sed più facile (difficile da usare \ n nel carattere di classe in posix sed
  • posizionare tutte le righe nel buffer (aggiungere il delimitatore di riga per la lista di sostituzione e -End- prima)
  • se questo è -End-³, rimuovere la riga e andare alla stampa finale
  • sostituire ogni primo modello (gruppo 1) in testo di secondo patttern (gruppo 2)
  • se trovato, riavvio (t again)
  • remove prima riga
  • processo di riavvio (t again). T è necessario perché b non ripristina il test e il prossimo t è sempre true.