it-swarm.com.de

Gibt es ein Befehlszeilenprogramm, mit dem ein bestimmter Zeilenblock in einer Textdatei gesucht und ersetzt werden kann?

[~ # ~] update [~ # ~] (siehe Ende der Frage)

Der Text "Suchen und Ersetzen", den ich gesehen habe, scheint nur zeilenweise zu suchen ...

Gibt es ein Befehlszeilenprogramm , das einen Zeilenblock lokalisieren kann ? (in einer Textdatei) und ersetzen durch einen anderen Zeilenblock.?

Beispiel: Enthält die Testdatei file this exact group von Zeilen:

'Twas brillig, and the slithy toves
Did gyre and gimble in the wabe:
All mimsy were the borogoves,  
And the mome raths outgrabe. 

'Beware the Jabberwock, my son!
The jaws that bite, the claws that catch!
Beware the Jubjub bird, and shun
The frumious Bandersnatch!'

Ich möchte dies, damit ich mehrere Textzeilen in einer Datei ersetzen kann und weiß, dass ich nicht die falschen Zeilen überschreibe.

Ich würde "The Jabberwocky" (Lewis Carroll) niemals ersetzen, aber es ist ein neuartiges Beispiel :)

[~ # ~] Update [~ # ~] :
.. (Sub-Update) Mein nachfolgender Kommentar zu den Gründen wann nicht verwenden sed sind nur im Zusammenhang mit; Schieben Sie kein Werkzeug zu weit über seine Konstruktionsabsicht hinaus (Ich verwende sed ziemlich oft und halte es für von unschätzbarem Wert.)

Ich habe gerade eine interessante Webseite über sed gefunden und wann ich sie nicht verwenden soll.
Also, wegen all der sed Antworten poste ich den Link .. er ist Teil des sed FAQ bei SourceForge

Außerdem bin ich mir ziemlich sicher, dass es eine Möglichkeit gibt, wie diff den Textblock lokalisieren kann (sobald er gefunden ist, Die Ersetzung ist ziemlich einfach: Mit head und tail) ... 'diff' werden alle erforderlichen Daten gesichert, aber ich habe noch nicht herausgefunden, wie man sie filtert. (Ich arbeite immer noch daran)

7
Peter.O

Dieses einfache python Skript sollte die folgende Aufgabe erfüllen:


#!/usr/bin/env python

# Syntax: multiline-replace.py input.txt search.txt replacement.txt

import sys

inp = open(sys.argv[1]).read()
needle = open(sys.argv[2]).read()
replacement = open(sys.argv[3]).read()

sys.stdout.write(inp.replace(needle,replacement))

Wie die meisten anderen Lösungen hat es den Nachteil, dass die gesamte Datei auf einmal in den Speicher geladen wird. Für kleine Textdateien sollte es jedoch gut genug funktionieren.

7
loevborg

Ansatz 1: Ändern Sie vorübergehend Zeilenumbrüche in etwas anderes

Das folgende Snippet tauscht Zeilenumbrüche mit Pipes aus, führt den Austausch durch und tauscht Trennzeichen zurück. Das Dienstprogramm kann ersticken, wenn die Zeile, die es sieht, extrem lang ist. Sie können ein beliebiges Zeichen zum Tauschen auswählen, solange es nicht in Ihrer Suchzeichenfolge enthalten ist.

<old.txt tr '\n' '|' |
sed 's/\(|\|^\)'\''Twas … toves|Did … Bandersnatch!'\''|/new line 1|new line 2|/g' |
tr '|' '\n' >new.txt

Ansatz 2: Ändern Sie das Datensatztrennzeichen des Dienstprogramms

Awk und Perl unterstützen die Einstellung von zwei oder mehr Leerzeilen als Datensatztrennzeichen. Übergeben Sie mit awk -vRS= (Leere Variable RS). Übergeben Sie in Perl -000 („Absatzmodus“) oder legen Sie $,="" Fest. Dies ist hier jedoch nicht hilfreich, da Sie eine Suchzeichenfolge mit mehreren Absätzen haben.

Awk und Perl unterstützen auch das Festlegen einer beliebigen Zeichenfolge als Datensatztrennzeichen. Setzen Sie RS oder $, Auf eine beliebige Zeichenfolge, die nicht in Ihrer Suchzeichenfolge enthalten ist.

<old.txt Perl -pe '
    BEGIN {$, = "|"}
    s/^'\''Twas … toves\nDid … Bandersnatch!'\''$/new line 1\nnew line 2/mg
' >new.txt

Ansatz 3: Arbeiten Sie an der gesamten Datei

Mit einigen Dienstprogrammen können Sie problemlos die gesamte Datei in den Speicher einlesen und bearbeiten.

<old.txt Perl -0777 -pe '
    s/^'\''Twas … toves\nDid … Bandersnatch!'\''$/new line 1\nnew line 2/mg
' >new.txt

Ansatz 4: Programm

Lies die Zeilen nacheinander. Beginnen Sie mit einem leeren Puffer. Wenn Sie die Zeile "'Twas" sehen und der Puffer leer ist, legen Sie ihn in den Puffer. Wenn Sie die Meldung "Did gyre" sehen und sich eine Zeile im Puffer befindet, hängen Sie die aktuelle Zeile an den Puffer an und so weiter. Wenn Sie gerade die „Bandersnatch-Zeile“ angehängt haben, geben Sie den Ersatztext aus. Wenn die aktuelle Zeile nicht in den Puffer gelangt ist, drucken Sie den Inhalt des Puffers, drucken Sie die aktuelle Zeile und leeren Sie den Puffer.

psusi zeigt eine sed-Implementierung. In sed ist das Pufferkonzept eingebaut; Es wird der Laderaum genannt. In awk oder Perl würden Sie nur eine Variable verwenden (vielleicht zwei, eine für den Pufferinhalt und eine für die Anzahl der Zeilen).

3
Gilles

[~ # ~] update [~ # ~] : loevborgs python script ist sicherlich die einfachste und beste lösung ( Es besteht kein Zweifel) und ich bin sehr zufrieden damit, aber ich möchte darauf hinweisen, dass das Bash-Skript, das ich (am Ende der Frage) vorgestellt habe, bei weitem nicht so kompliziert ist, wie es aussieht. Ich habe es abgeschnitten all die Debugging-Krätze, die ich verwendet habe, um es zu testen .. und hier ist es wieder ohne Überlastung (für jeden, der diese Seite besucht) .. Es ist im Grunde ein sed Einzeiler mit Vor- und Nach-Hex-Konvertierungen:

F=("$haystack"  "$needle"  "$replacement")
for f in "${F[@]}" ; do cat "$f" | hexdump -v -e '1/1 "%02x"' > "$f.hex" ; done
sed -i "s/$(cat "${F[1])}.hex")/$(cat "${F[2])}.hex")/p" "${F[0])}.hex"
cat "${F[0])}.hex" | xxd -r -p > "${F[0])}"
# delete the temp *.hex files.

