Discussion:
Ganze Zeilen in einer Textdatei ersetzen?
(zu alt für eine Antwort)
Klaus Fischer
2004-08-06 06:05:53 UTC
Permalink
Hi,

Ich habe folgende (funktionierende) procedure die es mir ermöglicht,
einzelne Textpassagen in einer Textdatei zu ersetzen:


procedure FileReplaceString(const FileName, searchstring,
replacestring: string);
var
fs: TFileStream;
S: string;
begin
fs := TFileStream.Create(FileName, fmOpenread or fmShareDenyNone);
try
SetLength(S, fs.Size);
fs.ReadBuffer(S[1], fs.Size);
finally
fs.Free;
end;
S := StringReplace(S, SearchString, replaceString, [rfReplaceAll, rfIgnoreCase]);
fs := TFileStream.Create(FileName, fmCreate);
try
fs.WriteBuffer(S[1], Length(S));
finally
fs.Free;
end;
end;

Diese funktioniert sehr gut, wenn ich einzelne Textpassagen ersetzen
will deren Inhalt ich als Suchbegriff kenne. Ich suche nun aber eine
Möglichkeit, ganze Zeilen in einer Textdatei zu ersetzen (bei deren
ich nicht den gesamten Such-Text kenne). Wie muß man o. g. Procedure
ändern, damit dies ermöglicht wird?

Ich habe bei TFileStream keine Methode entdeckt, wie ich an die
Zeilen-Nr. komme..?

So long,
Klaus
--
Computer sind nicht intelligent. Sie denken nur, dass sie es sind.
Ralf Kaiser
2004-08-06 06:53:46 UTC
Permalink
"Klaus Fischer" <k-***@expires-2004-08-31.arcornews.de> schrieb im
Newsbeitrag news:***@indus.dyndns.org...

Hallo,
Post by Klaus Fischer
Ich habe folgende (funktionierende) procedure die es mir ermöglicht,
[snip]
Post by Klaus Fischer
Ich habe bei TFileStream keine Methode entdeckt, wie ich an die
Zeilen-Nr. komme..?
Primitivlösung:

Lade den Text in eine TStringList, durchsuche diese, wenn der Text in einer
Zeile gefunden wurde dann ersetze diese Zeile in der TStringlist und
speichere sie wieder ab.

Ciao,
Ralf
Klaus Fischer
2004-08-06 12:22:53 UTC
Permalink
[Ersetze Zeile anhand eines "Teil-Suchstringes"]
Post by Ralf Kaiser
Lade den Text in eine TStringList, durchsuche diese, wenn der Text in einer
Zeile gefunden wurde dann ersetze diese Zeile in der TStringlist und
speichere sie wieder ab.
Thx, habe mir folg. gebastelt und es funktioniert soweit:

procedure ReplaceLine(cFile:string; str1, str2:string);
var sl:TStringList;
zPos:integer;
begin
sl := TStringList.Create;
try
sl.LoadFromFile(cFile);
sl.Sort;
sl.Find(str1, zPos);
sl.Delete(zPos);
sl.Insert(zPos,str2);
finally;
sl.SaveToFile(cFile);
sl.Free;
end;
end;

Allerdings leider trotzdem unbrauchbar, da ich die Textdatei zuerst
sortieren muss um Find/Insert zu benutzen. Es handelt sich um eine
.cfg-Datei mit Kommentaren bei der dann als Endergebnis alles wild
durcheinandergewürfelt wird. Ein sl.Unsort gibt es ja nicht.. ;)

So long,
Klaus
--
"..Geruechten zufolge, erscheint Win2000 erst im Sommer 1900"
Ralf Kaiser
2004-08-06 12:49:11 UTC
Permalink
Post by Klaus Fischer
Allerdings leider trotzdem unbrauchbar, da ich die Textdatei zuerst
sortieren muss um Find/Insert zu benutzen. Es handelt sich um eine
.cfg-Datei mit Kommentaren bei der dann als Endergebnis alles wild
durcheinandergewürfelt wird. Ein sl.Unsort gibt es ja nicht.. ;)
Hallo,

