Shell einen Liner, um einer Datei voran zu stellen


131

Dies ist wahrscheinlich eine komplexe Lösung .

Ich suche einen einfachen Operator wie ">>", aber zum Voranstellen.

Ich fürchte, es existiert nicht. Ich muss so etwas tun

 mv myfile tmp
 cat myheader tmp> myfile

Etwas schlauer?


Was ist los mit mktemp? Sie können die temporäre Datei danach jederzeit bereinigen ...
Candu

Antworten:


29

Der Hack unten war eine schnelle Antwort von der Stange, die funktionierte und viele positive Stimmen erhielt. Dann, als die Frage populärer wurde und mehr Zeit verging, berichteten empörte Leute, dass es irgendwie funktionierte, aber seltsame Dinge passieren konnten, oder es funktionierte einfach überhaupt nicht, so dass es eine Zeit lang wütend abgelehnt wurde. So ein Spaß.

Die Lösung nutzt die genaue Implementierung von Dateideskriptoren auf Ihrem System aus. Da die Implementierung zwischen den Nixen erheblich variiert, ist ihr Erfolg vollständig systemabhängig, definitiv nicht portierbar und sollte nicht für etwas verwendet werden, das auch nur vage wichtig ist.

Nun, mit all dem aus dem Weg war die Antwort:


Das Erstellen eines anderen Dateideskriptors für die Datei ( exec 3<> yourfile), von wo aus in diese ( >&3) geschrieben wird, scheint das Lesen / Schreiben in demselben Dateidilemma zu überwinden. Funktioniert für mich bei 600K-Dateien mit awk. Es schlägt jedoch fehl, denselben Trick mit 'cat' zu versuchen.

Wenn Sie das Präfix als Variable an awk ( -v TEXT="$text") übergeben, wird das Problem der wörtlichen Anführungszeichen überwunden, wodurch verhindert wird, dass dieser Trick mit 'sed' ausgeführt wird.

#!/bin/bash
text="Hello world
What's up?"

exec 3<> yourfile && awk -v TEXT="$text" 'BEGIN {print TEXT}{print}' yourfile >&3

3
WARNUNG: Überprüfen Sie die Ausgabe, um sicherzustellen, dass sich außer der ersten Zeile nichts geändert hat. Ich habe diese Lösung verwendet, und obwohl sie zu funktionieren schien, bemerkte ich, dass einige neue Zeilen hinzugefügt wurden. Ich verstehe nicht, woher diese stammen, daher kann ich nicht sicher sein, ob diese Lösung wirklich ein Problem hat, aber Vorsicht.
Conradlee

1
Beachten Sie im obigen Bash-Beispiel, dass sich die doppelten Anführungszeichen über den Wagenrücklauf erstrecken, um das Voranstellen mehrerer Zeilen zu demonstrieren. Überprüfen Sie, ob Ihre Shell und Ihr Texteditor kooperativ sind. \ r \ n fällt mir ein.
John Mee

4
Verwenden Sie dies mit Vorsicht! Siehe den folgenden Kommentar für warum: stackoverflow.com/questions/54365/…
Alex

3
Wenn es jetzt unzuverlässig ist, wie wäre es, wenn Sie Ihre Antwort mit einer besseren Lösung aktualisieren?
Zakdances

Ein Hack ist genau dann nützlich, wenn genau bekannt ist, warum er funktioniert und unter welchen sorgfältig beobachteten Bedingungen . Ungeachtet Ihres Haftungsausschlusses erfüllt Ihr Hack diese Kriterien nicht ("vollständig systemabhängig", "scheint zu überwinden", "funktioniert für mich"). Wenn wir Ihren Haftungsausschluss ernst nehmen, sollte niemand Ihren Hack verwenden - und ich stimme zu. Also, was bleibt uns übrig? Eine laute Ablenkung. Ich sehe zwei Möglichkeiten: (a) Löschen Sie Ihre Antwort oder (b) verwandeln Sie sie in eine warnende Geschichte, die erklärt, warum dieser Ansatz - so verlockend er auch sein mag - im Allgemeinen nicht funktionieren kann .
mklement0

102

Dies verwendet immer noch eine temporäre Datei, aber zumindest in einer Zeile:

echo "text" | cat - yourfile > /tmp/out && mv /tmp/out yourfile

Credit: BASH: Stellen Sie einer Datei einen Text / Zeilen vor


4
Was macht -danach cat?
Makbol

Gibt es eine Möglichkeit, "Text" ohne neue Zeile hinzuzufügen?
Chishaku

@chishakuecho -n "text"
Sparhawk