Um meinen Hut in den Ring zu werfen, habe ich eine "sed" -Lösung gefunden, die keine Probleme mit speziellen Regex-Zeichen verursacht , weil es nicht einmal eins nutzt! .. stattdessen funktioniert es auf Hexdumped-Versionen der Dateien ...

Ich denke, es ist zu "kopflastig", aber es funktioniert und ist anscheinend nicht durch Größenbeschränkungen eingeschränkt. GNU sed hat ein unbegrenztes Muster Puffergröße, und hier endet der hexadezimale Block von Suchzeilen. Also ist es in dieser Hinsicht okay ...

Ich bin immer noch auf der Suche nach einer diff Lösung, da diese flexibler in Bezug auf Leerzeichen ist (und ich würde erwarten, schneller) ... aber bis dahin ... Es ist der berühmte Mr Sed. :)

Dieses Skript läuft wie es ist und ist vernünftigerweise kommentiert ...
Es sieht größer aus, als es ist; Ich habe nur 7 Zeilen essentiellen Codes.
Für einen semi-realistischen Test lädt es das Buch "Alice Through the Looking Glass" von Projekt Gutenberg (363,1 KB) herunter und ersetzt das ursprüngliche Jabberwocky-Gedicht durch ein Zeilen- umgekehrte Version von sich selbst. (Interessanterweise ist es nicht viel anders, wenn man es rückwärts liest :)

PS. Ich habe gerade festgestellt, dass eine Schwachstelle bei dieser Methode darin besteht, dass Ihr Original\r\n (0xODOA) als Zeilenumbruch verwendet und Ihr "zuzuordnender Text" mit\n (0x0A) gespeichert wird Wasser ... ('diff' hat keine solchen Probleme) ...


# In a text file, replace one block of lines with another block
#
# Keeping with the 'Jabberwocky' theme, 
#  and using 'sed' with 'hexdump', so 
#  there is no possible *special* char clash.
# 
# The current setup will replace only the first instance.
#   Using sed's 'g' command, it cah change all instances. 
#

  lookinglass="$HOME/Through the Looking-Glass by Lewis Carroll"
  jabberwocky="$lookinglass (jabberwocky)"
  ykcowrebbaj="$lookinglass (ykcowrebbaj)"

  ##### This section if FOR TEST PREPARATION ONLY
        fromURL="http://www.gutenberg.org/ebooks/12.txt.utf8"
        wget $fromURL -O "$lookinglass"
        if (($?==0))
        then  echo "Download OK"
        else  exit 1
        fi
        # Make a backup of the original (while testing)
        cp "$lookinglass" "$lookinglass(fromURL)"
        #
        # Extact the poem and write it to a file. (It runs from line 322-359)
        sed -n 322,359p "$lookinglass" > "$jabberwocky"
        cat "$jabberwocky"; read -p "This is the original.. (press Enter to continue)"
        #
        # Make a file containing a replacement block of lines
        tac "$jabberwocky" > "$ykcowrebbaj"
        cat "$ykcowrebbaj"; read -p "This is the REPLACEMENT.. (press Enter to continue)"
  ##### End TEST PREPARATION