anstatt mit "Find" zu arbeiten, das ja eine sortierte Stringlist
voraussetzt, kann due IndexOf() verwenden (ganz abgesehen davon, daß
"Insert" bei sortierten Listen eine Exception wirft!)

Um ein "Unsort" zu erhalten könnte man den unsprünglichen Index der Einträge
(vor dem Sortieren) mittels des Property "Objects" an die Zeilen hängen.
Dabei entweder den Indexwert in ein Objekt "verpacken" oder einfach den
integer zu TObject casten und dem Property Objects zuweisen. Nach der
Sortierung kann dann mittels CustomSort nochmals sortiert werden und dabei
der angehängte Index als Sortierkriterium benutzt werden

Aber es sollte wohl besser sein einfach statt Find IndexOf zu verwenden :-),
so ein "Unsort" kommt mir doch etwas abenteuerlich vor.

Ciao,
Ralf
Klaus Fischer
2004-08-07 06:18:02 UTC
Permalink
* Ralf Kaiser schrieb:

Hi,
Post by Ralf Kaiser
anstatt mit "Find" zu arbeiten, das ja eine sortierte Stringlist
voraussetzt, kann due IndexOf() verwenden (ganz abgesehen davon, daß
"Insert" bei sortierten Listen eine Exception wirft!)
[sl.Unsort - Verrenkungen :-)]
Post by Ralf Kaiser
Aber es sollte wohl besser sein einfach statt Find IndexOf zu verwenden :-),
so ein "Unsort" kommt mir doch etwas abenteuerlich vor.
Es scheint (wie auch in der OH beschrieben), dass IndexOf einen
vollständigen Suchstring voraussetzt. Ich möchte aber nur einen
Teil-Suchstring verwenden (da ich den genauen Inhalt *nicht* kenne)
Infolgedessen funktioniert IndexOf nicht bei folg. Verwendung:

Datei: c:\temp\test.cfg
Wert in einer Zeile der Datei: // test-alt

ReplaceLine('c:\temp\test.cfg','// test', '// test-neu');


procedure ReplaceLine(cFile:string; fSearch, fInsert:string);
var sl:TStringList;
zPos:integer;
begin
sl := TStringList.Create;
try
with sl do
begin
LoadFromFile(cFile);
zPos := IndexOf(fSearch);
if zPos > -1 then
begin
Delete(zPos);
Insert(zPos,fInsert);
SaveToFile(cFile);
end;
end;
finally;
sl.Free;
end;
end;

So long,
Klaus
--
Wenn Linux mal ohne Console auskommt und SuSE mit AOL ausgeliefert
wird, werde ich Gärtner. (Manuel Stürz in de.org.ccc)
Arne 'Timwi' Heizmann
2004-08-06 13:46:45 UTC
Permalink
Post by Klaus Fischer
procedure ReplaceLine(cFile:string; str1, str2:string);
var sl:TStringList;
zPos:integer;
begin
sl := TStringList.Create;
try
sl.LoadFromFile(cFile);
sl.Sort;
sl.Find(str1, zPos);
sl.Delete(zPos);
sl.Insert(zPos,str2);
finally;
sl.SaveToFile(cFile);
sl.Free;
end;
end;
Das hat jetzt mit deinem spezifischen Problem nichts zu tun, aber ich
würde an deiner Stelle das "SaveToFile" *vor* das "finally" setzen.
Der finally-Block wird aufgerufen, wenn eine Exception auftritt; du
willst doch nicht etwa eine potentiell kaputte StringList abspeichern.
Außerdem könnte beim Abspeichern auch eine Exception auftreten, und wenn
das passiert, wird sl nicht mehr freigegeben.

Timwi
Klaus Fischer
2004-08-07 06:29:42 UTC
Permalink
Hi,
Post by Arne 'Timwi' Heizmann
Post by Klaus Fischer
finally;
sl.SaveToFile(cFile);
sl.Free;
Das hat jetzt mit deinem spezifischen Problem nichts zu tun, aber ich
würde an deiner Stelle das "SaveToFile" *vor* das "finally" setzen.
[..]

Danke, verinnerlicht und hiermit geändert. ;)

