hawkmaster: Bereich in TXT Datei ändern?

Hallo zusammen,
ich möchte via PHP eine große Textdatei ändern.
Was ich hinbekomme:
ich lade die ganze TXT Datei in eine Textarea, ändere die entsprechenden Zeilen und speicher dann die Textarea wieder in der Datei.
Soweit ok.
Da die Datei aber sehr viele Zeilen hat und ich nur einen bestimmten, kleinen Bereich ändern möchte versuche ich nun nur den einen Bereich anzuzeigen und nur diesen zu ändern.

Die Anzeige bzw. das "Ausschneiden" des Bereichs klappt schon mal.
Das habe ich mit "preg_match" hinbekommen.
-----------------------------
*OpenUI *InputSlot: PickOne
*OrderDependency: 30 AnySetup *InputSlot
*DefaultInputSlot: 1Tray
*InputSlot 1Tray/Tray 1: "<</MediaPosition 1>> setpagedevice
userdict /RPS_MPdict 1 dict put
/RPS_MP_MEDIAPOSITION 1 def end"
*End
*InputSlot 2Tray/Tray 2: "<</MediaPosition 2>> setpagedevice
userdict /RPS_MPdict 1 dict put
/RPS_MP_MEDIAPOSITION 2 def end"
*End
*CloseUI: *InputSlot

------------------------------

Angenommen man ändert eine Zeile ab und fügt noch drei neue Zeilen ein.
Wie könnte man nun vorgehen um diesen geänderten Bereich mit dem alten auszutauschen und alles wieder in die Datei zu speichern?

