Ersetzen Sie die Zeichenfolge durch einen sequentiellen Index

9

Kann jemand einen eleganten Weg vorschlagen, um dies zu erreichen?

Eingang:

test  instant  ()

test  instant  ()

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

Ausgabe sollte sein:

test      instant1  ()

test      instant2  ()

test      instant1000()

Die leeren Zeilen befinden sich in meinen Eingabedateien und es gibt viele Dateien im selben Verzeichnis, die ich gleichzeitig verarbeiten muss.

Ich habe versucht, viele Dateien im selben Verzeichnis zu ersetzen, aber es hat nicht funktioniert.

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

Fehler:

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

und ich habe es auch versucht: perl -i -pe 's/instant/$& . ++$n/ge' *.vs

Es hat funktioniert, aber der Index wurde immer weiter von einer Datei zur anderen erhöht. Ich möchte das für diff-Datei auf 1 zurücksetzen. irgendwelche guten Vorschläge?

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

funktioniert, aber es ersetzt alle anderen Dateien sollte nicht ersetzt werden. Ich ziehe es vor, die Dateien nur durch "* .txt" zu ersetzen.

user3342338
quelle
Und bestehen sie alle ausschließlich aus Leerzeilen oder test instant ()?
Terdon
Ich habe die doppelten Zeilen wieder eingefügt. Sie sind oft ein Zeichen dafür, dass neue Benutzer nicht wissen, wie sie das Markup dieser Site verwenden sollen. Deshalb hat terdon sie entfernt, während Sie Ihren Dateiinhaltsblock richtig eingerückt haben, damit er als Dateiinhalt angezeigt wird. Hoffe es ist jetzt ok
Timo

Antworten:

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

oder mit GNU awk:

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

Fügen Sie die -iOption hinzu, um die Dateien direkt zu bearbeiten perl:

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

Oder rekursiv:

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

Erklärungen

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

-pbesteht darin, die Eingabe zeilenweise zu verarbeiten, den -efür jede Zeile übergebenen Ausdruck auszuwerten und auszudrucken. Für jede Zeile ersetzen wir (mit dem s/re/repl/flagsOperator) sich instantselbst ( $&) und den inkrementierten Wert einer Variablen ++$n. Die gFlagge ist die Substitution global (nicht nur einmal) zu machen, und eso , dass der Ersatz als perl - Code zu interpretieren ist e bewerten (keine fester string).

Für die direkte Bearbeitung, bei der ein Perl-Aufruf mehr als eine Datei verarbeitet, möchten wir $njede Datei zurücksetzen. Stattdessen verwenden wir $n{$ARGV}(wo $ARGVist die aktuell verarbeitete Datei).

Der awkverdient eine Erklärung.

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

Wir nutzen die Fähigkeit von GNU awk, Datensätze in beliebigen Zeichenfolgen (sogar regulären Ausdrücken) zu trennen. Mit setzen -vRS=instantwir den Aufzeichnungsabscheider auf instant. RTist die Variable, die enthält RS, instantwomit normalerweise übereinstimmt , mit Ausnahme des letzten Datensatzes, in dem es sich um die leere Zeichenfolge handelt. In der obigen Eingabe sind die Datensätze ( $0) und Datensatzterminatoren ( RT) ( [$0|RT]):

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

Wir müssen also zu Beginn jedes Datensatzes mit Ausnahme des ersten Datensatzes eine inkrementelle Zahl einfügen.

Welches ist, was wir oben tun. Für den ersten Datensatz nwird leer sein. Wir setzen ORS (den Ausgangsaufzeichnungsspeicher ) auf RT, damit awk gedruckt wird n $0 RT. Dies geschieht mit dem zweiten Ausdruck ( ++n), der eine Bedingung ist, die immer als wahr ausgewertet wird (eine Zahl ungleich Null), und daher wird die Standardaktion (Drucken $0 ORS) für jeden Datensatz ausgeführt.

Stéphane Chazelas
quelle
4
Dies könnte eine Erklärung gebrauchen .
Gilles 'SO - hör auf böse zu sein'
4

sedist wirklich nicht das beste Werkzeug für den Job, Sie möchten etwas mit besseren Skriptfunktionen. Hier sind einige Möglichkeiten:

  • Perl

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

    Das -pbedeutet "jede Zeile drucken", nachdem das angegebene Skript angewendet wurde -e. Der -000"Absatzmodus" wird aktiviert, sodass Datensätze (Zeilen) durch aufeinanderfolgende Zeilenumbrüche ( \n) definiert werden. Dadurch können Zeilen mit doppeltem Abstand korrekt behandelt werden. $&ist das letzte übereinstimmende Muster und $.die aktuelle Zeilennummer der Eingabedatei. Mit dem ein s///ekann ich Ausdrücke im Substitutionsoperator auswerten.

  • awk (dies setzt voraus, dass Ihre Daten genau wie gezeigt sind, mit drei durch Leerzeichen getrennten Feldern)

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

    Hier erhöhen wir die kVariable knur, wenn die aktuelle Zeile nicht leer ist. /./In diesem Fall drucken wir auch die erforderlichen Informationen. Leere Zeilen werden unverändert gedruckt.

  • verschiedene Muscheln

     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 
    

    Hier wird jede Eingabezeile automatisch in Leerzeichen aufgeteilt und die Felder werden als $a, $bund gespeichert $c. Dann wird innerhalb der Schleife $cfür jede Zeile, für die $anicht leer ist, um eins erweitert, und der aktuelle Wert wird neben dem zweiten Feld gedruckt $b.

HINWEIS: Bei allen oben genannten Lösungen wird davon ausgegangen, dass alle Zeilen in der Datei dasselbe Format haben. Wenn nicht, ist die Antwort von @ Stephane der richtige Weg.


Um mit vielen Dateien umzugehen und davon auszugehen, dass Sie dies für alle Dateien im aktuellen Verzeichnis tun möchten , können Sie Folgendes verwenden:

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

ACHTUNG: Das setzt voraus , einfache Dateinamen ohne Leerzeichen, wenn nötig mit etwas komplexen, geht für (unter der Annahme umgehen ksh93, zshoder bash):

find . -type f -print0 | while IFS= read -r -d ''; do
    perl -i -000pe 's/instant/$& . $./e' "$file"
done
terdon
quelle
Das Perl-Skript funktioniert. Es gibt jedoch ein kleines Problem, wenn die Zeilen einen doppelten Abstand haben.
user3342338
@ user3342338 Ja, das erhöht den Zähler, da ich die aktuelle Zeilennummer verwende. Dies ist ein sehr naiver Ansatz, da Stephane's robuster ist. Keine dieser Zeilen funktioniert, wenn Sie leere Zeilen haben oder wenn eine Ihrer Zeilen von dem abweicht, was Sie anzeigen.
Terdon
@ user3342338 siehe aktualisierte Antwort. Sie sollten jetzt alle für Dateien mit doppeltem Abstand funktionieren.
Terdon
Tolle Antwort und die Möglichkeit alternativer Methoden !! Vielen Dank
Madivad