So long,
Klaus
--
Der Hamster, das einzig wahre Nutztier neben der Maus
(Daniel Zimmermann in hamster.de.talk)
schultschik
2004-08-07 06:05:12 UTC
Permalink
Hallo Klaus,
Post by Klaus Fischer
Hi,
Ich habe folgende (funktionierende) procedure die es mir ermöglicht,
procedure FileReplaceString(const FileName, searchstring,
replacestring: string);
var
fs: TFileStream;
S: string;
begin
fs := TFileStream.Create(FileName, fmOpenread or fmShareDenyNone);
try
SetLength(S, fs.Size);
fs.ReadBuffer(S[1], fs.Size);
finally
fs.Free;
end;
S := StringReplace(S, SearchString, replaceString, [rfReplaceAll, rfIgnoreCase]);
fs := TFileStream.Create(FileName, fmCreate);
try
fs.WriteBuffer(S[1], Length(S));
finally
fs.Free;
end;
end;
Diese funktioniert sehr gut, wenn ich einzelne Textpassagen ersetzen
will deren Inhalt ich als Suchbegriff kenne. Ich suche nun aber eine
Möglichkeit, ganze Zeilen in einer Textdatei zu ersetzen (bei deren
ich nicht den gesamten Such-Text kenne). Wie muß man o. g. Procedure
ändern, damit dies ermöglicht wird?
Ich habe bei TFileStream keine Methode entdeckt, wie ich an die
Zeilen-Nr. komme..?
Mit folgender Lösung sollte es funktionieren:

procedure FileReplaceString(const FileName, searchstring, replacestring:
string);
var
buffer : TStringList;
LineNr : Integer;
begin
Buffer := TStringList.Create;
try
Buffer.LoadFromFile(Filename);
For LineNr := 0 to Buffer.Count-1 do
Buffer.Strings[LineNr] := StringReplace(Buffer.Strings[LineNr],
SearchString, replaceString, [rfReplaceAll, rfIgnoreCase]);
Buffer.SaveToFile(FilenName);
finally
Buffer.Destroy;
end;
end;

Andreas
Klaus Fischer
2004-08-07 11:29:38 UTC
Permalink
* schultschik schrieb:

Hi,
Post by Klaus Fischer
Ich habe bei TFileStream keine Methode entdeckt, wie ich an die
Zeilen-Nr. komme..?
[..]

Sorry, leider auch nicht brauchbar.

Testdatei "c:\temp\test.cfg"

Inhalt der Datei:
..
..
seta name "test"
..
..

FileReplaceString('c:\temp\test.cfg','seta name ',
'seta name "neu"');

ergibt in Ergebnis:

..
seta name "neu""test"
..

Nochmal: Das entscheidende soll sein, dass ich nur einen
Teil-Suchstring als "searchstring" benutze, wobei dann der
"replacestring" die ganze Zeile komplett ersetzen soll.

So long,
Klaus
--
Ich habe Zone Alarm in Hintergrund mitlaufen, das ist fuer mich Pflicht.
ich habe eine Schüssel Weihwasser neben der Tastatur stehen. Das
hilft genauso. [Jürgen Schmadlak zu Werner Fuhrman in dcsn]
schultschik Andreas
2004-08-08 05:34:57 UTC
Permalink
Hallo Klaus,
Post by Klaus Fischer
Nochmal: Das entscheidende soll sein, dass ich nur einen
Teil-Suchstring als "searchstring" benutze, wobei dann der
">replacestring" die ganze Zeile komplett ersetzen soll.

Dann hast du den falschen Befehl verwendet. StringReplace ersetzt immer in
einem String
den gefunden Teilstring durch einen anderen Teilstring, jedoch niemals den
ganzen String!
Post by Klaus Fischer
Buffer.Strings[LineNr] := StringReplace(Buffer.Strings[LineNr],
SearchString, replaceString, [rfReplaceAll, rfIgnoreCase]);
Gut, dann sieht das ganze so aus:

procedure FileReplaceString(const FileName, searchstring, replacestring:
string);
var
buffer : TStringList;
LineNr : Integer;
str, strcomp : string; //<<< Neu
begin
Buffer := TStringList.Create;
try
Buffer.LoadFromFile(Filename);
str := Uppercase(searchstring); //<<<Neu
For LineNr := 0 to Buffer.Count-1 do
begin //<<<Neu
Buffer.Strings[LineNr] := StringReplace(Buffer.Strings[LineNr],
//<<<Löschen
SearchString, replaceString, [rfReplaceAll, rfIgnoreCase]); //<<<Löschen
strcomp := Uppercase(Buffer.Strings[LineNr]); //<<<Neu
if pos(str,strcomp) > 0 then Buffer.Strings[LineNr] :=
ReplaceString; //<<<Neu
end; //<<<Neu
Buffer.SaveToFile(FilenName);
finally
Buffer.Destroy;
end;
end;

Die beiden Uppercase (Suchstring und auch einzelne Zeilen) brauchst du
deshalb, da
"Pos" die Groß/Kleinschreibung nicht ignoriert. Die Funktion "Pos" liefert
das erste
Auftreten eines Teilstrings innerhalb der Zeile. Hast du einen gefunden
(Position größer 0), so wird die
ganze Zeile ersetzt.

Willst du auch Widechars verarbeiten oder sollen Sonderzeichen richtig
erkannt werden,
musst du Funktion "Pos" gegen "AnsiPos" ersetzt werden. Die Parameter sind
bei
beiden Funktionen gleich.

Die Klasse TStringList dient nur für die Bufferung der Daten als einzelne
Zeilen. Der
Import und Export in die Datei wird durch diese Klasse automatisch
abgewickelt.

Andreas
Klaus Fischer
2004-08-08 10:58:28 UTC
Permalink
* schultschik Andreas schrieb:

[..]
Post by schultschik Andreas
Dann hast du den falschen Befehl verwendet. StringReplace ersetzt immer in
einem String
ok., inzwischen ist mir das auch klar geworden.
[..]

thx., auch deine Version überstand meine Tests klaglos. Da fällt die
Auswahl schwer.. ;9
Post by schultschik Andreas
Auftreten eines Teilstrings innerhalb der Zeile. Hast du einen gefunden
(Position größer 0), so wird die
ganze Zeile ersetzt.
ACK.
Post by schultschik Andreas
Willst du auch Widechars verarbeiten oder sollen Sonderzeichen richtig
erkannt werden,
musst du Funktion "Pos" gegen "AnsiPos" ersetzt werden. Die Parameter sind
bei
beiden Funktionen gleich.
Danke, gut zu wissen, dass es AnsiPos auch gibt. Wird aber im Moment
(noch) nicht benötigt.

So long,
Klaus
--
T-Online, aus Freude am zahlen!
Klaus Fischer
2004-08-07 11:56:23 UTC
Permalink
* Klaus Fischer schrieb:

[String bzw. Zeile ersetzen bei Eingabe eines Teil-Suchstringes]
Post by Klaus Fischer
Ich habe bei TFileStream keine Methode entdeckt, wie ich an die
Zeilen-Nr. komme..?
Für alle die es interessiert, ich habe jetzt mit Heraufsetzung
meiner brach liegenden Gehirnwindungen :) die Lösung gefunden:

procedure ReplaceLine(cFile:string; fSearch, fInsert:string);
var sl:TStringList;
zPos:integer;
i:integer;
begin
sl := TSTringList.Create;
try
with sl do
begin
LoadFromFile(cFile);
for i:=0 to Count-1 do
if Copy(Strings[i],0,Length(fSearch)) = fSearch then
begin
Delete(i);
Insert(i,fInsert);
SaveToFile(cFile);
end;
end;
finally;
sl.Free;
end;
end;

So long,
Klaus
--
Um Rekursion zu verstehen, muss man erst mal Rekursion verstehen.
Michael Winter
2004-08-07 19:04:59 UTC
Permalink
Post by Klaus Fischer
if Copy(Strings[i],0,Length(fSearch)) = fSearch then
Der minimale Startwert von Copy ist 1, aber die Funktion korrigiert deinen
Fehler.

Abgesehen davon ist der Vergleich case-sensitiv und klappt nur, wenn die
Zeile mit dem Suchstring beginnt. In beiden folgenden Fällen würde bei
einer Suche nach 'bar' keine Ersetzung stattfinden:

Bar
foo bar

Aber vielleicht willst du das so und hast es im OP nur nicht zumAusdruck
gebracht.

Statt Löschen und Einfügen
Post by Klaus Fischer
begin
Delete(i);
Insert(i,fInsert);
ist jedenfalls Ersetzen in der Stringliste einfacher und deutlicher sowie
das Speichern
Post by Klaus Fischer
SaveToFile(cFile);
end;
nach jedem einzelnen Ersetzen (von potenziell mehreren) unnötig.

Meine Version sähe so aus (ungetestet, case-sensitiv und der Suchstring
muss am Zeilenanfang stehen wie in deiner Version):

procedure ReplaceLine(const cFile, fSearch, fInsert: String);
var
sl: TStringList;
i: integer;
Modified: Boolean;
begin
Modified := false;
sl := TSTringList.Create;
try
sl.LoadFromFile(cFile);
for i := 0 to sl.Count - 1 do begin
if Copy(sl[i], 1, Length(fSearch)) = fSearch then begin
sl[i] := fInsert;
Modified := true;
end;
end;
if Modified then
sl.SaveToFile(cFile);
finally;
sl.Free;
end;
end;

-Michael
Klaus Fischer
2004-08-08 06:23:25 UTC
Permalink
Hi Michael,
Post by Michael Winter
Der minimale Startwert von Copy ist 1, aber die Funktion korrigiert deinen
Fehler.
Opps, Glück gehabt.
Post by Michael Winter
Abgesehen davon ist der Vergleich case-sensitiv und klappt nur, wenn die
Zeile mit dem Suchstring beginnt. In beiden folgenden Fällen würde bei
Ich wollte eigentlich schon case-insensitiv..
Post by Michael Winter
Aber vielleicht willst du das so und hast es im OP nur nicht zumAusdruck
gebracht.
Am Anfang der Zeile genügt mir eigentlich. Obwohl die Routine
deutlich universeller wäre, wenn der Gesamtstring bearbeitet würde..
Post by Michael Winter
Statt Löschen und Einfügen
sl[i] := fInsert;
Post by Michael Winter
Post by Klaus Fischer
SaveToFile(cFile);
end;
Das ist IMO deutlich eleganter, vor allen Dingen mit der 'Modified'
variable, thx. Obwohl ich mich etwas wundere, warum nicht sl.Text[i]
statt sl[i] benötigt wird. 'sl' ist doch "nur" das "Gesamtobjekt"?
Post by Michael Winter
nach jedem einzelnen Ersetzen (von potenziell mehreren) unnötig.
Der Wert kommt in der Regel nur einmal im file vor.
Post by Michael Winter
Meine Version sähe so aus (ungetestet, case-sensitiv und der Suchstring
Um case-insensitiv zu erreichen, habe ich deinen Code noch
folgendermaßen geändert:

procedure ReplaceLine(const cFile: String; fSearch, fInsert: String);
..
try
sl.LoadFromFile(cFile);
fSearch := LowerCase(fSearch);
for i:=0 to sl.Count-1 do
begin
if LowerCase(Copy(sl[i], 1, Length(fSearch))) = fSearch then
..

Getestet und funktioniert bestens.

So long,
Klaus
--
Computer dienen uns zur Lösung von Problemen
die wir ohne sie nicht hätten.
Thomas G. Liesner
2004-08-08 09:40:24 UTC
Permalink
Post by Klaus Fischer
Das ist IMO deutlich eleganter, vor allen Dingen mit der 'Modified'
variable, thx. Obwohl ich mich etwas wundere, warum nicht sl.Text[i]
statt sl[i] benötigt wird. 'sl' ist doch "nur" das "Gesamtobjekt"?
Schau mal in die Hilfe unter dem Stichwort "default". Nebenbei wäre es
sl.lines[i], sl.text ist ein anderes Property, sl.text[i] gibt dir das
ite *Zeichen* von allen in Stringlist vorhandenen und mir #13#10
zusammengefügten Lines.

So long,
Thomas G. Liesner
--
Hamster beta V2.0.5.5 seit 01.06.2004 verfügbar
http://tglsoft.de/ bzw. http://hamster.arcornews.de/tgl/
Klaus Fischer
2004-08-08 16:16:03 UTC
Permalink
[sl[i] statt sl.text[i]]
Post by Thomas G. Liesner
Schau mal in die Hilfe unter dem Stichwort "default". Nebenbei wäre es
sl.lines[i], sl.text ist ein anderes Property, sl.text[i] gibt dir das
ite *Zeichen* von allen in Stringlist vorhandenen und mir #13#10
zusammengefügten Lines.
Aja Danke, hab ich verwexelt ;)

