2014-08-29 14 views
5

Ich habe eine Datei mit einer Liste von Ersatzpaaren (etwa 100 von ihnen), die von sed verwendet werden, um Zeichenfolgen in Dateien zu ersetzen.Shell-Skript für mehrere sed Ersetzungen optimieren

Die Paare gehen wie:

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

und meine aktuellen Code ist:

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 

ich nicht denken helfen kann, aber dass es eine optimalere Weise die Ersetzungen durchzuführen. Ich habe versucht, die Schleife umzudrehen, um zuerst durch die Zeilen der Datei zu laufen, aber das erwies sich als viel teurer.

Gibt es andere Möglichkeiten, dieses Skript zu beschleunigen?

EDIT

Vielen Dank für die schnellen Antworten. Lassen Sie mich die verschiedenen Vorschläge ausprobieren, bevor Sie eine Antwort wählen.

Eine Sache zu klären: Ich brauche auch Teilausdrücke/Gruppen Funktionalität. Zum Beispiel ist ein Ersatz ich brauchen könnte:

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

Einige Details zu den Verbesserungen (aktualisiert werden):

  • Methode: Bearbeitungszeit
  • Original-Skript: 0.85s
  • statt awk: 0,71s
  • anubhavas Methode: 0.18s
  • chthonicdaemon Methode: 0,01s
+0

