Wie füge ich den Inhalt einer Datei vor einem Muster (Marker) in eine andere Datei ein?

32

File1 Inhalt:

line1-file1      "1" 
line2-file1      "2"
line3-file1      "3" 
line4-file1      "4" 

File2 Inhalt:

line1-file2     "25"  
line2-file2     "24"  
Pointer-file2   "23"  
line4-file2     "22" 
line5-file2     "21"

Nach der Ausführung des Perl / Shell-Skripts sollte der File2Inhalt wie folgt aussehen:

line1-file2     "25"  
line2-file2     "24" 
line1-file1      "1" 
line2-file1      "2"
line3-file1      "3" 
line4-file1      "4" 
Pointer-file2   "23" 
line4-file2     "22" 
line5-file2     "21"

dh fügen Sie den Inhalt von File1in File2vor der Zeile ein, die "Pointer" enthält.

user1228191
quelle
1
Ebenfalls gefragt bei StackOverflow
Glenn Jackman
Ich habe bereits die SO-Frage für den Abschluss gestimmt, aus diesem Grund gibt es keinen Grund mehr, diese Frage zu schließen. Übrigens ist die Frage nach der Qualität der SO weitaus schlechter, daher ist es logisch, diese und nicht diese zu schließen.
Peter sagt, dass Monica

Antworten:

33

sed hat eine Funktion dafür und kann die Modifikation inline machen:

sed -i -e '/Pointer/r file1' file2

Dadurch wird Ihre Pointer- Zeile jedoch über der Datei1 platziert. Um es unten auszudrücken: Ausgabe der Verzögerungsleitung:

sed -n -i -e '/Pointer/r file1' -e 1x -e '2,${x;p}' -e '${x;p}' file2 
jfg956
quelle
8
Könnten Sie bitte erklären, was zu -e 1x -e '2,${x;p}' -e '${x;p}'tun ist? Ich verstehe, dass Sie Sachen im Musterpuffer austauschen und dann drucken, aber ich weiß nicht, warum Sie -nam Anfang die Option "Leise" hinzugefügt haben .
HDL
@ jfg956 Ist es möglich, den 'Zeiger'-Teil einfach in der Originaldatei zu ersetzen und zu löschen. Das kann ich mit einem zweiten Durchgang von sed herausfinden, aber ist es möglich, es in einem Durchgang zu tun?
Alexander Cska
17

Ohne zu benutzen sedoder awk...

Finden Sie zuerst die Linie, auf der sich Ihr Muster befindet:

line=$(grep -n 'Pointer' file2 | cut -d ":" -f 1)

Verwenden Sie dann 3 Befehle, um das gewünschte Ergebnis auszugeben:

{ head -n $(($line-1)) file2; cat file1; tail -n +$line file2; } > new_file

Dies hat den Nachteil , den Zugriff auf 3 Mal der Datei file2, könnte aber ein klarer sein als seddie awkLösung.

jfg956
quelle
10

awkmacht das ziemlich einfach.
Fügen Sie die Zeile vor der Datei ein:

awk '/Pointer/{while(getline line<"innerfile"){print line}} //' outerfile >tmp
mv tmp outerfile

Um die innere Datei nach der PointerZeile auszudrucken , ändern Sie einfach die Reihenfolge der Muster (Sie müssen ein Semikolon hinzufügen, um die Standardaktion zu erhalten), und Sie können die lineVariable löschen:

awk '//; /Pointer/{while(getline<"innerfile"){print}}' outerfile >tmp
mv tmp outerfile

Und nur weil noch niemand benutzt perlhat,

# insert file before line
perl -e 'while(<>){if($_=~/Pointer/){system("cat innerfile")};print}' outerfile

# after line
perl -e 'while(<>){print;if($_=~/Pointer/){system("cat innerfile")}}' outerfile
Kevin
quelle
es funktioniert, aber es wird entfernt die Zeile mit dem Zeiger
user1228191
Ebenso, wie man den Inhalt von Datei 1 in Datei 2 nach dieser "Zeiger" enthaltenden Zeile mit awk
user1228191 28.02.12
@ user1228191 Erstes behoben, zweites hinzugefügt.
Kevin
Die 'Perl'-Version scheint nicht zu funktionieren. system("cat innerfile")gibt das innerfilean die Konsole aus. Vermisse ich etwas?
Kaartic
Der awk-Befehl [gawk '/ <body> / {while (getline line <"$ HOME / bin / SrunScenario.style") {print line}} //' index.html> new_index.html] schleift und druckt nur Millionen von Zeilen . gawk V4.2.0 Was fehle ich hier?
JESii
7

