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?

Elmarco
quelle
Was ist los mit mktemp? Sie können die temporäre Datei danach jederzeit bereinigen ...
Candu
Siehe auch
Anton Tarasenko

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
John Mee
quelle
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

Jason Navarrete
quelle
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
31
echo '0a
your text here
.
w' | ed some_file

ed ist der Standard Editor! http://www.gnu.org/fun/jokes/ed.msg.html

Fluffle
quelle
7
oder, anstatt den einzufügenden Text fest zu 0r header.file
codieren
Da die Frage nach einem echo -e '0a\nyour text here\n.\nw' | ed some_file
Einzeiler gestellt
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.

anonym
quelle
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.

Vinko Vrsalovic
quelle
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; } )
ehdr
quelle
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
user2227573
quelle
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.

Eric Woodruff
quelle
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".

cb0
quelle
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.

dbr
quelle
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
Max Tsepkov
quelle
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
Daniel Velkov
quelle
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

nemisj
quelle
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.

benjwadams
quelle
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.

torf
quelle
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

Alberto A. Medina
quelle
1
Ich finde auch die Originaldatei verloren. Ubuntu Lucid.
Igel
2

Folgendes habe ich entdeckt:

echo -e "header \n$(cat file)" >file
Hammad Akhwand
quelle
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
schwach
quelle
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.

Kochfelder
quelle
2
sed -i -e "1s/^/new first line\n/" old_file.txt
Shixilun
quelle
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
JuSchu
quelle
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)'

crizCraig
quelle
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.

user137369
quelle
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.

user5704481
quelle
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.

vinyll
quelle
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'
lkraav
quelle
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
Jayen
quelle
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
Dave Butler
quelle
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.

Tim Kennedy
quelle
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 .

Jaybee
quelle