ubuntuusers.de

Bash: Text einer Datei in eine andere Datei einfügen

Status: Gelöst | Ubuntu-Version: Xubuntu 16.04 (Xenial Xerus)
Antworten |

KeyzerSoze

Anmeldungsdatum:
28. September 2014

Beiträge: 120

Hallo ubuntuusers,

ich möchte aus einer Datei Text extrahieren und diesen in eine andere Datei an einem bestimmten Punkt einfügen.

Ich verwende dafür folgenden Code (kommentiert):

./merge.sh
#! /bin/bash
sed  -n "/Anfang/,/Ende/p" data1| head -n-1 > temp      #Text kopieren zwischen Token A (inkl.; Anfang) und Token B (exkl.; Ende) in file "temp"
input=$(cat temp)                                       #Diesen Text in Variable speichern
sed -i "/tokenzeile/ { N; s/tokenzeile\n/$input\n&/ }" data 
# Fügt in Variable gespeicherten Text vor der "tokenzeile" ein.
rm -f temp      # lösche temporäres file

Ich erhalte als Meldung der Bash:

./merge.sh: Zeile 8: /usr/bin/sed: Die Argumentliste ist zu lang

Bemerkung: Die Größenordnung des kopierten Textes ist ca. 1MB.

Es sind 14.000 Zeilen zu je 6 Spalten mit doubles.

Die Fehlermeldung scheint sich also auf die Dateigröße der Variable zu beziehen.

Wie kann ich das schlau lösen? Bzw. ist es nicht sinnvoll, den Text vorher in einer Variable zwischen zu speichern?

Leider weiß ich nicht, wie ich das auf andere Weise lösen könnte.

Danke im Voraus!

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17621

Wohnort: Berlin

Kannst Du in Worten sagen, was die Zeile

1
sed -i "/tokenzeile/ { N; s/tokenzeile\n/$input\n&/ }" data

bewirken soll? Es soll inplace die Datei data geändert werden, und zwar wo auf das Muster tokenzeile getroffen wird. Soweit klar. Die nächste Zeile nach tokenzeile soll an den Puffer angehängt werden, der zu dieser Zeit leer ist, dann das Muster tokenzeile gefolgt von Zeilenumbruch mit Input, gefolgt von Zeilenumbruch, gefolgt vom passenden Muster (tokenzeile) ersetzt werden?

Links, wenn das Muster bis zum Zeilenende reicht, gehört m.E. ein $ statt des \n hin, weil der Zeilenumbruch als Trenner ausgewertet wird, und nie Teil des Musters wird.

Das eigentliche Probleme ist aber $input, das von der Shell ausgewertet und an sed übergeben wird, und alles mögliche an bedeutungsschweren Sonderzeichen enthalten kann oder, wie bei Dir, zu groß sein kann. Vielleicht hilft Dir r (read file) weiter, aber ob das vor oder hinter die tokenzeile gehört müsstest Du, bevor Du -i machst, mal prüfen:

1
sed '/tokenzeile/r temp' data

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

Wohnort: Wolfen (S-A)

Das Problem ist: für sed ist der Befehl auf jeden Fall beim Zeilenumbruch zu Ende:

track@track:~$ echo '1
2
3
4'  |  sed '2 s/2/a
b/'
sed: -e Ausdruck #1, Zeichen 7: Nicht beendeter `s'-Befehl 

Wenn Du dafür aber awk nimmst, geht es auch mit Mehrzeilern:

track@track:~$ echo 'a
> b
> c
> d'  >  einfügen.txt
track@track:~$ echo '1
2
3
4'  >  einfügen.hier
track@track:~$ ein=$( sed -n '/b/,/d/{/d/q; p}'  einfügen.txt )
track@track:~$ echo "$ein"
b
c
track@track:~$ awk -v "var=$ein" '{print} /3/ {print var}'  einfügen.hier
1
2
3
b
c
4 

Du siehst auch, dass ich das head durch einen Zeilenausschluss im sed ersetzt habe.

Wie das mit einer Variablengröße von 1 MB aussieht, habe ich nicht probiert. Aber das sollte gehen, denke ich.

Edit:
Alternativ kannst Du das Zeug natürlich auch - ganz ohne Variablen - mit sed in Abschnitten zusammenstoppeln:

track@track:~$ sed -n '1,/3/p'  einfügen.hier;  sed -n '/b/,/d/{/d/q; p}'  einfügen.txt;  sed '1,/3/d'  einfügen.hier
1
2
3
b
c
4 

LG,

track

KeyzerSoze

(Themenstarter)

Anmeldungsdatum:
28. September 2014

Beiträge: 120

Hallo,