Ein leichter Job für ed:

ed -s file1 <<IN
/Pointer/-r file2
,p
q
IN

-r file1Liest die angegebene Datei nach der adressierten Zeile ein. In diesem Fall handelt es sich um die Zeile vor der ersten übereinstimmenden Zeile Pointer. Dadurch wird der Inhalt file2nur einmal eingefügt, auch wenn er Pointerin mehreren Zeilen vorkommt. Wenn Sie es vor jeder übereinstimmenden Zeile einfügen möchten, fügen Sie global flag hinzu:

ed -s file1 <<IN
g/Pointer/-r file2
,p
q
IN

Ersetzen Sie ,pdurch, wwenn Sie die Datei direkt bearbeiten möchten.


Das akzeptierte sed Antwort funktioniert in den meisten Fällen, aber wenn sich der Marker in der letzten Zeile befindet, funktioniert der Befehl nicht wie erwartet: Er fügt den Inhalt File1nach dem Marker ein.
Ich habe es anfangs versucht mit:

sed '/Pointer/{r file1
N}' file2

das funktioniert auch gut (wie r es auch am Ende des Zyklus ), hat aber das gleiche Problem, wenn sich der Marker in der letzten Zeile befindet (es gibt keine Next-Zeile nach der letzten Zeile). Um dies zu umgehen, können Sie Ihrer Eingabe eine neue Zeile hinzufügen:

sed '/Pointer/{              # like the first one, but this time even if the
r file1                      # marker is on the last line in File2 it
N                            # will be on the second to last line in
}                            # the combined input so N will always work;
${                           # on the last line of input: if the line is
/^$/!{                       # not empty, it means the marker was on the last
s/\n$//                      # line in File2 so the final empty line in the
}                            # input was pulled i\n: remove the latter;
//d                          # if the line is empty, delete it
}' file2 <(printf %s\\n)

Dadurch wird file2vor jeder übereinstimmenden Zeile Inhalt eingefügt. Um es nur vor der ersten übereinstimmenden Zeile einzufügen, können Sie ein loop verwenden und einfach die next-Zeile hineinziehen, bis Sie am Ende der Datei angelangt sind:

sed '/Pointer/{
r file2
N
:l
$!n
$!bl
}
${
/^$/!{
s/\n$//
}
//d
}' file1 <(printf %s\\n)

Mit diesen sedLösungen können Sie nicht mehr direkt bearbeitet werden (Sie können jedoch zu einer anderen Datei umleiten).

don_crissti
quelle
6

Verwenden Sie eine Schleife, um die Zeilen in Datei2 zu lesen. Wenn Sie eine Zeile finden, die mit beginnt Pointer, drucken Sie file1 aus. Dies ist unten gezeigt:

#!/bin/bash
while IFS= read -r line
do
    if [[ "$line" =~ ^Pointer.*$ ]]
    then
        cat file1
    fi
    echo "$line"
done < file2
Dogbane
quelle
4

Hierfür gibt es einige Möglichkeiten sed. Eine Möglichkeit ist ein verzögertes Lesen, wie es in der akzeptierten Antwort empfohlen wird. Es könnte auch so geschrieben werden:

sed -e '$!N;P;/\nPointer/r file1' -e D file2

... mit einem kleinen expliziten Look-Ahead anstelle des Look-Behind, der an anderer Stelle mit dem Haltepuffer implementiert wurde. Das wird unvermeidlich dasselbe Problem mit der letzten Zeile haben, die @don_crissti notiert, weil N das so ist Erhöhung den Leitungszyklus und der read Befehl Zeilennummer angewandt wird.

Sie können es umgehen:

echo | sed -e '$d;N;P;/\nPointer/r file1' -e D file2 -

Nicht alle seds interpretieren die -Standardeingabe, aber viele tun dies. ( POSIX sagt, sed sollte -Standard-In bedeuten, wenn der Implementierer dies wünscht- Standard-In meinen ???)

Eine andere Möglichkeit besteht darin, den angehängten Inhalt in der richtigen Reihenfolge zu verarbeiten. Es gibt einen weiteren Befehl, der die Ausgabe auf die gleiche Weise rplant wie ead und sedsie rin der Reihenfolge anwendet, in der sie geschrieben wurden. Es ist jedoch ein bisschen komplizierter - es bedeutet, dass man eins verwendet sed, aum das PointerMatch an die Ausgabe eines anderen sedin seinem Skript anzuhängen .