# The main process
#
# Make 'hexdump' versions of the 3 files... source, expected, replacement 
  cat "$lookinglass" | hexdump -v -e '1/1 "%02x"' > "$lookinglass.xdig"
  cat "$jabberwocky" | hexdump -v -e '1/1 "%02x"' > "$jabberwocky.xdig"
  cat "$ykcowrebbaj" | hexdump -v -e '1/1 "%02x"' > "$ykcowrebbaj.xdig"
# Now use 'sed' in a safe (no special chrs) way.
# Note, all files are now each, a single line  ('\n' is now '0A')
  sed -i "s/$(cat "$jabberwocky.xdig")/$(cat "$ykcowrebbaj.xdig")/p" "$lookinglass.xdig"

  ##### This section if FOR CHECKING THE RESULTS ONLY
        # Check result 1
        read -p "About to test for the presence of  'jabberwocky.xdig'  within itself (Enter) "
        sed -n "/$(cat "$jabberwocky.xdig")/p"     "$jabberwocky.xdig"
        echo -e "\n\nA dump above this line, means: 'jabberwocky' is as expected\n" 
        # Check result 2
        read -p "About to test for the presence of  'ykcowrebbaj.xdig'  within itself (Enter) "
        sed -n "/$(cat "$ykcowrebbaj.xdig")/p"     "$ykcowrebbaj.xdig"
        echo -e "\n\nA dump above this line, means: 'ykcowrebbaj' is as expected\n" 
        # Check result 3
        read -p "About to test for the presence of  'lookinglass.xdig'  within itself (Enter) "
        sed -n "/$(cat "$ykcowrebbaj.xdig")/p"     "$lookinglass.xdig"
        echo -e "\n\nA dump above this line, means: 'lookinglass' is as expected\n" 
        # Check result 4
        read -p "About to test for the presence of  'lookinglass.xdig'  within itself (Enter) "
        sed -n "/$(cat "$jabberwocky.xdig")/p"     "$lookinglass.xdig"
        echo -e "\n\nNo dump above this line means: 'lookinglass' is as expected\n"
  ##### End of CHECKING THE RESULTS

# Now convert the hexdump to binary, and overwrite the original
  cat "$lookinglass.xdig" | xxd -r -p > "$lookinglass"
# Echo the "modified" poem to the screen
  sed -n 322,359p "$lookinglass"
  echo -e "\n\nYou are now looking at the REPLACEMENT text (dumped directly from the source 'book'"
2
Peter.O

Ich war mir sicher, dass es einen Weg geben musste, dies mit sed zu tun. Nach einigem googeln bin ich auf folgendes gestoßen:

http://austinmatzko.com/2008/04/26/sed-multi-line-search-and-replace/

Darauf aufbauend schrieb ich:

sed -n '1h;1!H;${;g;s/foo\nbar/jar\nhead/g;p;}' < x

Was hat den Inhalt von x richtig genommen:

foo Bar

Und ausspucken:

glaskopf

2
psusi

Selbst wenn Sie hoary sed und Perl nicht mögen, finden Sie möglicherweise immer noch eine Vorliebe für awk mit grauen Schablonen. Diese Antwort scheint das zu sein, wonach Sie suchen. Ich reproduziere es hier. Angenommen, Sie haben drei Dateien und möchten needle durch replacement in haystack ersetzen:


awk ' BEGIN { RS="" }
      FILENAME==ARGV[1] { s=$0 }
      FILENAME==ARGV[2] { r=$0 }
      FILENAME==ARGV[3] { sub(s,r) ; print }
    ' needle replacement haystack > output

Dies beinhaltet keine regulären Ausdrücke und unterstützt Zeilenumbrüche. Es scheint mit relativ großen Dateien zu funktionieren. Es wird die gesamte Datei in den Speicher geschlürft, sodass es nicht mit Dateien beliebiger Größe funktioniert. Wenn Sie es eleganter haben möchten, können Sie den gesamten Shebang in ein Bash-Skript einschließen oder ihn in ein awk -Skript umwandeln.

2
loevborg