Kannst Du in Worten sagen, was die Zeile

sed -i "/tokenzeile/ { N; s/tokenzeile\n/$input\n&/ }" data

bewirken soll?

Ich habe mich im Wesentlichen an folgendem Artikel orientiert: (und das einzufügende Wort durch eine Variable ersetzt) http://stackoverflow.com/questions/12248784/shell-bash-insert-text-before-a-certain-line#12248998

$ cat animals
dog
cat
dolphin
cat

$ sed  "/cat/ { N; s/cat\n/giraffe\n&/ }" animals
dog
giraffe
cat
dolphin
cat

1.    match a line with (/cat/)
2.    continue on next line (N)
3.    substitute the matched pattern with the insertion and the matched string, where & represent the matched string.

Dieser Code kommt schon sehr nahe an die Lösung heran:

sed '/tokenzeile/r temp' data

"Leider" steht aber die tokenzeile vor dem eingefügten Text und nicht hinter dem eingefügten Text, wie ich es gerne hätte.

(sed 'r temp /tokenzeile/' data liefert nicht den gewünschten Effekt.)
(sed 'r /tokenzeile/ temp' data liefert nicht den gewünschten Effekt.)

Jetzt versuche ich nochmals die Lösung von track zu verstehen, die für mich auf dem ersten Blick leider etwas kryptisch aussieht ( ☺ ), aber ich glaube das kriege ich hin ☺

Kann man eigentlich pauschal sagen, wann für Textbearbeitungen besser awk und wann besser sed geeignet ist?

KeyzerSoze

(Themenstarter)

Anmeldungsdatum:
28. September 2014

Beiträge: 120

Okay, ein kurzes Update:

./merge.sh

#! /bin/bash
sed  -n "/Anfang/,/Ende/{/Ende/q; p}" data1 > temp      #Text kopieren zwischen Token A (inkl.; Anfang) und Token B (exkl.; Ende) in file "temp"; 


input=$(cat temp)                                       #Diesen Text in Variable speichern

awk -v "var=$input" '{print} /Zeilevordereingefügtwerdensoll/ {print var}' data 

rm -f temp      # lösche temporäres file

liefert:

$ ./merge.sh 
./merge.sh: Zeile 10: /usr/bin/awk: Die Argumentliste ist zu lang

Wie das mit einer Variablengröße von 1 MB aussieht, habe ich nicht probiert. Aber das sollte gehen, denke ich.

Ich fürchte, also doch, falls ich deine Lösung nicht falsch implementiert habe.

Falls es wichtig ist (ich habe es leider zu erwähnen vergessen):

Die Zeile /Zeilevordereingefügtwerdensoll/ beinhaltet einen Unterstrich "_"

Müsste ich den irgendwie in

\_/

Slashes setzen?

Danke im Voraus!

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

Wohnort: Wolfen (S-A)

KeyzerSoze schrieb:

$ ./merge.sh 
./merge.sh: Zeile 10: /usr/bin/awk: Die Argumentliste ist zu lang

Dann scheint demnach zwar nicht die Variable selbst, aber die Länge der Kommandozeile mit den 1 MB Größe überfordert zu sein ...

Wie das mit einer Variablengröße von 1 MB aussieht, habe ich nicht probiert. Aber das sollte gehen, denke ich.

Ich fürchte, also doch, falls ich deine Lösung nicht falsch implementiert habe.

Ja.

Die Zeile /Zeilevordereingefügtwerdensoll/ beinhaltet einen Unterstrich "_"

Ich sehe keinen Grund, warum das Probleme machen sollte ...


Wenn Du jetzt sowieso die 2 Teile mit sed zusammenstoppelst, dann kannst Du Dir ja auch aussuchen, ob die Einfügung vor oder nach der Zeile mit dem Stichwort soll.

Guck Dir meinen Vorschlag oben noch mal genau an: der besteht nämlich aus 3 Teilen:

  1. drucken von der 1. bis einschließlich der Zielzeile - indem grundsätzlich erstmal nichts gedruckt wird, sondern nur ausdrücklich die 1. Zeile bis zur Zielzeile:

    1
    sed -n '1,/3/p'  einfügen.hier;
    
  2. Den Teil kennen wir schon, der ist das gleiche wie bei der Geschichte mit der Variablen:

    1
    sed -n '/b/,/d/{/d/q; p}'  einfügen.txt;
    
  3. durcken des Rests, ausschließlich der Zielzeile - indem erstmal grundsätzlich alles gedruckt wird, aber alles von 1. Zeile bis einschließlich der Zielzeile unterdrückt wird:

    1
    sed '1,/3/d'  einfügen.hier
    

KeyzerSoze schrieb:

