
Durchsucht die Datei nach der letzten Zeile mit einem bestimmten String, um eine Zeile nach dieser Übereinstimmung hinzuzufügen. Beispiel:
Eingang:
Apple
Banana
Apple
Orange
Apple
Grapefruit
Ausgabe:
Apple
Banana
Apple
Orange
Apple
I am new string replaced at last apple
Grapefruit
Antwort1
Mit GNU sed
können Sie Folgendes tun:
sed ':a;N;$! ba;s/.*\nApple/&\nYour appended line/'
Das Muster :a;N;$! ba
hängt Zeilen aus der Datei mit an den Puffer an, N
solange das Dateiende ( $
Adresse) nicht ( !
) erreicht ist (Sprung zu :a
mit ba
). Wenn du also die Schleife verlässt, sind alle Zeilen im Puffer und das Suchmuster .*\nApple
passt in die ganze Datei bis zur letzten Zeile, die mit beginnt Apple
. Dann &
fügt der in den Ersetzungsstring die ganze Übereinstimmung ein und hängt deinen Text an.
Eine portable Lösung, die sed
auch auf anderen Versionen funktioniert, wäre
sed -e ':a' -e 'N;$! ba' -e 's/.*\(\n\)Apple/&\1Your appended line/'
Hier wird das Skript an den Labels aufgeteilt und statt \n
im Ersetzungsstring zu verwenden (was bei Nicht-GNU- sed
s nicht garantiert funktioniert), wird auf den aus dem Muster verwiesen, das \(\)
mit der \1
Referenz umgeben ist.
Antwort2
Ich habe dieses Beispiel gesehenHier
sed -i -e "\$aTEXTTOEND" <filename>
Information:
i: edit files in place
e: add the script to the commands to be executed
$: sed address location
a: append command
TEXTTOEND: text to append to end of file
Antwort3
Ich gehe davon aus, dass Ihre Beispieleingabe eine Datei mit dem Namen ist fruits.txt
.
Möchte man nach dem letzten Vorkommen von „Apple“ eine neue Zeile einfügen, geht das sed
etwa so:
sed -rz 's/(^(.*\n)?Apple)(\n|$)/\1\ninserted line\n/'
Die -r
Option aktiviert erweiterte reguläre Ausdrücke.
Die -z
Option weist darauf hin sed
, dass als Zeilentrennzeichen NUL-Zeichen (ASCII-Code 0) anstelle der normalen Zeilenumbruchzeichen verwendet werden sollen. Auf diese Weise wird die gesamte Textdatei auf einmal verarbeitet, anstatt Zeile für Zeile.
Der sed
Befehl s/PATTERN/REPLACEMENT/
sucht in nach dem (erweiterten -r
) regulären Ausdruck PATTERN
und ersetzt ihn durch den ausgewerteten REPLACEMENT
String.
Der reguläre Ausdruck (^(.*\n)?Apple)(\n|$)
passt zu einer beliebigen Anzahl von Zeilen vom Anfang ( ^
) bis zum letzten Vorkommen von, Apple
gefolgt von einem Zeilenumbruch ( \n
) oder dem Dateiende ( $
).
Jetzt wird diese ganze Übereinstimmung durch ersetzt \1\ninserted line\n
, wobei dies \1
für den ursprünglichen Inhalt der ersten Übereinstimmungsgruppe steht, also alles bis Apple
. Tatsächlich wird also direkt nach dem letzten Vorkommen von „Apple“ inserted line
ein Zeilenumbruch ( ) eingefügt, der vorangestellt und nachgestellt wird .\n
Dies funktioniert auch, wenn die Zeile „Apple“ die erste, letzte oder einzige Zeile in der Datei ist.
Beispiel-Eingabedatei (Ihrem Beispiel wurde eine Zeile hinzugefügt, um das Verhalten deutlich zu machen):
Apple
Banana
Apple
Orange
Apple
Cherry
Beispielausgabe:
Apple
Banana
Apple
Orange
Apple
inserted line
Cherry
Wenn Sie mit dem Ergebnis zufrieden sind und die Änderungen direkt vor Ort vornehmen möchten fruits.txt
, anstatt nur die Änderungen auszugeben, können Sie die folgende -i
Option hinzufügen:
sed -irz 's/(^(.*\n)?Apple)(\n|$)/\1\ninserted line\n/'
Wenn Sie jedoch lediglich eine Textzeile an das Ende der Datei anhängen möchten, sed
ist dies nicht das optimale Tool.
>>
Sie können dann einfach die Umleitung der angehängten Ausgabe von Bash verwenden :
echo "I am new string replaced at last apple" >> fruits.txt
Das >>
nimmt die Standardausgabe des Befehls auf der linken Seite (der echo
Befehl druckt hier einfach seine Argumente auf die Standardausgabe) und hängt sie an die Datei an, die auf der rechten Seite angegeben ist. Wenn die Datei noch nicht existiert, wird sie erstellt.
Antwort4
In dieser Antwort:
- sed+tac (doppelte Eingabeumkehr)
- AWK mit Doppeleingang
- Perl mit doppelter Eingabe
- Alternatives Perl mit doppelter Eingabeumkehr
sed + tac
Bei diesem Ansatz kommen drei Prozesse zum Einsatz: Zuerst wird die Datei umgekehrt, dann wird sed verwendet, um die erste Übereinstimmung zu finden, der gewünschte Text wird nach der ersten Übereinstimmung eingefügt und dann wird der Text erneut umgekehrt.
$ tac input.txt | sed '0,/Apple/{s/Apple/NEW THING\n&/}' | tac
Apple
Banana
Apple
Orange
Apple
NEW THING
something else
something other entirely
blah
Die Grundidee ist dieselbe wie bei der unten beschriebenen alternativen Perl-Version: Die erste Übereinstimmung in der umgekehrten Zeilenliste ist die letzte Übereinstimmung in der ursprünglichen Liste. Der Vorteil dieses Ansatzes ist Einfachheit und Lesbarkeit für den Benutzer.
Dieser Ansatz funktioniert gut bei kleinen Dateien, ist jedoch bei großen Dateien umständlich und benötigt wahrscheinlich eine beträchtliche Zeit, um eine große Anzahl von Zeilen zu verarbeiten.
AWK
Awk kann für das, was Sie versuchen, ein wenig benutzerfreundlicher sein:
$ awk -v n="AFTER LAST APPLE" 'NR==FNR&&/Apple/{l=NR};NR!=FNR{if(FNR==l){print $0;print n}else print}' input.txt input.t>
Apple
Banana
Apple
Orange
Apple
AFTER LAST APPLE
something else
something other entirely
blah
Die Grundidee besteht darin, Ihre Eingabedatei zweimal an awk zu übergeben – zunächst, um herauszufinden, wo sich der letzte Apfel befindet, und dann, um den Text einzufügen, wenn wir die Position des letzten „Apfels“ übergeben, den wir bei der ersten Iteration gefunden haben.
Der Schlüssel zum Erfolg liegt in der Auswertung der Variablen NR
(wobei alle verarbeiteten Zeilen und Summen gezählt werden) und FNR
(Zeilennummer in der aktuellen Datei). Wenn diese beiden Dateien ungleich sind, wissen wir, dass wir die zweite Datei verarbeiten. Somit wissen wir, dass wir den ersten Versuch unternommen haben, den Speicherort des letzten Vorkommens von Apple zu finden.
Da wir die Datei zweimal lesen, hängt die Leistung von der Größe der Datei ab, ist aber besser als die Kombination aus sed und tac, da wir die Datei nicht zweimal umkehren müssen.
Perl
perl -sne '$t+=1;$l=$. if /Apple/ and $.==$t;
if($t!=$.){if($.==$l){print $_ . $n . "\n";}else{print $_}};
close ARGV if eof;' -- -n="AFTER LAST APPLE" input.txt input.txt
Die Perl-Lösung folgt derselben Idee wie die AWK-Lösung. Wir übergeben die Eingabe zweimal und verwenden die erste Dateiübergabe, um herauszufinden, wo Apple zuletzt vorkam, und speichern die Zeilennummer in einer $l
Variable. Der Unterschied zu AWK besteht darin, dass es FNR
in Perl keine Variable gibt, daher müssen wir zu diesem Zweck $t+=1
und verwenden close ARGV if eof
.
Der Unterschied besteht darin, dass -s
Perl mit switch die Übergabe von Argumenten erlaubt. In diesem Fall wird der String, den wir einfügen möchten, über -n
switch übergeben.
Alternative Perl
Hier ist noch eine weitere Möglichkeit, dies in Perl zu tun. Die Idee besteht darin, die Datei einfach in ein Zeilenarray einzulesen und dieses Array umzukehren. Die erste Übereinstimmung im umgekehrten Array ist die letzte in der ursprünglichen Zeilenliste. Daher können wir vor dieser Zeile einen gewünschten Text einfügen und die Liste erneut umkehren:
$ perl -le '@a1=<>;END{do{push @a2,"NEW LINE\n" and $f=1 if $_ =~ /Apple/ and !$f; push @a2,$_} for reverse(@a1); print reverse(@a2)}' input.txt
Apple
Banana
Apple
Orange
Apple
NEW LINE
something else
something other entirely
blah