So long,
Klaus
--
Downgrade your system for only 89 dollars! Install Windows!
Holger Schieferdecker
2004-08-09 08:55:27 UTC
Permalink
Klaus Fischer says...
Post by Klaus Fischer
Um case-insensitiv zu erreichen, habe ich deinen Code noch
procedure ReplaceLine(const cFile: String; fSearch, fInsert: String);
..
try
sl.LoadFromFile(cFile);
fSearch := LowerCase(fSearch);
for i:=0 to sl.Count-1 do
begin
if LowerCase(Copy(sl[i], 1, Length(fSearch))) = fSearch then
Wenn Deine strings auch Umlaute enthalten können, solltest Du besser
AnsiLowerCase verwenden.

Gruß
Holger
--
Gerechtigkeit ohne Liebe macht hart.
Glaube ohne Liebe macht fanatisch.
Macht ohne Liebe macht gewalttätig.
Pflicht ohne Liebe macht verdrießlich.
Ordnung ohne Liebe macht kleinlich.
Klaus Fischer
2004-08-09 11:41:43 UTC
Permalink
Post by Holger Schieferdecker
Klaus Fischer says...
[..]
Post by Holger Schieferdecker
Wenn Deine strings auch Umlaute enthalten können, solltest Du besser
AnsiLowerCase verwenden.
Da betreffende text files (.cfg) rein Englischsprachig sind
(höchstens Umlaute in meinen Kommentaren), benötige ich
AnsiLowerCase nicht. Trotzdem Danke für den Hinweis.

So long,
Klaus
--
seit ca. 3 Monaten muss ich feststellen das meine pop3 Eintragungen
geändert werden(Outlook2000) . Auch das formatieren der Festplatte
half nicht. Im pop3 steht dann folgende IP 127.0.0.1 wer kennt die
schweinebande bzw. wer weis abhilfe. [eu-surf in de.comp.security]
Klaus Fischer
2004-08-09 12:26:02 UTC
Permalink
Post by Klaus Fischer
Hi,
Ich habe folgende (funktionierende) procedure die es mir ermöglicht,
So, dank den vielen Tipps hier, habe ich hier meine finale Version.
(AnsiPos und AnsiLowerCase habe ich jetzt mal trotzdem verwendet);):

procedure FileReplaceString(const FileName, searchstring, replacestring: string);
var
sl: TStringList;
LineNr: Integer;
Modified: Boolean;
str, strcomp: string;
begin
Modified := false;
sl := TStringList.Create;
try
sl.LoadFromFile(Filename);
str := AnsiLowerCase(searchstring);
For LineNr:=0 to sl.Count-1 do
begin
strcomp := AnsiLowerCase(sl.Strings[LineNr]);
if AnsiPos(str,strcomp) > 0 then
sl.Strings[LineNr] := ReplaceString;
Modified := true;
end;
if Modified then
sl.SaveToFile(FileName);
finally
sl.Destroy;
end;
end;

Den "Zuschlag" dass ich mich für diese procedure entschieden habe,
war der Umstand, dass ich nun auch aus der "Mitte" der Textzeile
ersetzen/suchen kann. Danke nochmal an Andreas und den anderen.

So long,
Klaus
--
_Was_ Du schreibst ist egal, aber ich finde das Layout Deines
Postings hervorragend! (Georg Siegemund)
Lesen Sie weiter auf narkive:
Suchergebnisse für 'Ganze Zeilen in einer Textdatei ersetzen?' (Fragen und Antworten)
4
Antworten
Externe festplatte wird nicht mehr erkannt?
gestartet 2010-08-25 12:01:05 UTC
hardware
Loading...