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?
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?
Antworten:
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
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
-
danach cat
?
echo -n "text"
yourfile
es sich um einen Symlink handelt, wird dies nicht das tun, was Sie wollen.
echo '0a
your text here
.
w' | ed some_file
ed ist der Standard Editor! http://www.gnu.org/fun/jokes/ed.msg.html
0r header.file
echo -e '0a\nyour text here\n.\nw' | ed some_file
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.
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.
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; } )
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
{ echo "prepended text"; cat myfile } | sponge myfile
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.
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".
this is the header
Am Ende hatte ich zwei Zeilen in my.txt. Selbst nachdem ich Bash aktualisiert 4.3.42(1)-release
habe, erhalte ich das gleiche Ergebnis.
<(...)
) schreibt. Daher gibt es keine Garantie, dass diese my.txt
vollständig im Voraus gelesen wird , ohne die diese Technik nicht funktioniert.
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 sponge
Sie 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.
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
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
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
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.
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.
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
Folgendes habe ich entdeckt:
echo -e "header \n$(cat file)" >file
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 vis
und cat
kö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.
sed -i -e "1s/^/new first line\n/" old_file.txt
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.
echo
scheint eine sicherere Option zu sein. echo "my new line\n$(cat my/file.txt)" > my/file.txt
${new_line}
, nicht vor der Zieldatei
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.
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-msg
an den eine In-Repo- .gitmessage
Datei 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 1r
stattdessen 0r
, weil dadurch die leere schreibfertige Zeile über der Datei aus der Originalvorlage verbleibt. Setzen Sie dann keine leere Zeile auf Ihre, .gitmessage
da Sie am Ende zwei leere Zeilen haben.-s
unterdrü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'
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
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.
cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfile
unterscheidet sich jedoch ausreichend von meiner Antwort, sodass es sich um eine eigene Antwort handeln sollte.
IMHO gibt es keine Shell-Lösung (und wird es auch nie geben), die unabhängig von der Größe der beiden Dateien myheader
und 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<>myfile
Piping 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 myfile
auf den aktuellen Wert des Dateisystemzeigers ändern für myheader
und ersetzen Sie im Dateisystem das EOF
von myheader
durch 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 .
mktemp
? Sie können die temporäre Datei danach jederzeit bereinigen ...