Gibt es eine Befehlszeilen-Dienstprogramm-App, die einen bestimmten Zeilenblock in einer Textdatei finden und ersetzen kann?

7

UPDATE (siehe Ende der Frage)

Der Text "Hilfsprogramme suchen und ersetzen", den ich gesehen habe, scheint nur zeilenweise zu suchen ...

Gibt es ein Kommandozeilen - Tool , das kann lokalisieren (in einer Textdatei) einen Block von Linien, und ersetzen Sie es mit einem anderen Block von Linien.?

Beispiel: Enthält die Testdatei folgende exact groupZeilen:

'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 neues Beispiel :)

UPDATE :
.. (sub-update) Mein folgender Kommentar über Gründe , wenn nicht ist sed verwendet nur im Kontext von; Schieben Sie kein Werkzeug zu weit über seine Konstruktionsabsicht hinaus (ich benutze 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 benutzen soll.
Aufgrund all der sed- Antworten werde ich den Link posten. Er ist Teil der sed-FAQ zu sourceforge

Ich bin mir auch ziemlich sicher, dass es eine Möglichkeit diffgibt, den Textblock zu lokalisieren (sobald er gefunden ist, ist der Ersatz ziemlich einfach; mit headund tail) ... 'diff' gibt alle erforderlichen Daten aus, aber ich habe noch nicht herausgefunden, wie man es filtert, ... (ich arbeite noch daran)

Peter.O
quelle

Antworten:

7

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 geschlürft wird. Für kleine Textdateien sollte es jedoch gut genug funktionieren.

loevborg
quelle
1
Na dann! Das ist der richtige Weg! ... Ich habe alles drauf geworfen und es hat den Test bestanden ... (und warum sollte es nicht ... Python hat aus gutem Grund einen "Repräsentanten" .;) .... Ich muss weiter suchen in die "Ersetzen" -Methode (oder wie auch immer Python sie nennt) .. aber .. sie ist eine Geherin! ... Thanks.loevborg ..
Peter.O
3

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

Das folgende Snippet tauscht Zeilenumbrüche gegen Pipes aus, führt den Austausch durch und tauscht Trennzeichen zurück. Das Dienstprogramm kann ersticken, wenn die Linie, 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 das Festlegen von zwei oder mehr Leerzeilen als Datensatztrennzeichen. Mit awk übergeben -vRS=(leere RSVariable). Übergeben Sie mit Perl -000(„Absatzmodus“) oder setzen Sie $,="". 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 RSoder $,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 lesen und daran arbeiten.

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

Ansatz 4: Programm

Lesen Sie 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 "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 aufgenommen wurde, drucken Sie den Pufferinhalt, drucken Sie die aktuelle Zeile und leeren Sie den Puffer.

psusi zeigt eine sed-Implementierung. In sed ist das Pufferkonzept integriert; Es heißt Hold Space. In awk oder perl verwenden Sie nur eine Variable (möglicherweise zwei, eine für den Pufferinhalt und eine für die Anzahl der Zeilen).

Gilles 'SO - hör auf böse zu sein'
quelle
Gilles, danke für die Möglichkeiten. Wie ich in anderen Kommentaren erwähnt habe, habe ich alles ausgeschlossen, was einen regulären Ausdruck verwendet. Weil mein Suchtext irgendwann mit Regex-Sonderzeichen kollidieren wird. Ich dachte, so etwas könnte im Bereich des Codierers (Quellcode-Mods) bekannt sein, aber vielleicht auch nicht ... Ich werde mich mit awk befassen ein bisschen weiter, aber ich vermute, dass read -r(ich habe gerade das -r heute entdeckt) der richtige Weg sein könnte ... Ich habe letzte Woche ein funktionierendes Skript geschrieben, das keinen regulären Ausdruck verwendet, aber ich wusste bereits, wo ich den Text finden kann ( über seine Zeilennummer). read -roder awk funktioniert möglicherweise zum Auffinden in einer unbekannten Datei.
Peter.O
@ fred.bear: Die Suche nach Text mit Regexp-Sonderzeichen ist ein separates Problem. Eine Möglichkeit, dies zu lösen, besteht darin, die Suchzeichenfolge vorzuverarbeiten, um Sonderzeichen anzugeben. Wenn das Dienstprogramm dies unterstützt, ist es besser, eine Literal-String-Suche durchzuführen. Awk: indexFunktion. Perl: indexFunktion, \Q…\ERegexp-Escape.
Gilles 'SO - hör auf böse zu sein'
Gilles, das Tool müsste nicht nur die Suche nach wörtlichen Zeichenfolgen unterstützen, sondern auch die Substitution von wörtlichen Zeichenfolgen.
Loevborg
@loevborg: In Perl ist es nur s/\Q$needle/$haystack/g. Awk ist weniger einfach, aber machbar.
Gilles 'SO - hör auf böse zu sein'
2

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/

Aufgrund dessen schrieb ich am Ende:

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

Welches hat den Inhalt von x richtig genommen:

Foo Bar

Und ausspucken:

Glas Kopf

psusi
quelle
Ich würde sed hier nicht empfehlen. Obwohl dies möglich ist, wird die Verwendung des Haltebereichs schnell kompliziert (hier ist es mit drei Zeilen bereits ein Chaos).
Gilles 'SO - hör auf böse zu sein'
Das hat mich sicher zum Ding gemacht. Ich benutze sed (und mag es), aber wie Gilles erwähnte, kann das Arbeiten mit dem Laderaum schnell zu einem Schmerz werden (also versuche ich, mich davon fernzuhalten). Das größte Problem bei jeder Regex-Suche ist jedoch, wenn Ihr Regex-Muster ist "Unbekannt"; Die Wahrscheinlichkeit eines Zusammenstoßes ist hoch ... Ich habe also alles ausgeschlossen, was Regex verwendet. Der Hauptpunkt dabei ist, manuelle Anpassungen zu vermeiden ... Ehrlich gesagt, wenn sed eine Möglichkeit hat , Regex auszuschalten , könnte seine Adressbereichsfunktion sehr nützlich sein, andernfalls ist es nicht das Werkzeug für den Job. sed talk is good talk
Peter.O
2

Selbst wenn Sie hoary sedund nicht perlmögen, finden Sie vielleicht immer noch eine Vorliebe für graue Vorlagen awk. Diese Antwort scheint genau das zu sein, wonach Sie suchen. Ich reproduziere es hier. Angenommen , Sie haben drei Dateien und ersetzt werden sollen needlemit replacementin haystack:


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 ziemlich großen Dateien zu funktionieren. Es beinhaltet das Schlürfen der gesamten Datei in den Speicher, sodass es nicht mit Dateien beliebiger Größe funktioniert. Wenn Sie es eleganter wünschen, können Sie den gesamten Shebang in ein Bash-Skript einschließen oder in ein awkSkript umwandeln.

loevborg
quelle
Ich muss die falsche Nachricht gegeben haben. Ich LIEBE 'sed'! und benutze es für alle Dinge unter der Sonne ... Ich denke nur, es ist nicht das beste Werkzeug für diesen Job ... Ich bin gerade dabei, eine "sed" -Lösung zu veröffentlichen ... die funktioniert und NICHT haben wird Probleme mit zusammenstoßenden Regex-Sonderzeichen, aber ich denke, es erstellt zu viele Dateien und ist daher "kopflastig" für den Job ...... Nachdem ich es gepostet und eine Pause gemacht habe, werde ich es mir genauer ansehen Ihre Lösung .. Danke ...
Peter.O
.. Leider in Bezug auf Regex-Sonderzeichen, wenn gleichermaßen fehlgeschlagen wie alle Regex-Utils. Wenn sie auf einen besonderen Charakter stoßen , behandeln sie ihn auf besondere Weise. Nicht gut, wenn er nicht besonders sein soll. Es stürzte auf [, (und falsche Ergebnisse auf $, ^(die Daten nicht gefunden haben, obwohl es identisch war) ... Ich habe nicht weiter prüfen ... (aber der Befehl sieht „genau richtig“ :)
Peter. O
Fred, du hast recht; Ich hätte die Dokumentation überprüfen sollen. Darüber hinaus gsubscheint angemessener als sub. Ich finde es ziemlich seltsam, dass kein einfaches mulitline nonregex-Dienstprogramm zum Suchen / Ersetzen aufgetaucht ist. Es scheint keinen einfachen Weg zu geben, der Suchnadel zu entkommen / sie zu zitieren ( zum Beispiel bei der perlVerwendung quotemeta).
Loevborg
Ich bin mir gerade bewusst geworden grep -F --fixed-strings. Das bringt grep in eine ganz neue Kategorie (für mich) ... wie read -r... wörtliche Interpretation ... Wenn ich das nur sedhätte, wäre alles sehr einfach ... (hat vielleicht awkso etwas (?) .... und Übrigens ist der einfachste Teil des Ganzen das Ersetzen der Linien, ein einfacher Kopf (sobald die erste Zeilennummer identifiziert ist) | Katzenersatz | Schwanz der Rest (die Länge des entfernten Stücks ist bekannt) ... Also jetzt mit grep -F -A{num}bald genäht :) werden kann
Peter.O
2

UPDATE : Loevborgs Python-Skript ist sicherlich die einfachste und beste Lösung (daran besteht kein Zweifel) und ich bin sehr zufrieden damit, aber ich möchte darauf hinweisen, dass das Bash-Skript, das ich vorgestellt habe (am Ende der Frage) ist bei weitem nicht so kompliziert wie es aussieht. Ich habe all die Debugging-Krätze herausgeschnitten, die ich zum Testen verwendet habe. Und hier ist es wieder ohne Überlastung (für jeden, der diese Seite besucht). Es ist im Grunde ein sedEinzeiler mit Hex-Conversions vor und nach:

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 bei speziellen Regex-Zeichen keine Probleme verursacht , da nicht einmal eine verwendet wird! .. stattdessen funktioniert es auf Hexdumped-Versionen der Dateien ...

Ich denke , es ist zu „kopflastig“, aber es funktioniert, und wird offenbar nicht durch Größenbeschränkungen eingeschränkt .. GNU sed eine unbegrenzte hat Muster Puffergröße, und das ist , wo der Hexdumped Block von Suchlinien endet .. So In dieser Hinsicht ist es okay ...

Ich bin immer noch auf der Suche nach einer diffLösung, weil sie in Bezug auf Leerraum flexibler sein wird (und ich würde erwarten; schneller) ... aber bis dahin ... ist es der berühmte Mr. Sed. :) :)

Dieses Skript läuft vollständig wie es ist und wird vernünftigerweise kommentiert ...
Es sieht größer aus als es ist; Ich habe nur 7 Zeilen wesentlichen Codes.
Für einen semi-realistischen Test lädt es das Buch "Alice durch den Spiegel" von Project Gutenberg (363,1 KB) herunter ... und ersetzt das ursprüngliche Jabberwocky-Gedicht durch eine zeilenumgekehrte Version von sich selbst. (Interessanterweise ist es nicht viel anders rückwärts lesen :)

PS. Ich habe gerade festgestellt, dass eine Schwachstelle bei dieser Methode darin besteht, dass Ihr Original \ r \ n (0xODOA) als Zeilenumbruch verwendet und Ihr "übereinstimmender 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'"
Peter.O
quelle
Dies scheint eine unglaublich schwierige Lösung zu sein. Es ist viel besser, reguläre Ausdrücke zu verwenden und die Suchzeichenfolge in Anführungszeichen zu setzen.
Loevborg
:) ... Ja, ich stimme zu, aber ich habe diese ganze Linux-Ubuntu-Sache erst seit ungefähr 4 Monaten "verprügelt". Wenn mich also eine Idee wie diese packt, setzt sie sich ein Ziel, und das ist großartig, um ernsthaft zu werden Hand-on-Bash / allgemeine Erfahrung ... aber wie ich in den Kommentaren irgendwo erwähne, gibt es nur 7 Zeilen wesentlichen Codes; Der Rest war nur "Debuggen" und Herunterladen einer Testdatei usw. (Ich habe viel daraus gelernt ...
Peter.O