Suchen und ersetzen in Datei: Speicherzugriffsfehler
ich
- perl
0 Hopsel0 迪拉斯
Tach!
Ich habe ein Problem mit perl -p -e 's/); /);\n/g' dummy
Da dummy zwischen 6 und 7 Gigabyte groß ist führt das zu der Medlung "Speicherzugriffsfehler". dummy ist übrigens der echte Name. Den habe ich so gewählt, weil es den Inhalt in der Form nur temporär gibt.
In dummy gibt es gegenwärtig keine Zeilenumbrüche und es ist alles eine Zeile. Das möchte ich damit ändern.
Ich suche nach Alternativen, die sich an der Dateigröße nicht stören.
MfG
ich
Hi ich!
Ich suche nach Alternativen, die sich an der Dateigröße nicht stören.
Ich kenne mich mit Perl ja nun überhaupt nicht aus. :-)
Aber vielleicht wäre es eine Möglichkeit, immer nur eine bestimmte Anzahl an Zeichen (ein bis zwei Milliönchen) herauszupicken, bestimmte Zeichen zu ersetzen, in eine neue Datei schreiben, und bei der alten fortfahren, bis das Dateiende erreicht ist.
MfG H☼psel
Tach.
Ich kenne mich mit Perl ja nun überhaupt nicht aus. :-)
Ich auch nicht wirklich. Ich brauche nur einen Befehl, den man da an der Komandozeile eingeben kann - so wie den, den ich probiert habe. Im Grunde genommen ist es egal, ob dazu Perl oder etwas anderes genutzt wird. Hauptsache ich bekomme da statt Leerzeichen einen Zeilenumbruch hinter jede ");" folge.
Das Thema habe ich aber bei Perl einsortiert, weil ich es damit versucht habe.
MfG
ich
Hell-O!
Ich brauche nur einen Befehl, den man da an der Komandozeile eingeben kann - so wie den, den ich probiert habe.
Mit einem Befehl ist es nicht getan, du brauchst ein Script, das deine Datei block- bzw. zeichenweise einliest und wegschreibt. Wird ein bestimmtes Muster gefunden, dann wird nicht das gelesene sondern ein Zeilenumbruch weggeschrieben. Mal so runtergetippt:
my $source = $ARGV[0];
my $target = $ARGV[1];
my $char = '';
open IN, $source;
open OUT, ">$target";
flock OUT, LOCK_EX;
while(!eof(IN)) {
# naechstes Zeichen lesen
$char .= getc(IN);
print STDOUT "$char\n";
# naechster Durchlauf, wenn ) oder ; am Ende stehen
next if $char =~ /[);]$/;
# Suchen und Ersetzen
$char =~ s/\); /\);\n/;
# Wegschreiben
print OUT $char;
# $char leeren
$char = '';
}
close(IN);
close(OUT);
So müsste es funktionieren.
Siechfred
Tach.
So müsste es funktionieren.
Ja, das läuft. Aber es ist viel zu langsam um praxistauglich zu sein 10 MB in 5 Minuten...
Das würde einige Tage laufen.
Tach.
So müsste es funktionieren.
Ja, das läuft. Aber es ist viel zu langsam um praxistauglich zu sein 10 MB in 5 Minuten...
Habe auch mal die Bildschirmausgabe der gelesenen Zeichen rausgenommen - etwa 80 MB in 2 Minuten - schon besser...
Hattest Du das da zum debuggen eingebaut? Ich hab's nicht gleich gesehen.
Hell-O!
Habe auch mal die Bildschirmausgabe der gelesenen Zeichen rausgenommen - etwa 80 MB in 2 Minuten - schon besser...
Hattest Du das da zum debuggen eingebaut? Ich hab's nicht gleich gesehen.
Ja, das hatte ich zum Debuggen eingebaut und bei C&P vergessen, rauszulöschen.
Siechfred
Also mein Angebot steht noch.
Meine Lösung braucht auf einem alten PC für 2GB 10-15 min incl. Dateinbankeinträge und umfangreichen Suchmethoden.
Da ich Deine Mail-Adresse nicht habe, kann ich auch nix verschicken.
gruß
David
Hell-O!
Meine Lösung braucht auf einem alten PC für 2GB 10-15 min incl. Dateinbankeinträge und umfangreichen Suchmethoden.
Magst du sie uns zeigen? Man lernt schließlich nie aus.
Siechfred
Der Kram ist zu umfangreich.
Ich kanns gerne an jeden verschicken, der mir seine Mail gibt.
Gruß
David
Also, der Ansatz von Hopsel ist bei der Dateigröße wichtig, da sonst wahrscheinlich versucht wird die gesamte Datei in den Speicher einzulesen.
Ich habe dazu in PHP mal eine streamingfähige Funktion geschrieben, die die Datei immer nur Blockweise einliest. Eine Änderung der Daten habe ich nicht implementiert, da ich meine Dateien nur auslesen wollte. Ich habe das mit Dateien getestet, die ca. 2 GB groß waren (also wesentlich größer als mein damaliger Speicher).
Vermutlich ändert sich bei Dir die Dateigröße (da Du ja Leerzeichen einfügen willst). Deswegen würde ich die alte Datei nicht überschreiben, sondern (falls Du noch HDD-Platz hast) eine neue Schreiben, wo die entspr. Manipulationen durchgeführt wurden. Ansonsten müßte meine Funktion geändert werden, da sich die Zeiger sonst mit jedem eingefügten Zeichen zusätzlich verschieben müssen.
Du kannst die kommentierten Funktionen mit Kommentaren bei mir per Mail anfordern, Dateien mit Testaufrufen habe ich ebenfalls.
Die Dateien sind zu umfangreich, um sie hier zu posten.
Ich würde übrigens empfehlen, die Datei zu splitten oder in einer anderen Form zu speichern, da 10GB allgemein schwer zu handeln sind.
Ich habe meine Daten damals mit obigen Funktionen in einer Datenbank registriert und wusste dann zumindest wo ich nach bestimmten Dingen suchen muss. Der Einsatz der Funktionen ist aber Variabel und kann auch anders eingesetzt werden.
Gruß
David
Hell-O!
Also, der Ansatz von Hopsel ist bei der Dateigröße wichtig, da sonst wahrscheinlich versucht wird die gesamte Datei in den Speicher einzulesen.
Ja, genau das ist das Problem.
Ich habe dazu in PHP mal eine streamingfähige Funktion geschrieben, die die Datei immer nur Blockweise einliest.
Was, wenn die Zeichenfolge "); " beim blockweisen Lesen zerschnitten wird?
Eine Änderung der Daten habe ich nicht implementiert, da ich meine Dateien nur auslesen wollte.
Aber genau das ist hier der Knackpunkt, weshalb blockweises Einlesen der Datei zu unerwünschten Effekten führen kann.
Siechfred
stimmt.
Meine Funktion bedenkt das.
Gruß
David
Meine Funktion bedenkt das.
Und in welcher Form?
Siechfred
Hallo Sechfred,
auszug aus meinem Skript:
// Butterfly-Funktionalität:
// Bei Übergabe von 2 $buffern wird die Grenze des ersten soweit überschritten wie
// strlen($newcasearray)-1 damit auch der Grenzbereich untersucht wird
Tut mir leid, aber die Veröffentlichung hier scheitert am Umfang.
Ich hatte es getestet.
Gruß
David
// Bei Übergabe von 2 $buffern wird die Grenze des ersten soweit überschritten wie
// strlen($newcasearray)-1 damit auch der Grenzbereich untersucht wird
Ah, verstehe. Letztlich macht mein Vorschlag allerdings auch nichts anderes, nur dass er zeichenweise statt blockweise einliest.
Siechfred
Hallo Siechfred,
// Bei Übergabe von 2 $buffern wird die Grenze des ersten soweit überschritten wie
// strlen($newcasearray)-1 damit auch der Grenzbereich untersucht wirdAh, verstehe. Letztlich macht mein Vorschlag allerdings auch nichts anderes, nur dass er zeichenweise statt blockweise einliest.
auch wenn normalerweise aus dem Cache gelesen werden sollte, kann das blockweise Einlesen die Geschwindigkeit der Gesamtoperation möglicherweise deutlich steigern. In der O-Notation sind beide Verfahren sicher gleich schnell - aber auch ein konstanter Faktor wäre hier wünschenswert. Viel umfangreicher sollte Dein Skript dennoch nicht werden.
Freundliche Grüße
Vinzenz
stimmt, zeichenweises einlesen dauert sehr lange.
strlen($newcasearray) muss immer so lang sein wie der jeweilige Suchbegriff. Da dann 1 subtrahiert wird, besteht keine Gefahr, daß der gleiche Begriff zweimal an der gleichen Stelle gefunden wird.
Ich habe eine Suchfunktion wo ich auch Arrays übergeben kann und wo die Länge des Überschneidenden Bereichs abhängig von der des Suchbegriffs bestimmt wird.
Gruß
David
Hey,
den Speicherzugriffsfehler erhältst du, weil du tatsächlich alles in ein $_
einzulesen versuchst. Siehe http://perldoc.perl.org/perlrun.html#*-p*--p, das -p wird zu einem while
mit <>
expandiert. Bekanntlich ist <>
nur die Kurzform für $_ = readline(*STDIN);
.
Schau dir mal den Schalter -0 an. Gib ihm den oktalen Wert für ), nämlich 051. Jetzt wird ) als Zeilenende interpretiert. Wenn dann am Zeilenanfang ; und Leerzeichen steht, ersetze es mit ; und \n.
perl -0051 -pe's/^; /;\n/' dummy
Diese Lösung über chunkweises Einlesen wird nochmals schneller sein als die vorher gepostete über zeichenweises Einlesen.
Hell-O!
Schau dir mal den Schalter -0 an. Gib ihm den oktalen Wert für ), nämlich 051. Jetzt wird ) als Zeilenende interpretiert. Wenn dann am Zeilenanfang ; und Leerzeichen steht, ersetze es mit ; und \n.
*vor die Stirn klatsch* klar, der Input record separator. An das Naheliegendste denkt man oft nicht :-)
Das bringt mich doch noch auf die Idee, meinen Vorschlag ein wenig umzubauen, denn nicht jeder kann/darf/will Perl auf der Kommandozeile ausführen:
local $/ = '); ';
open IN, $source;
open OUT, ">$target";
while(<IN>) {
$_ =~ s/\); /\);\n/;
print OUT $_;
}
close(IN);
close(OUT);
Das dürfte m.E. die mit Abstand schnellste Script-Variante werden.
Siechfred
local $/ = '); ';
open IN, $source;
open OUT, ">$target";
while(<IN>) {
$_ =~ s/); /);\n/;
print OUT $_;
}
close(IN);
close(OUT);
>
> Das dürfte m.E. die mit Abstand schnellste Script-Variante werden.
Die RegEx brauchst du nicht, da das Zeilentrennzeichen ja mit eingelesen wird (du kannst es aber mit chomp() entfernen).
~~~perl
local $/ = '); ';
open IN, $source;
open OUT, ">$target";
while(<IN>)
{
print OUT "$_\n";
}
close(IN);
close(OUT);
Das dürfte nochmal eine ganze Menge bringen, da RegExen langsam sind
Struppi.
Hell-O!
Die RegEx brauchst du nicht, da das Zeilentrennzeichen ja mit eingelesen wird (du kannst es aber mit chomp() entfernen).
Hm, die bessere Wahl wäre m.E. chop, da aus dem Leerzeichen ja ein Newline werden soll, und das Leerzeichen wird immer am Ende der Zeile stehen. Deine Variante belässt das Leerzeichen dort, wo es ist, ich denke, dass das der OP so nicht möchte.
Das dürfte nochmal eine ganze Menge bringen, da RegExen langsam sind
Da hast du ohne Zweifel Recht.
Siechfred
Die RegEx brauchst du nicht, da das Zeilentrennzeichen ja mit eingelesen wird (du kannst es aber mit chomp() entfernen).
Hm, die bessere Wahl wäre m.E. chop, da aus dem Leerzeichen ja ein Newline werden soll, und das Leerzeichen wird immer am Ende der Zeile stehen. Deine Variante belässt das Leerzeichen dort, wo es ist, ich denke, dass das der OP so nicht möchte.
Ach so, ich hatte den etwas ausgeuferten Thread nur überflogen, ja dann dürfte chop richtiger und sinnvoller sein.
Struppi.