Shell un liner per anteporre a un file


Risposte:


29

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

3
ATTENZIONE: controlla l'output per assicurarti che nulla sia cambiato tranne la prima riga. Ho usato questa soluzione e, sebbene sembrasse funzionare, ho notato che alcune nuove linee sembravano essere state aggiunte. Non capisco da dove vengano, quindi non posso essere sicuro che questa soluzione abbia davvero un problema, ma attenzione.
Conradlee,

1
Si noti nell'esempio bash sopra che le virgolette doppie si estendono sul ritorno a capo al fine di dimostrare anteporre più righe. Verifica che la shell e l'editor di testo siano cooperativi. \ r \ n mi viene in mente.
John Mee,

4
Usa questo con cautela! Vedere il seguente commento per il motivo: stackoverflow.com/questions/54365/…
Alex

3
Se ora non è affidabile, che ne dici di aggiornare la tua risposta con una soluzione migliore?
zakdances,

Un hack è utile se e solo se è noto con precisione perché funziona e, quindi, sotto quali vincoli attentamente osservati . Nonostante il tuo disclaimer, il tuo hack non soddisfa questi criteri ("interamente dipendente dal sistema", "sembra superare", "funziona per me"). Se prendiamo sul serio il tuo disclaimer, nessuno dovrebbe usare il tuo hack - e sono d'accordo. Allora, cosa ci rimane? Una distrazione rumorosa. Vedo due opzioni: (a) elimina la tua risposta, oppure (b) trasformala in un racconto cautelativo che spiega perché, per quanto allettante, questo approccio non può funzionare in generale .
mklement0

103

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


4
Cosa sta facendo -dopo cat?
Makol

C'è un modo per aggiungere "testo" senza una nuova riga dopo?
Chishaku,

@chishakuecho -n "text"
Sparhawk il

3
Un avvertimento: se si yourfiletratta di un collegamento simbolico, questo non farà ciò che desideri.
pastore

La risposta è diversa da questa: echo "text"> / tmp / out; cat yourfile >> / tmp / out; mv / tmp / out yourfile ??
Richard,


20

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.


6
Questo dovrebbe probabilmente essere un commento, non una risposta separata.
lkraav,

10
@Ikraav: forse, ma il "commento" è molto lungo ed elaborato (e utile), non avrà un bell'aspetto e forse non si adatta comunque alla limitata capacità di commento di StackOverflow.
Hendy Irawan,

18

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.


16

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; } )

15

Se ne hai bisogno sui computer che controlli, installa il pacchetto "moreutils" e usa "sponge". Quindi puoi fare:

cat header myfile | sponge myfile

Questo ha funzionato bene per me se combinato con la risposta di @Vinko Vrsalovic:{ echo "prepended text"; cat myfile } | sponge myfile
Jesse Hallett,

13

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.


9

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".


perché funziona? Le parentesi fanno sì che il gatto di mezzo venga eseguito in una shell separata e scarichi l'intero file contemporaneamente?
Catskul,

Penso che <(cat my.txt) crei un file temporaneo da qualche parte nel file system. Questo file viene quindi letto prima di modificare il file originale.
cb0

Qualcuno una volta mi ha mostrato cosa puoi fare con la sintassi "<()". Comunque non ho mai imparato come si chiama questo metodo e non sono riuscito a trovare qualcosa nella manpage di bash. Se qualcuno ne sa di più, per favore fatemelo sapere.
cb0

1
Non ha funzionato per me. Non so perché. Sto usando GNU bash, versione 3.2.57 (1) -release (x86_64-apple-darwin14), sono su OS X Yosemite. Ho finito con due righe di this is the headerin my.txt. Anche dopo aver aggiornato Bash per 4.3.42(1)-releaseottenere lo stesso risultato.
Kohányi Róbert,

1
Bash non crea un file temporaneo, ma crea un FIFO (pipe) in cui scrive il comando nel processo substitution ( <(...)) , quindi non c'è alcuna garanzia che my.txtvenga letto per intero , senza il quale questa tecnica non funzionerà.
mklement0,

9

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?) ..


7

Come suggerisce Daniel Velkov, usa tee.
Per me, questa è una soluzione intelligente semplice:

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

7

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

Appeso per un po '. Terminato con "tee: main: nessuno spazio sul dispositivo". principale erano diversi GB, sebbene entrambi i file di testo originali fossero solo pochi KB.
Marcos,

3
Se la soluzione che hai pubblicato non funziona (e in effetti riempie gli hard disk degli utenti), fai un favore alla community ed elimina la tua risposta.
aioobe,

1
Potete indicarmi una linea guida che dice che le risposte sbagliate dovrebbero essere eliminate?
Daniel Velkov,

3

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


3

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.


La migliore ricetta dal mio punto di vista in quanto utilizza un comando storico e lo fa in modo semplice.
Diego,

2

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.


2

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


1
Anch'io trovo il file originale perso. Ubuntu Lucid.
Riccio

2

Ecco cosa ho scoperto:

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

1
È lo stesso del precedente suggerimento di @nemisj No?
Riccio

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

2

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.


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

esattamente quello che stavo cercando, ma in effetti non la risposta giusta alla domanda
masterxilo,

2

Con $ (comando) è possibile scrivere l'output di un comando in una variabile. Quindi l'ho fatto con tre comandi in una riga e nessun file temporaneo.

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

1

Se hai un file di grandi dimensioni (poche centinaia di kilobyte nel mio caso) e accedi a Python, questo è molto più veloce delle catsoluzioni pipe:

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


1

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.


2
Questo sembra esplodere (e troncare il tuo file!) Se hai qualcosa nel tuo file di destinazione che printf interpreta come un'opzione di formattazione.
Alan H.

echosembra un'opzione più sicura. echo "my new line\n$(cat my/file.txt)" > my/file.txt
Alan H.

@AlanH. Metto in guardia dai pericoli nella risposta.
user137369

In realtà, hai solo avvertito delle percentuali ${new_line}, non del file di destinazione
Alan H.

Potresti essere più esplicito? È un grande avvertimento IMO. "Ovunque" non lo rende molto chiaro.
Alan H.

1

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.


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

dove "my_file" è il file a cui anteporre "my_string".


0

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'

0

variabili, 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

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

0

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.


Un editore ha suggerito di cambiarlo in cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfile, tuttavia è sufficientemente diverso dalla mia risposta che dovrebbe essere la propria risposta.
Tim Kennedy,

0

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 .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.