3
Eine Warnung: Wenn yourfilees sich um einen Symlink handelt, wird dies nicht das tun, was Sie wollen.
Dshepherd

Unterscheidet sich die Antwort davon: echo "text"> / tmp / out; cat yourfile >> / tmp / out; mv / tmp / out yourfile ??
Richard


20

John Mee: Es ist nicht garantiert, dass Ihre Methode funktioniert, und sie wird wahrscheinlich fehlschlagen, wenn Sie mehr als 4096 Byte voranstellen (zumindest passiert das mit gnu awk, aber ich nehme an, dass andere Implementierungen ähnliche Einschränkungen haben). In diesem Fall schlägt dies nicht nur fehl, sondern es tritt auch in eine Endlosschleife ein, in der die eigene Ausgabe gelesen wird, wodurch die Datei wächst, bis der gesamte verfügbare Speicherplatz belegt ist.

Probieren Sie es aus:

exec 3<>myfile && awk 'BEGIN{for(i=1;i<=1100;i++)print i}{print}' myfile >&3

(Warnung: Töte es nach einer Weile oder es wird das Dateisystem füllen)

Darüber hinaus ist es sehr gefährlich, Dateien auf diese Weise zu bearbeiten, und es ist ein sehr schlechter Rat, als ob etwas passiert, während die Datei bearbeitet wird (Absturz, Festplatte voll). Es ist fast garantiert, dass sich die Datei in einem inkonsistenten Zustand befindet.


6
Dies sollte wahrscheinlich ein Kommentar sein, keine separate Antwort.
lkraav

10
@Ikraav: Vielleicht, aber der "Kommentar" ist sehr lang und ausführlich (und nützlich), er wird nicht gut aussehen und passt vielleicht sowieso nicht zu StackOverflows begrenzter Kommentarkapazität.
Hendy Irawan

18

Ohne temporäre Datei nicht möglich, aber hier geht ein Oneliner

{ echo foo; cat oldfile; } > newfile && mv newfile oldfile

Sie können andere Tools wie ed oder perl verwenden, um dies ohne temporäre Dateien zu tun.


16

Es kann erwähnenswert sein, dass es oft eine gute Idee ist , die temporäre Datei mit einem Dienstprogramm wie mktemp sicher zu generieren , zumindest wenn das Skript jemals mit Root-Rechten ausgeführt wird. Sie könnten zum Beispiel Folgendes tun (erneut in Bash):

(tmpfile=`mktemp` && { echo "prepended text" | cat - yourfile > $tmpfile && mv $tmpfile yourfile; } )

15

Wenn Sie dies auf Computern benötigen, die Sie steuern, installieren Sie das Paket "moreutils" und verwenden Sie "sponge". Dann können Sie tun:

cat header myfile | sponge myfile

Dies funktionierte gut für mich in Kombination mit der Antwort von @Vinko Vrsalovic:{ echo "prepended text"; cat myfile } | sponge myfile
Jesse Hallett

13

Mit einem Bash-Heredoc können Sie die Notwendigkeit einer tmp-Datei vermeiden:

cat <<-EOF > myfile
  $(echo this is prepended)
  $(cat myfile)
EOF

Dies funktioniert, weil $ (cat myfile) ausgewertet wird, wenn das Bash-Skript ausgewertet wird, bevor die Katze mit Umleitung ausgeführt wird.


9

Angenommen, die Datei, die Sie bearbeiten möchten, ist my.txt

$cat my.txt    
this is the regular file

Und die Datei, die Sie voranstellen möchten, ist Header

$ cat header
this is the header

Stellen Sie sicher, dass die Header-Datei eine letzte leere Zeile enthält.
Jetzt können Sie es mit voranstellen

$cat header <(cat my.txt) > my.txt

Sie enden mit

$ cat my.txt
this is the header
this is the regular file

Soweit ich weiß, funktioniert dies nur in "Bash".


Warum funktioniert das? Verursachen die Klammern, dass die mittlere Katze in einer separaten Shell ausgeführt wird und die gesamte Datei auf einmal ausgegeben wird?
Catskul

Ich denke, die <(cat my.txt) erstellt irgendwo im Dateisystem eine temporäre Datei. Diese Datei wird dann gelesen, bevor die Originaldatei geändert wird.
cb0

Jemand hat mir einmal gezeigt, was Sie mit der Syntax "<()" machen können. Ich habe jedoch nie erfahren, wie diese Methode heißt, und ich konnte auch nichts in der Bash-Manpage finden. Wenn jemand mehr darüber weiß, lass es mich wissen.
cb0