"Leider" steht aber die tokenzeile vor dem eingefügten Text und nicht hinter dem eingefügten Text, wie ich es gerne hätte.

Ok, wenn Du es anders herum haben willst, brauchst Du also nur die Methoden 1 und 3 zu vertauschen (und den Bereich natürlich dann auch umdrehen: von der Zielzeile bis zum Ende), dann hast Du die Zielzeile hinter der Einfügung, genau wie Du es möchtest.

Kann man eigentlich pauschal sagen, wann für Textbearbeitungen besser awk und wann besser sed geeignet ist?

Nee, so pauschal nicht. Grundsätzlich ist awk komplexer und kann viel mehr, aber speziell bei Zeilen-Bereichen ist eigentlich nur sed so genial einfach.

LG,

track

KeyzerSoze

(Themenstarter)

Anmeldungsdatum:
28. September 2014

Beiträge: 120

Ok, wenn Du es anders herum haben willst, brauchst Du also nur die Methoden 1 und 3 zu vertauschen (und den Bereich natürlich dann auch umdrehen: von der Zielzeile bis zum Ende), dann hast Du die Zielzeile hinter der Einfügung, genau wie Du es möchtest.

Okay, ich glaube ich habe Deinen Gedankengang jetzt verstanden ☺

1. sed -n '1,/Trennpunkt/{/Trennpunkt/q; p}' data > newfile              # datafile bis zum Trennpunkt (exklusiv) behalten, den Rest löschen/abschneiden
2. sed -n '/Anfang/,/Trennpunkt2/{/Trennpunkt2/q; p}' data1 >> newfile   # extrahierten Text einfügen von data1
3. sed    '1,/Trennpunkt/d' data >> newfile                              # den Rest des Datafiles mit Trennpunkt inklusive einfügen

Was noch nicht klappt, ist Schritt 3., der Trennpunkt ist leider nicht dabei.

3. sed    '1,/Trennpunkt/{/Trennpunkt/q; d}' data >> newfile          # funktioniert nicht, ich verstehe nicht was hier passiert 

Wie könnte man alternativ die Zielzeile angeben? Um im 3. Schritt statt zu löschen, einfach zu kopieren. Die Anfangszeile ist ja 1,, aber die Zielzeile ??

Viele Grüße

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

Wohnort: Wolfen (S-A)

Ok, also nochmal, von oben:

track schrieb:

track@track:~$ echo 'a
b
c
d'  >  einfügen.txt
track@track:~$ echo '1
2
3
4'  >  einfügen.hier 
track@track:~$ sed -n '1,/3/p'  einfügen.hier;  sed -n '/b/,/d/{/d/q; p}'  einfügen.txt;  sed '1,/3/d'  einfügen.hier
1
2
3
b
c
4 

Das schloss die Trennmarke im 1. Block ein. Nun bauen wir nur den 1. Block um, so wie ich sagte:

track@track:~$ sed '/3/,$d'  einfügen.hier;  sed -n '/b/,/d/{/d/q; p}'  einfügen.txt;  sed '1,/3/d'  einfügen.hier
1
2
b
c
4 

Damit ist die Trennmarke nicht mehr im 1. Block vorhanden, aber auch nicht im 3. Block, sie fehlt. Also müssen wir jetzt auch noch den 3. Block umkrempeln:

track@track:~$ sed '/3/,$d'  einfügen.hier;  sed -n '/b/,/d/{/d/q; p}'  einfügen.txt;  sed -n '/3/,$p'  einfügen.hier
1
2
b
c
3
4 

Jetzt passt's. Und dann noch alles zusammen in eine Datei umleiten:

1
( sed '/3/,$d'  einfügen.hier;  sed -n '/b/,/d/{/d/q; p}'  einfügen.txt;  sed -n '/3/,$p'  einfügen.hier )  >  einfügen.neue_datei

Dadurch, dass Du die Befehle gruppierst, kannst Du sie gemeinsam in die neue Datei umleiten.

LG,

track

KeyzerSoze

(Themenstarter)

Anmeldungsdatum:
28. September 2014

Beiträge: 120

Okay

,$

ist also die Zeichensetzung für die letzte Zeile (bzw. bis zur letzten Zeile), im Vergleich zur ersten

1,'''

.

Dadurch, dass Du die Befehle gruppierst, kannst Du sie gemeinsam in die neue Datei umleiten.

Sehr interessant ☺

Vielen Dank für die klare und verständliche Hilfe ☺

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

Wohnort: Wolfen (S-A)

KeyzerSoze schrieb:

,$

ist also die Zeichensetzung für die letzte Zeile

Jo. - steht aber alles im sed- Manual drin !

LG,

track

Antworten |