Diese Frage hat Antworten [hier] (http://stackoverflow.com/questions/25329309). Ja, Sie suchen nach Geschwindigkeit, aber bitte, warum zwei Fragen. – martin

+1

Um ehrlich zu sein, bringt diese Frage nicht wirklich das Element der Geschwindigkeit oder der Teilausdrücke zum Ausdruck. Die Antworten, die hier gegeben wurden, waren viel hilfreicher. –

+1

Ok, dann klären Sie Ihre Frage in Bezug auf die Teilausdrücke, indem Sie sie in den Daten platzieren und Eingabe und gewünschte Ausgabe bereitstellen, die Ihre Frage stark verbessern und klar von den anderen unterscheiden. – martin

Antwort

7

Sie sed verwenden können, um korrekt zu produzieren -formatierten sed Eingang:

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

hmmmm 'sed: -e Ausdruck # 1, char 17: unbekannte Option zu 's''. Zeichen 17 ist zufällig das | Begrenzer in meiner Ersetzungsdatei –

+0

Nachdem ich das gesagt habe, bekomme ich das Konzept und versuche es zu testen. –

+1

das Problem ist mit dem Komma (Tippfehler?). aber auf jeden Fall, absolut rasante Geschwindigkeit und ziemlich sparsam! Vielen Dank! –

3

ich vor kurzem verschiedene String-Ersatzmethoden gebenchmarkt, darunter ein benutzerdefiniertes Programm, sed -e, perl -lnpe und ein wahrscheinlich nicht so weit MySQL Kommandozeilen-Programm bekannt, replace. replace für String-Ersatz optimiert war fast eine Größenordnung schneller als sed. Die Ergebnisse sahen so etwas wie diese (langsamste zuerst):

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

Wenn Sie Leistung wollen, replace verwenden. Um es auf Ihrem System verfügbar zu haben, müssen Sie jedoch eine MySQL-Distribution installieren.

Von replace.c:

ersetzen Strings in Textdatei

Dieses Programm ersetzt Strings in Dateien oder von stdin nach stdout. Es akzeptiert eine Liste von from-string/to-string-Paaren und ersetzt jedes Vorkommen einer from-string durch die entsprechende to-string. Das erste Vorkommen einer gefundenen Zeichenfolge wird abgeglichen. Wenn es mehr als eine Möglichkeit gibt, die Zeichenfolge zu ersetzen, werden längere Übereinstimmungen vor kürzeren Übereinstimmungen bevorzugt.

...

Die Programme erstellen eine DFA-Zustandsmaschine der Strings und die Geschwindigkeit ist nicht abhängig von der Anzahl der Ersetzungsstrings (nur der Anzahl der Ersetzungen). Es wird angenommen, dass eine Zeile mit \ n oder \ 0 endet. Es gibt keine Begrenzung außer Speicher auf Länge der Zeichenfolgen.


Mehr zu sed. Sie können mehrere Kerne mit sed nutzen, indem Sie Ihre Ersatz in #cpus Gruppen aufgeteilt und dann Rohr sie durch sed Befehle, etwa wie folgt:

$ 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 

Auch, wenn Sie sed oder perl und Ihrem System hat eine UTF- 8 Setup, dann steigert auch seine Leistung eine LANG=C vor den Befehlen zu platzieren:

$ LANG=C sed ... 
+0

Zu diesem Thema, sed sed schneller mit N Anzahl von "-e" oder N Anzahl der einzelnen sed Befehle? Wenn N> 100. –

+0

IIRC war, war es ein bisschen schneller, ein "N" von Ersetzungen in einem einzelnen "sed" -Befehl zu verwenden als "N" number "sed" -Befehle. Ich erinnere mich, dass ich ein bisschen überrascht war, dass einige hundert Prozesse nicht gleichzeitig die Performance um ein Vielfaches verschlechterten. – miku

1

Sie awk unnötige Anrufungen abgeholzt und verwenden können BASH Name-Wert-Paare zu brechen:

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

IFS = '|' gibt enable read zum Auffüllen von name-value in 2 verschiedenen Shell-Variablen old und new.

Vorausgesetzt, dass ~ nicht in Ihren Name-Wert-Paaren vorhanden ist. Wenn dies nicht der Fall ist, können Sie ein alternatives sed-Trennzeichen verwenden.

+1

Das scheint sehr schnell zu sein, aber ich habe Probleme mit Teilausdrücken. Anstatt die in Gruppen gespeicherten Werte zurückzugeben, erhalte ich sie wörtlich (z. B. \ 1 \ 2 usw.). –

+0

Kannst du mir ein paar Beispielzeilen mit diesen Teilausdrücken geben, damit ich sie reproduzieren kann und dir eine Lösung vorschlage? – anubhava

+0

Danke für die Antwort, ein Beispiel ist '([0-9]) U | \\ 10'. –

0

Sie können dies versuchen.

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 

Dies wird den Befehl sed nur einmal auf die Datei mit allen Ersatz ausführen. Sie können auch awk durch ersetzen. ist vielleicht besser optimiert als awk, obwohl ich mir nicht sicher bin.

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

0,3 s Verbesserung. Nicht schlecht. –

+0

Ich habe mich geirrt, der "Cut" hat den Prozess beschleunigt, aber das Pattern-Bit hat nicht funktioniert. Aus irgendeinem Grund wurde das erste Zeichen des Dateinamens, der an sed übergeben wurde, gelöscht. Versuchen herauszufinden, warum. –

0

Sie möchten vielleicht die ganze Sache in awk tun:

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 

eine Liste der alten und neuen Wörter aus der ersten Datei aufzubauen. Die next stellt sicher, dass der Rest des Skripts nicht auf der ersten Datei ausgeführt wird. Für die zweite Datei, durchlaufen Sie die Liste der Ersetzungen und führen Sie sie einzeln nacheinander aus. Die 1 am Ende bedeutet, dass die Zeile gedruckt wird. Hier

+0

Ein Problem für mich ist, dass ich Gruppen (d. H. \ 1) in den 'sed' Ersatz verwenden. –

+0

Verwenden Sie gawk? Wenn ja, könnte dies angepasst werden, um "Gensub" zu verwenden –

1

ist das, was ich versuchen würde:

  1. speichern Ihre sed Such ersetzen Paar in einem Bash-Array dergleichen;
  2. Erstellen Sie Ihre Sed-Befehl basierend auf diesem Array mit parameter expansion
  3. ausführen Befehl.
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 

Dieses Ergebnis in diesem Befehl:

sed -en/alt/neu/g -en/tobereplaced/Ersatz/g Datei

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 
    }' 

Mehr zum Spaß über sed zu coden. Versuchen Sie vielleicht für eine Zeit Leistung, weil dies nur 1 sed starten, das rekursiv ist.

für Posix sed (so --posix mit GNU sed)

explaination

  • Kopie Ersatzliste vor Dateiinhalt mit einem Trennzeichen (für Zeile mit ³ und für die Liste mit -End-) für eine einfachere sed-handhabung (schwer zu verwenden \ n in der klasse zeichen in posix sed.
  • platzieren sie alle zeile im buffer (fügen sie das delimiter der zeile für ersatzliste und -End- vor)
  • wenn diese -End-³ ist, entfernen die Zeile und gehen zum endgültigen Druck
  • jedes erste Muster ersetzen (Gruppe 1) in Text zweiten patttern gefunden (Gruppe 2)
  • wenn gefunden, Neustart (t again)
  • remove erste Zeile
  • Neustart Prozess (t again). T wird benötigt, weil b den Test nicht zurücksetzt und der nächste t immer wahr ist.
Verwandte Themen