1
Hat bei mir nicht funktioniert. Ich weiß nicht warum. Ich verwende GNU Bash, Version 3.2.57 (1) -Veröffentlichung (x86_64-apple-darwin14), ich bin auf OS X Yosemite. this is the headerAm Ende hatte ich zwei Zeilen in my.txt. Selbst nachdem ich Bash aktualisiert 4.3.42(1)-releasehabe, erhalte ich das gleiche Ergebnis.
Kohányi Róbert

1
Bash erstellt keine temporäre Datei, sondern ein FIFO (Pipe), in das der Befehl in der Prozesssubstitution ( <(...)) schreibt. Daher gibt es keine Garantie, dass diese my.txtvollständig im Voraus gelesen wird , ohne die diese Technik nicht funktioniert.
mklement0

9

Wenn Sie versuchen, Dinge zu tun, die im Shell-Skript schwierig werden, würde ich dringend empfehlen, das Skript in einer "richtigen" Skriptsprache (Python / Perl / Ruby / etc) neu zu schreiben.

Wenn Sie einer Datei eine Zeile voranstellen, ist dies nicht über Piping möglich. Wenn Sie so etwas tun cat blah.txt | grep something > blah.txt, wird die Datei versehentlich gelöscht. Es gibt einen kleinen Dienstprogrammbefehl, den spongeSie installieren können (Sie tun es)cat blah.txt | grep something | sponge blah.txt und puffert den Inhalt der Datei und schreibt ihn dann in die Datei). Es ähnelt einer temporären Datei, aber Sie müssen dies nicht explizit tun. aber ich würde sagen, das ist eine "schlechtere" Anforderung als beispielsweise Perl.

Es mag eine Möglichkeit geben, dies über awk oder ähnliches zu tun, aber wenn Sie ein Shell-Skript verwenden müssen, denke ich, dass eine temporäre Datei bei weitem die einfachste (/ einzige?) Möglichkeit ist.


7

Wie Daniel Velkov vorschlägt, verwenden Sie Tee.
Für mich ist das eine einfache, intelligente Lösung:

{ echo foo; cat bar; } | tee bar > /dev/null

7

EDIT: Das ist kaputt. Siehe Seltsames Verhalten beim Voranstellen einer Datei mit Katze und Tee

Die Problemumgehung für das Überschreibproblem lautet tee:

cat header main | tee main > /dev/null

Eine Weile aufgehängt. Beendet mit "tee: main: Kein Platz mehr auf dem Gerät". Haupt war mehrere GB, obwohl beide Originaltextdateien nur wenige KB waren.
Marcos

3
Wenn die von Ihnen veröffentlichte Lösung fehlerhaft ist (und tatsächlich die Festplatten der Benutzer ausfüllt), tun Sie der Community einen Gefallen und löschen Sie Ihre Antwort.
Aioobe

1
Können Sie mich auf eine Richtlinie verweisen, die besagt, dass falsche Antworten gelöscht werden sollten?
Daniel Velkov

3

Die, die ich benutze. In diesem Fall können Sie die Reihenfolge, zusätzliche Zeichen usw. so angeben, wie Sie es möchten:

echo -e "TEXTFIRSt\n$(< header)\n$(< my.txt)" > my.txt

PS: Nur funktioniert es nicht, wenn Dateien Text mit Backslash enthalten, da sie als Escape-Zeichen interpretiert werden


3

Meistens zum Spaß / Shell Golf, aber

ex -c '0r myheader|x' myfile

wird den Trick machen, und es gibt keine Pipelines oder Umleitungen. Natürlich ist vi / ex nicht wirklich für den nicht interaktiven Gebrauch gedacht, daher blinkt vi kurz auf.


Aus meiner Sicht das beste Rezept, da es einen etablierten Befehl verwendet und dies auf einfache Weise tut.
Diego

2

Warum nicht einfach den Befehl ed verwenden (wie bereits von fluffle hier vorgeschlagen)?

ed liest die gesamte Datei in den Speicher und führt automatisch eine direkte Dateibearbeitung durch!

Also, wenn Ihre Datei nicht so groß ist ...

# cf. "Editing files with the ed text editor from scripts.",
# http://wiki.bash-hackers.org/doku.php?id=howto:edit-ed

prepend() {
   printf '%s\n' H 1i "${1}" . wq | ed -s "${2}"
}

echo 'Hello, world!' > myfile
prepend 'line to prepend' myfile

Eine weitere Problemumgehung wäre die Verwendung offener Dateihandles, wie von Jürgen Hötzel in der Umleitungsausgabe von seds / c / d / 'myFile zu myFile vorgeschlagen