sed '   /Pointer/!d                  #only operate on first match
        s/[]^$&\./*[]/\\&/g;H        #escape all metachars, Hold
        s|.*|/&/!p;//!d|p;g          #print commands, exchange
        s|.|r file1&a\\&|;q' file2|  #more commands, quit
        sed -nf - file2              #same input file

Der erste sedschreibt also im Grunde den zweiten sedein Skript, das der zweite sedauf Standardeingabe liest (vielleicht ...) und der Reihe nach anwendet. Der erste sedfunktioniert nur bei der ersten Übereinstimmung für Pointergefunden, und danach qwird die Eingabe beendet. Seine Aufgabe ist es, ...

  1. s/[]^$&\./*[]/\\&/g;H
    • Stellen Sie sicher, dass alle Musterzeichen sicher mit Backslash versehen sind, da die Sekunde sedjedes Bit, das sie liest, wörtlich interpretieren muss, um es richtig zu machen. Sobald dies erledigt ist, legen Sie eine Kopie in den Halten Raum.
  2. s|.*|/&/!p;//!d|p; x
    • Sagen Sie dem Zweiten sed, er soll pjede Eingabezeile drucken, !außer der, die /&/wir nur mit einem Mustersafe versehen haben. und dann dalle gleich zu eletieren. pDrucken Sie die Befehle in der zweiten Zeile sed, und xändern Sie dann den halten und den Musterpuffer, um mit unserer gespeicherten Kopie zu arbeiten.
  3. s|.|r file1&a\\&|p;q
    • Das einzige \nZeichen, mit dem wir hier arbeiten, ist eine ewline, da sedeine vorangestellt wurde, als wir Hdie Zeile zuvor bearbeitet haben . Also fügen wir den Befehl ein r file1und folgen ihm mit unserer \newline, dann dem Befehl a\\für append, gefolgt von einer \newline. Der Rest unserer Held-Linie folgt dieser letzten \newline.

Das Skript, das der erste schreibt, sieht ungefähr so ​​aus:

/Pointer-file2   "23"/!p;//!d
r file1
a\
Pointer-file2   "23"

Grundsätzlich ist die zweite sedwird jede Zeile drucken , sondern derjenige , der die ersten sedSätze es an aPPEND. Für diese bestimmte Zeile sind zwei verzögerte Schreibvorgänge für die Standardausgabe geplant - der erste ist der read von file1und der zweite ist eine Kopie der Zeile, die wir danach haben möchten. Die sedErstbehandlung ist in diesem Fall gar nicht nötig (siehe? Keine Backslashes), aber es ist wichtig, sicher zu fliehen, wie ich es hier tue, wenn eine Musterübereinstimmung als Eingabe verwendet wird.

Wie auch immer, es gibt ein paar Möglichkeiten.

mikeserv
quelle
2

Mit AWK ist das ziemlich einfach:

Datei1 in Datei2 vor Muster = "Zeiger"

Laden Sie zuerst den Inhalt von File1 in eine Variable

f1="$(<File1)"

dann machen Sie die Einfügung

awk -vf1="$f1" '/Pointer/{print f1;print;next}1' file2

(Oder wenn Sie File1 nach "Pointer" einfügen möchten)

awk -vf1="$f1" '/Pointer/{print;print f1;next}1' file2
Amos Folarin
quelle
2

mein bevorzugter weg: templating .

sed 's/CHANGEME/$x/g' origfile | x="$(<file2insert)" envsubst '$x' > newfile

Dadurch wird jedes CHANGEME- Vorkommen in origfile durch den Inhalt von file2insert ersetzt . Entfernen Sie das letzte g von sed, um nur das erste Auftreten von CHANGEME zu ersetzen .

nrc
quelle
Wie können Sie $xin Ihrem ersten Befehl verwenden, wenn er nur in Ihrem zweiten Befehl definiert ist?
Totor
Das "$ x" im ersten Befehl ist nur ein Platzhalter, der von envsubst im zweiten Befehl ausgewertet wird. Bei einfachen Anführungszeichen von sed-Skript wird $ x von Ihrer Shell nicht ausgewertet.
nrc
2

[Dateiinhalt in eine andere Datei einfügen, BEVOR Muster]

sed -i '/PATTERN/r file1' -e //N file2

[Nach Muster]

sed -i '/PATTERN/r file1' file2
Jim.Y
quelle
Nfunktioniert gut, aber nicht, wenn das MUSTER mit der letzten Eingabezeile übereinstimmt
Sundeep,
0
 awk '/Pointer/{system("cat File1")}1' File2
dedowsdi
quelle