Questa è probabilmente una soluzione complessa .
Sto cercando un operatore semplice come ">>", ma per anticipare.
Temo che non esista. Dovrò fare qualcosa del genere
mv myfile tmp cat myheader tmp> myfile
Qualcosa di più intelligente?
Questa è probabilmente una soluzione complessa .
Sto cercando un operatore semplice come ">>", ma per anticipare.
Temo che non esista. Dovrò fare qualcosa del genere
mv myfile tmp cat myheader tmp> myfile
Qualcosa di più intelligente?
Risposte:
L' hack di seguito è stata una risposta rapida e immediata che ha funzionato e ricevuto molti voti positivi. Quindi, man mano che la domanda diventava più popolare e più tempo passava, le persone indignate iniziarono a riferire che in qualche modo funzionava, ma potevano accadere cose strane, o semplicemente non funzionava affatto, quindi è stato furiosamente ridimensionato per un certo periodo. Così divertente.
La soluzione sfrutta l'implementazione esatta dei descrittori di file sul tuo sistema e, poiché l'implementazione varia in modo significativo tra i nix, il suo successo dipende interamente dal sistema, definitivamente non portatile e non dovrebbe essere invocato per qualcosa di vagamente importante.
Ora, nonostante tutto, la risposta è stata:
La creazione di un altro descrittore di file per il file ( exec 3<> yourfile) quindi scrivere su that ( >&3) sembra superare il dilemma di lettura / scrittura sullo stesso file. Funziona per me su file da 600K con awk. Tuttavia, provare lo stesso trucco usando 'cat' fallisce.
Passare il prependage come variabile a awk ( -v TEXT="$text") risolve il problema delle citazioni letterali che impedisce di fare questo trucco con 'sed'.
#!/bin/bash
text="Hello world
What's up?"
exec 3<> yourfile && awk -v TEXT="$text" 'BEGIN {print TEXT}{print}' yourfile >&3
Questo utilizza ancora un file temporaneo, ma almeno è su una riga:
echo "text" | cat - yourfile > /tmp/out && mv /tmp/out yourfile
Attestazione: BASH: antepone un testo / linee a un file
-dopo cat?
echo -n "text"
yourfiletratta di un collegamento simbolico, questo non farà ciò che desideri.
echo '0a
your text here
.
w' | ed some_file
ed è l'editor standard! 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: il tuo metodo non è garantito per funzionare e probabilmente fallirà se anteponi più di 4096 byte di roba (almeno è quello che succede con gnu awk, ma suppongo che altre implementazioni avranno vincoli simili). In questo caso non solo fallirà, ma entrerà in un ciclo infinito in cui leggerà il proprio output, facendo crescere il file fino a riempire tutto lo spazio disponibile.
Provalo tu stesso:
exec 3<>myfile && awk 'BEGIN{for(i=1;i<=1100;i++)print i}{print}' myfile >&3
(avviso: uccidilo dopo un po 'o riempirà il filesystem)
Inoltre, è molto pericoloso modificare i file in questo modo, ed è un pessimo consiglio, come se succedesse qualcosa mentre il file è in fase di modifica (crash, disco pieno), ti viene quasi garantito di rimanere con il file in uno stato incoerente.
Non è possibile senza un file temporaneo, ma ecco un oneliner
{ echo foo; cat oldfile; } > newfile && mv newfile oldfile
Puoi usare altri strumenti come ed o perl per farlo senza file temporanei.
Vale la pena notare che spesso è una buona idea generare in sicurezza il file temporaneo usando un'utilità come mktemp , almeno se lo script verrà mai eseguito con i privilegi di root. Ad esempio, potresti fare quanto segue (di nuovo in bash):
(tmpfile=`mktemp` && { echo "prepended text" | cat - yourfile > $tmpfile && mv $tmpfile yourfile; } )
Se ne hai bisogno sui computer che controlli, installa il pacchetto "moreutils" e usa "sponge". Quindi puoi fare:
cat header myfile | sponge myfile
{ echo "prepended text"; cat myfile } | sponge myfile
Usando un bash heredoc puoi evitare la necessità di un file tmp:
cat <<-EOF > myfile
$(echo this is prepended)
$(cat myfile)
EOF
Questo funziona perché $ (cat myfile) viene valutato quando viene valutato lo script bash, prima che venga eseguito il gatto con reindirizzamento.
supponendo che il file che si desidera modificare sia my.txt
$cat my.txt
this is the regular file
E il file che si desidera anteporre è l'intestazione
$ cat header
this is the header
Assicurati di avere una riga vuota finale nel file di intestazione.
Ora puoi anteporre a
$cat header <(cat my.txt) > my.txt
Si finisce con
$ cat my.txt
this is the header
this is the regular file
Per quanto ne so questo funziona solo in "bash".
this is the headerin my.txt. Anche dopo aver aggiornato Bash per 4.3.42(1)-releaseottenere lo stesso risultato.
<(...)) , quindi non c'è alcuna garanzia che my.txtvenga letto per intero , senza il quale questa tecnica non funzionerà.
Quando inizi a provare a fare cose che diventano difficili in shell-script, suggerirei vivamente di cercare di riscrivere lo script in un linguaggio di script "corretto" (Python / Perl / Ruby / etc)
Per quanto riguarda l'anticipazione di una riga a un file, non è possibile farlo tramite piping, poiché quando si esegue qualcosa del genere cat blah.txt | grep something > blah.txt, si cancella inavvertitamente il file. Esiste un piccolo comando di utilità chiamato spongeche puoi installare (lo fai cat blah.txt | grep something | sponge blah.txte bufferizza il contenuto del file, quindi lo scrive nel file). È simile a un file temporaneo ma non è necessario farlo esplicitamente. ma direi che è un requisito "peggiore" di, diciamo, Perl.
Potrebbe esserci un modo per farlo tramite awk o simili, ma se devi usare shell-script, penso che un file temporaneo sia di gran lunga il modo più semplice (/ solo?) ..
Come suggerisce Daniel Velkov, usa tee.
Per me, questa è una soluzione intelligente semplice:
{ echo foo; cat bar; } | tee bar > /dev/null
EDIT: questo è rotto. Vedi Comportamento strano quando si antepone un file con cat e tee
La soluzione alternativa al problema di sovrascrittura sta utilizzando tee:
cat header main | tee main > /dev/null
Quello che uso. Questo ti consente di specificare l'ordine, i caratteri aggiuntivi, ecc. Nel modo che preferisci:
echo -e "TEXTFIRSt\n$(< header)\n$(< my.txt)" > my.txt
PS: solo non funziona se i file contengono testo con barra rovesciata, perché viene interpretato come caratteri di escape
Principalmente per divertimento / shell golf, ma
ex -c '0r myheader|x' myfile
farà il trucco, e non ci sono condutture o reindirizzamenti. Naturalmente, vi / ex non è realmente per uso non interattivo, quindi vi lampeggerà brevemente.
Perché non usare semplicemente il comando ed (come già suggerito da fluffle qui)?
ed legge l'intero file in memoria ed esegue automaticamente una modifica del file sul posto!
Quindi, se il tuo file non è così grande ...
# 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
Ancora un'altra soluzione sarebbe usare handle di file aperti come suggerito da Jürgen Hötzel nell'output di reindirizzamento da sed 's / c / d /' myFile a myFile
echo cat > manipulate.txt
exec 3<manipulate.txt
# Prevent open file from being truncated:
rm manipulate.txt
sed 's/cat/dog/' <&3 > manipulate.txt
Tutto questo potrebbe essere messo su una sola riga, ovviamente.
Una variante della soluzione di cb0 per "nessun file temporaneo" per anteporre testo fisso:
echo "text to prepend" | cat - file_to_be_modified | ( cat > file_to_be_modified )
Ancora una volta, questo si basa sull'esecuzione di una sotto-shell - il (..) - per evitare che il gatto si rifiuti di avere lo stesso file per input e output.
Nota: mi è piaciuta questa soluzione. Tuttavia, nel mio Mac il file originale è perso (pensavo che non dovrebbe ma lo fa). Ciò potrebbe essere risolto scrivendo la soluzione come: echo "testo da anteporre" | cat - file_to_be_modified | cat> tmp_file; mv tmp_file file_to_be_modified
Ecco cosa ho scoperto:
echo -e "header \n$(cat file)" >file
ATTENZIONE: questo richiede un po 'più di lavoro per soddisfare le esigenze del PO.
Dovrebbe esserci un modo per far funzionare l'approccio sed con @shixilun nonostante i suoi dubbi. Ci deve essere un comando bash per sfuggire agli spazi bianchi durante la lettura di un file in una stringa sost sostitutiva sed (ad esempio sostituire i caratteri di nuova riga con '\ n'. Comandi di shell vise catpuò gestire caratteri non stampabili, ma non spazi bianchi, quindi questo non risolverà gli OP problema:
sed -i -e "1s/^/$(cat file_with_header.txt)/" file_to_be_prepended.txt
fallisce a causa delle nuove righe non elaborate nello script sostitutivo, che devono essere precedute da un carattere di continuazione della riga () e forse seguite da un &, per mantenere felice la shell e sed, come questa risposta SO
sed ha un limite di dimensione di 40 K per i comandi di sostituzione della ricerca non globali (nessun trailing / g dopo il modello), quindi probabilmente eviterebbe che il buffer spaventoso sovraccarichi i problemi di awk di cui l'anonimato ha avvertito.
sed -i -e "1s/^/new first line\n/" old_file.txt
Una soluzione con 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}"
Puoi anche fare:
printf "${new_line}\n$(cat ${target_file})" > "${target_file}"
Ma in quel caso devi essere sicuro che non ce ne sia da nessuna %parte, incluso il contenuto del file di destinazione, in quanto ciò può essere interpretato e rovinare i risultati.
echosembra un'opzione più sicura. echo "my new line\n$(cat my/file.txt)" > my/file.txt
${new_line}, non del file di destinazione
Puoi usare la riga di comando perl:
perl -i -0777 -pe 's/^/my_header/' tmp
Dove -i creerà una sostituzione in linea del file e -0777 ridurrà l'intero file e farà ^ corrispondere solo all'inizio. -pe stamperà tutte le righe
O se my_header è un file:
perl -i -0777 -pe 's/^/`cat my_header`/e' tmp
Dove / e consentirà una valutazione del codice nella sostituzione.
Mi sta piacendo @ di fluffle ed approccio al meglio. Dopotutto, le opzioni della riga di comando di qualsiasi strumento rispetto ai comandi dell'editor con script sono essenzialmente la stessa cosa qui; non vedere una soluzione di editor con script "pulizia" minore o quant'altro.
Ecco il mio one-liner aggiunto a .git/hooks/prepare-commit-msganteporre un .gitmessagefile in repo per eseguire il commit dei messaggi:
echo -e "1r $PWD/.gitmessage\n.\nw" | ed -s "$1"
Esempio .gitmessage:
# Commit message formatting samples:
# runlevels: boot +consolekit -zfs-fuse
#
Lo sto facendo 1rinvece 0r, perché ciò lascerà la riga vuota pronta per la scrittura in cima al file dal modello originale. Non mettere una linea vuota sopra la tua .gitmessageallora, finirai con due linee vuote.-selimina l'output delle informazioni diagnostiche di ed.
In connessione con questo, ho scoperto che per vim-buffs è anche bello avere:
[core]
editor = vim -c ':normal gg'
Penso che questa sia la variante più pulita di ed:
cat myheader | { echo '0a'; cat ; echo -e ".\nw";} | ed myfile
come una funzione:
function prepend() { { echo '0a'; cat ; echo -e ".\nw";} | ed $1; }
cat myheader | prepend myfile
Se stai scrivendo script in BASH, in realtà, puoi semplicemente rilasciare:
cat - yourfile / tmp / out && mv / tmp / out yourfile
Questo è in realtà nell'esempio complesso che tu stesso hai pubblicato nella tua domanda.
cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfile, tuttavia è sufficientemente diverso dalla mia risposta che dovrebbe essere la propria risposta.
IMHO non esiste una soluzione shell (e non sarà mai una) che funzionerebbe in modo coerente e affidabile indipendentemente dalle dimensioni dei due file myheadere myfile. Il motivo è che se si desidera farlo senza ricorrere a un file temporaneo (e senza lasciare che la shell ricorra silenziosamente in un file temporaneo, ad esempio attraverso costrutti come exec 3<>myfile, eseguire il piping atee , ecc.
La "vera" soluzione che stai cercando ha bisogno di giocherellare con il filesystem, quindi non è disponibile nello spazio utente e dipende dalla piattaforma: stai chiedendo di modificare il puntatore del filesystem in uso dal myfilevalore corrente del puntatore del filesystem per myheadere sostituire nel filesystem il EOFof myheadercon un collegamento concatenato all'indirizzo del filesystem corrente indicato da myfile. Questo non è banale e ovviamente non può essere fatto da un non superutente, e probabilmente nemmeno dal superutente ... Gioca con gli inode, ecc.
Puoi più o meno falsificare questo utilizzando i dispositivi loop, però. Vedi ad esempio questo thread SO .
mktemp? In seguito puoi sempre ripulire il file temporaneo ...