echo cat > manipulate.txt
exec 3<manipulate.txt
# Prevent open file from being truncated:
rm manipulate.txt
sed 's/cat/dog/' <&3 > manipulate.txt

All dies könnte natürlich in einer einzigen Zeile stehen.


2

Eine Variante der Lösung von cb0 für "keine temporäre Datei", um festen Text voranzustellen:

echo "text to prepend" | cat - file_to_be_modified | ( cat > file_to_be_modified ) 

Dies hängt wiederum von der Ausführung der Sub-Shell ab - der (..) -, um zu vermeiden, dass die Katze sich weigert, dieselbe Datei für die Eingabe und Ausgabe zu haben.

Hinweis: Diese Lösung hat mir gefallen. Auf meinem Mac geht jedoch die Originaldatei verloren (dachte, es sollte nicht, aber es tut es). Dies könnte behoben werden, indem Sie Ihre Lösung wie folgt schreiben: echo "text to prepend" | cat - file_to_be_modified | cat> tmp_file; mv tmp_file file_to_be_modified


1
Ich finde auch die Originaldatei verloren. Ubuntu Lucid.
Igel

2

Folgendes habe ich entdeckt:

echo -e "header \n$(cat file)" >file

1
Dies ist das gleiche wie der frühere Vorschlag von @nemisj Nein?
Igel

2
sed -i -e '1rmyheader' -e '1{h;d}' -e '2{x;G}' myfile

2

WARNUNG: Dies erfordert etwas mehr Arbeit, um die Anforderungen des OP zu erfüllen.