vielen Dank und viele Grüße
hawk

  1. Hallo,

    Angenommen man ändert eine Zeile ab und fügt noch drei neue Zeilen ein.
    Wie könnte man nun vorgehen um diesen geänderten Bereich mit dem alten auszutauschen und alles wieder in die Datei zu speichern?

    das Vorgehen ist im Grundprinzip immer wieder dasselbe mit leichten Variationen. Da Textdateien keine feste Satzlänge haben, kann man nicht gezielt bestimmte Bereiche darin überschreiben; erst recht nicht, wenn der neue Text länger oder kürzer als der ersetzte ist.

    Es gilt also immer:
     1. Speichere (kopiere) den unveränderten Teil *vor* dem editierten Block
     2. Schreibe den neuen, geänderten Block
     3. Speichere (kopiere) den unveränderten Teil *nach* dem editierten Block

    So long,
     Martin

    --
    F: Was macht ein Offizier, der in der Nase bohrt?
    A: Er holt das Letzte aus sich heraus.
  2. Hello,

    Angenommen man ändert eine Zeile ab und fügt noch drei neue Zeilen ein.
    Wie könnte man nun vorgehen um diesen geänderten Bereich mit dem alten auszutauschen und alles wieder in die Datei zu speichern?

    Zeilenweise :-))

    Es gibt ja gar keine Zeilen in der Datei. Die sind ja nur fiktiv. Hast Du schon mal eine Festplatte auseinandergenommen? Konntest Du da Zeilen sehen?

    Nun mal ernsthaft. Stell dir die Datei einfach als einen Bindfaden vor mit Knoten drin. Die Stücke von einem Knoten zum nächsten (exclusiv dem vorderen, inclusiv dem hinteren) sind Deine Zeilen.

    Wie würdest Du da nun eine Zeile rausschneiden und zwei neue einfügen?

    Was musst Du wissen?

    • Den Aufsetzpunkt in der Originaldatei als Byteposition, also den Beginn des fraglichen Bereiches.
    • Die Länge in _Bytes_, NICHT in _Zeichen_, des fraglichen Bereiches
        Je nachdem, wie die Datei codiert ist, kann ein Zeichen auch mehr als ein Byte benötigen.
        Du benötigst aber die Byte-Position des Beginns und die Länge in Bytes.

    Spule bis zur Beginnposition vor und schreibe dabei die eingelesenen Zeichen in eine neue Datei. Schreibe nun die neuen Zeichen in die neue Datei. Spule in der Originaldatei weiter bis zur Endeposition des fraglichen Bereiches vor, ohne die alten Zeichen in die neue Datei zu übertragen. Lese nun den Rest der alten Datei ein und übertrage ihn dabei in die neue Datei.

    Abschließend kannst Du die alte Datei in *.bak (oder so ähnlich) umbenennen und der neuen den Namen der alten geben.

    Das Ganze sollte geschützt geschehen, also unter Locking.

    Liebe Grüße aus dem schönen Oberharz

    Tom vom Berg

    --
     ☻_
    /▌
    / \ Nur selber lernen macht schlau
    http://bergpost.annerschbarrich.de
  3. Hallo Tom, Hallo Martin
    vielen Dank für eure Hilfe.
    So ich habe nun mal was "gebastelt".
    Vielleicht hat mal jemand eine ähnliche Anforderung, daher ein Auszug des Codes.

      
    	//gesamte Datei einlesen  
    	$fp = fopen( $source_file, 'r');  
    	$string1 = fread($fp, filesize($source_file));  
    	fclose($fp);  
    	  
    	$startblock = strpos($string1, $begin_area); //Position bis zum Beginn des Bereichs der ausgetauscht werden soll  
    	$endblock = strpos($string1, $end_area); //End Position des Bereichs der ausgetauscht werden soll  
    	$beginreadend = $endblock + strlen($end_area); //Endpostions plus der Länge der Zeile dazu addieren  
    	  
    	$fp = fopen( $source_file, 'r');  
    	$string2 = fread($fp, $startblock);//Alles einlesen bis zum Beginn des Blocks der ausgetauscht werden soll.  
    	fseek ( $fp, $beginreadend, 'SEEK_CUR' );//Neues Handle setzen  
    	$endcontent = fread($fp, filesize($source_file));//Rest nach Block bis Dateiende einlesen  
    	fclose($fp);  
      
    	$modifiedstring = $string2 . $newcontent . $endcontent; //Neuen String zusammensetzen  
    	  
    	// Sichergehen, dass die Datei existiert und beschreibbar ist  
    	if (is_writable($source_file)) {  
    	  
    		if (!$handle = fopen($source_file, "w")) {  
    			 print "Kann die Datei $filename nicht öffnen";  
    			 exit;  
    		}  
    	  
    		// Schreibe $somecontent in die geöffnete Datei.  
    		if (!fwrite($handle, $modifiedstring)) {  
    			print "Kann in die Datei $filename nicht schreiben";  
    			exit;  
    		}  
    	  
    		print "Fertig, in Datei $filename wurde $somecontent geschrieben";  
    	  
    		fclose($handle);  
    	  
    	} else {  
    		print "Die Datei $filename ist nicht schreibbar";  
    	}  
    
    

    Was meint ihr, umständlich oder kann man es so lassen?
    Auf jedenfall klappt es so. Das einzge ist das in der neuen (modifizierten Datei) keine CRLF sondern nur LF drin sind.
    Aber ich denke das ist nicht so tragisch oder?

    viele Grüße
    hawk

    1. Hallo,

      So ich habe nun mal was "gebastelt".

      sehr schön, und ich erlaube mir noch etwas gut gemeinte Kritik.

      Dein Ansatz hat vor allem eine konzeptbedingte Einschränkung: Er setzt voraus, dass die Datei klein genug ist, um sie mit PHP vollständig in den verfügbaren Arbeitsspeicher zu laden. Bei Dateigrößen im Bereich von Megabytes ist die Gefahr groß, dass dem Script dann der Speicher ausgeht.

      Wenn man das vermeiden will, muss man aber ganz anders ansetzen, etwa blockweise oder zeilenweise lesen. Das ist insgesamt etwas mehr Aufwand, lohnt sich aber, wenn man sich daraus eine universell verwendbare Funktion stricken will.

      //gesamte Datei einlesen
      $fp = fopen( $source_file, 'r');
      $string1 = fread($fp, filesize($source_file));
      fclose($fp);

      Alles klar, $string1 enthält nun den gesamten Dateiinhalt (falls das Script nicht schon hier wegen Speichermangel beendet wurde).

      $startblock = strpos($string1, $begin_area); //Position bis zum Beginn des Bereichs der ausgetauscht werden soll
      $endblock = strpos($string1, $end_area); //End Position des Bereichs der ausgetauscht werden soll
      $beginreadend = $endblock + strlen($end_area); //Endpostions plus der Länge der Zeile dazu addieren

      Die Variable $endblock finde ich irreführend benannt, weil sie eben noch nicht das Ende des Blocks bezeichnet. Ich hätte daher die letzten beiden Zeilen zu einer zusammengefasst und mir dadurch eine unnötige und damit verwirrende Variable gespart.

      $fp = fopen( $source_file, 'r');
      $string2 = fread($fp, $startblock);//Alles einlesen bis zum Beginn des Blocks der ausgetauscht werden soll.
      fseek ( $fp, $beginreadend, 'SEEK_CUR' );//Neues Handle setzen
      $endcontent = fread($fp, filesize($source_file));//Rest nach Block bis Dateiende einlesen
      fclose($fp);

      Du öffnest und liest die Datei ein zweites Mal, obwohl du doch in $string1 schon den gesamten Inhalt hast. Mit substr() hättest du die beiden Teile einfacher und effizienter als mit einem erneuten Dateizugriff.
      Übrigens hast du bis hierher (fast) den gesamten Dateiinhalt schon doppelt im Arbeitsspeicher.

      $modifiedstring = $string2 . $newcontent . $endcontent; //Neuen String zusammensetzen

      Und hier zum dritten Mal. Dein Speicherbedarf ist also grob gesagt dreimal so groß wie nötig.

      // Sichergehen, dass die Datei existiert und beschreibbar ist
      if (is_writable($source_file)) {

      Das ist gut!

        if (!$handle = fopen($source\_file, "w")) {  
        	 print "Kann die Datei $filename nicht öffnen";  
        	 exit;  
        }  
      
        // Schreibe $somecontent in die geöffnete Datei.  
        if (!fwrite($handle, $modifiedstring)) {  
        	print "Kann in die Datei $filename nicht schreiben";  
        	exit;  
        }  
      
        print "Fertig, in Datei $filename wurde $somecontent geschrieben";  
      
        fclose($handle);  
      

      } else {
      print "Die Datei $filename ist nicht schreibbar";
      }

      Endlich mal jemand, der sich auch um mögliche auftretende Fehler kümmert! Wobei ... du hast anfangs nicht überprüft, ob die Originaldatei überhaupt existiert.

      Was meint ihr, umständlich oder kann man es so lassen?

      Etwas umständlich auf jeden Fall, vor allem wegen des doppelten Lesens und des dreifachen Pufferns.

      Das einzge ist das in der neuen (modifizierten Datei) keine CRLF sondern nur LF drin sind.

      Nanu? Die unveränderten Bereiche am Anfang und am Ende schreibst du doch auch wieder in die Ausgabedatei, ohne sie zu verändern. Da dürfte also keine Änderung auftreten. Beim neu hinzugekommenen bzw. editierten Bereich kommt's drauf an, wie du ihn aufbereitest: Setzt du als Zeilenumbrüche nur \n (LF) ein, oder notierst du \r\n (CR+LF)?

      Aber ich denke das ist nicht so tragisch oder?

      Im Unix-Bereich ist \n als Zeilenumbruch üblich, im Windows-Bereich \r\n. Die meisten Programme können aber mittlerweile mit beiden Varianten umgehen. Für normale Textdateien ist das daher nebensächlich. Nur wenn beide Formen gemischt in einer Datei auftreten, könnte es etwas Durcheinander geben.
      Und wenn die bearbeitete Datei später in einem Kontext verwendet wird, wo eine bestimmte Notation vorgeschrieben ist, muss man auch darauf achten.

      So long,
       Martin

      --
      PCMCIA: People Can't Memorize Computer Industry Acronyms
      1. Hallo Martin,
        herzlichen Dank für deine Analyse und Kommentare.
        ICh werde mal schauen, das ich es noch besser hinbekomme.
        Ich will eigentlich eine universale Funktion machen, die man vielleicht öfters einsetzen kann.

        Nanu? Die unveränderten Bereiche am Anfang und am Ende schreibst du doch auch wieder in die Ausgabedatei, ohne sie zu verändern. Da dürfte also keine Änderung auftreten. Beim neu hinzugekommenen bzw. editierten Bereich kommt's drauf an, wie du ihn aufbereitest: Setzt du als Zeilenumbrüche nur \n (LF) ein, oder notierst du \r\n (CR+LF)?

        ja mich hat das auch gewundert, weil ich ja die Originalblöcke vorne und hinten nicht verändere. Ich habe aber die modifizierte Datei mit einer Kopie der Originaldatei mit einem Diff Tool verglichen und  sieht man dann nur LF.

        vielen Dank und viele Grüße
        hawk

        1. Hello,

          Ich will eigentlich eine universale Funktion machen, die man vielleicht öfters einsetzen kann.

          Dann lies Dir bitte auch den Artikel von Christian Seiler durch:
          http://aktuell.de.selfhtml.org/artikel/programmiertechnik/dateisperren/

          Die Berücksichtigung der dort beschriebenen Szenarien würde Deine Funktion noch umm einiges universeller machen können.

          Viel Erfolg.

          Liebe Grüße aus dem schönen Oberharz

          Tom vom Berg

          --
           ☻_
          /▌
          / \ Nur selber lernen macht schlau
          http://bergpost.annerschbarrich.de