Es sollte eine Möglichkeit geben, den sed-Ansatz von @shixilun trotz seiner Bedenken zum Laufen zu bringen. Es muss ein Bash-Befehl vorhanden sein, um Leerzeichen beim Lesen einer Datei in eine sed-Ersatzzeichenfolge zu umgehen (z. B. Zeilenumbruchzeichen durch '\ n' ersetzen. Shell-Befehle visund catkönnen nicht druckbare Zeichen verarbeiten, jedoch keine Leerzeichen, sodass die OPs nicht gelöst werden Problem:

sed -i -e "1s/^/$(cat file_with_header.txt)/" file_to_be_prepended.txt

schlägt aufgrund der rohen Zeilenumbrüche im Ersatzskript fehl, denen ein Zeilenfortsetzungszeichen () vorangestellt werden muss und möglicherweise ein & folgt, um die Shell und sed glücklich zu halten, wie diese SO-Antwort

sed hat eine Größenbeschränkung von 40 KB für nicht globale Such-Ersetzungs-Befehle (kein nachfolgendes / g nach dem Muster), sodass wahrscheinlich die beängstigenden Pufferüberlaufprobleme von awk vermieden werden, vor denen anonym gewarnt wurde.


2
sed -i -e "1s/^/new first line\n/" old_file.txt

genau das, wonach ich gesucht habe, aber in der Tat nicht die richtige Antwort auf die Frage
masterxilo

2

Mit $ (Befehl) können Sie die Ausgabe eines Befehls in eine Variable schreiben. Also habe ich es in drei Befehlen in einer Zeile und ohne temporäre Datei gemacht.

originalContent=$(cat targetfile) && echo "text to prepend" > targetfile && echo "$originalContent" >> targetfile

1

Wenn Sie eine große Datei (in meinem Fall einige hundert Kilobyte) und Zugriff auf Python haben, ist dies viel schneller als catPipe-Lösungen:

python -c 'f = "filename"; t = open(f).read(); open(f, "w").write("text to prepend " + t)'


1

Eine Lösung mit printf:

new_line='the line you want to add'
target_file='/file you/want to/write to'

printf "%s\n$(cat ${target_file})" "${new_line}" > "${target_file}"

Sie könnten auch tun:

printf "${new_line}\n$(cat ${target_file})" > "${target_file}"

In diesem Fall müssen Sie jedoch sicherstellen, dass es %nirgendwo etwas gibt, einschließlich des Inhalts der Zieldatei, da dies interpretiert werden kann und Ihre Ergebnisse vermasseln kann.


2
Dies scheint in die Luft zu jagen (und Ihre Datei abzuschneiden!), Wenn Ihre Zieldatei etwas enthält, das printf als Formatierungsoption interpretiert.
Alan H.

echoscheint eine sicherere Option zu sein. echo "my new line\n$(cat my/file.txt)" > my/file.txt
Alan H.

@ AlanH. Ich warne vor den Gefahren in der Antwort.
user137369

Eigentlich haben Sie nur vor Prozenten in gewarnt ${new_line}, nicht vor der Zieldatei
Alan H.

Könnten Sie expliziter sein? Es ist eine wirklich große Einschränkung IMO. "Überall" macht dies nicht sehr deutlich.
Alan H.

1

Sie können die Perl-Befehlszeile verwenden:

perl -i -0777 -pe 's/^/my_header/' tmp

Wobei -i einen Inline-Ersatz für die Datei erstellt und -0777 die gesamte Datei schlürft und ^ nur mit dem Anfang übereinstimmt. -pe druckt alle Zeilen

Oder wenn my_header eine Datei ist:

perl -i -0777 -pe 's/^/`cat my_header`/e' tmp

Wobei das / e eine Auswertung des Codes in der Ersetzung zulässt.


0
current=`cat my_file` && echo 'my_string' > my_file && echo $current >> my_file

Dabei ist "my_file" die Datei, der "my_string" vorangestellt werden soll.


0

Ich mag @ fluffles ed Ansatz am besten. Schließlich sind die Befehlszeilenwechsel eines Tools im Vergleich zu Skripteditorbefehlen hier im Wesentlichen dasselbe. Ich sehe nicht, dass eine Skripteditor-Lösung "Sauberkeit" weniger oder so ist.

Hier ist mein Einzeiler, .git/hooks/prepare-commit-msgan den eine In-Repo- .gitmessageDatei angehängt wird , um Nachrichten festzuschreiben:

echo -e "1r $PWD/.gitmessage\n.\nw" | ed -s "$1"

Beispiel .gitmessage:

# Commit message formatting samples:
#       runlevels: boot +consolekit -zfs-fuse
#

Ich mache 1rstattdessen 0r, weil dadurch die leere schreibfertige Zeile über der Datei aus der Originalvorlage verbleibt. Setzen Sie dann keine leere Zeile auf Ihre, .gitmessageda Sie am Ende zwei leere Zeilen haben.-sunterdrückt die Ausgabe von Diagnoseinformationen von ed.

Im Zusammenhang damit habe ich festgestellt, dass es für Vim-Fans auch gut ist, Folgendes zu haben:

[core]
        editor = vim -c ':normal gg'

0

Variablen, ftw?

NEWFILE=$(echo deb http://mirror.csesoc.unsw.edu.au/ubuntu/ $(lsb_release -cs) main universe restricted multiverse && cat /etc/apt/sources.list)
echo "$NEWFILE" | sudo tee /etc/apt/sources.list

0

Ich denke, dies ist die sauberste Variante von ed:

cat myheader | { echo '0a'; cat ; echo -e ".\nw";} | ed myfile

als eine Funktion:

function prepend() { { echo '0a'; cat ; echo -e ".\nw";} | ed $1; }

cat myheader | prepend myfile

0

Wenn Sie in BASH Skripte erstellen, können Sie Folgendes tun:

cat - yourfile / tmp / out && mv / tmp / out yourfile

Das ist eigentlich in dem komplexen Beispiel, das Sie selbst in Ihrer eigenen Frage gepostet haben.


Ein Redakteur schlug vor, dies zu ändern, dies cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfileunterscheidet sich jedoch ausreichend von meiner Antwort, sodass es sich um eine eigene Antwort handeln sollte.
Tim Kennedy

0

IMHO gibt es keine Shell-Lösung (und wird es auch nie geben), die unabhängig von der Größe der beiden Dateien myheaderund konsistent und zuverlässig funktioniert myfile. Der Grund dafür ist, dass Sie dies tun möchten, ohne zu einer temporären Datei zurückzukehren (und ohne dass die Shell stillschweigend zu einer temporären Datei zurückkehrt, z. B. durch Konstrukte wie exec 3<>myfilePiping totee usw.).

Die "echte" Lösung, nach der Sie suchen, muss mit dem Dateisystem herumspielen. Daher ist sie im Benutzerbereich nicht verfügbar und plattformabhängig: Sie möchten den verwendeten Dateisystemzeiger myfileauf den aktuellen Wert des Dateisystemzeigers ändern für myheaderund ersetzen Sie im Dateisystem das EOFvon myheaderdurch einen verketteten Link zur aktuellen Dateisystemadresse, auf die verwiesen wird myfile. Dies ist nicht trivial und kann offensichtlich nicht von einem Nicht-Superuser und wahrscheinlich auch nicht vom Superuser durchgeführt werden ... Spielen Sie mit Inodes usw.

Sie können dies jedoch mehr oder weniger mit Loop-Geräten vortäuschen. Siehe zum Beispiel diesen SO-Thread .

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.