Come posso eseguire un comando modificando il suo file (argomento) "sul posto" usando bash?


110

Ho un file temp.txt, che voglio ordinare con il sortcomando in bash.

Voglio che i risultati ordinati sostituiscano il file originale.

Questo non funziona per esempio (ottengo un file vuoto):

sortx temp.txt > temp.txt

È possibile farlo in una riga senza ricorrere alla copia su file temporanei?


EDIT: l' -oopzione è molto interessante per sort. Ho usato sortnella mia domanda come esempio. Mi imbatto nello stesso problema con altri comandi:

uniq temp.txt > temp.txt.

Esiste una soluzione generale migliore?


Risposte:


171
sort temp.txt -o temp.txt

3
Questa è una risposta. In realtà mi chiedevo se esiste una soluzione generica a questo problema. Ad esempio, se voglio trovare tutte le righe UNIQ in un file "in place", non posso fare -o
jm.

Non è generico, ma puoi usare -u con GNU sort per trovare linee uniche
James

Qualcuno ha risolto il problema per consentire ad esempio sort --inplace *.txt? Sarebbe pazzesco
vedi il

@sehe Prova questo:find . -name \*.txt -exec sort {} -o {} \;
Keith Gaughan

29

A ha sortbisogno di vedere tutti gli input prima di poter iniziare a produrre. Per questo motivo, il sortprogramma può facilmente offrire un'opzione per modificare un file sul posto:

sort temp.txt -o temp.txt

Nello specifico, la documentazione di GNUsort dice:

Normalmente, l'ordinamento legge tutto l'input prima di aprire il file di output, quindi puoi tranquillamente ordinare un file sul posto usando comandi come sort -o F Fe cat F | sort -o F. Tuttavia, sortcon --merge( -m) puoi aprire il file di output prima di leggere tutto l'input, quindi un comando come cat F | sort -m -o F - Gnon è sicuro in quanto l'ordinamento potrebbe iniziare a scrivere Fprimacat abbia finito di leggerlo.

Mentre la documentazione di BSD sortdice:

Se [il] file di output è uno dei file di input, sort lo copia in un file temporaneo prima di ordinare e scrivere l'output in [il] file di output.

Comandi come uniq possono iniziare a scrivere l'output prima che finiscano di leggere l'input. Questi comandi in genere non supportano la modifica sul posto (e sarebbe più difficile per loro supportare questa funzione).

In genere si aggira questo problema con un file temporaneo o, se si desidera assolutamente evitare di avere un file intermedio, è possibile utilizzare un buffer per memorizzare il risultato completo prima di scriverlo. Ad esempio, con perl:

uniq temp.txt | perl -e 'undef $/; $_ = <>; open(OUT,">temp.txt"); print OUT;'

Qui, la parte perl legge l'output completo dalla uniqvariabile in $_e quindi sovrascrive il file originale con questi dati. Potresti fare lo stesso nel linguaggio di scripting che preferisci, forse anche in Bash. Ma tieni presente che avrà bisogno di memoria sufficiente per archiviare l'intero file, questo non è consigliabile quando si lavora con file di grandi dimensioni.


19

Ecco un approccio più generale, funziona con uniq, sort e quant'altro.

{ rm file && uniq > file; } < file

14
Un altro approccio generico, con spongele moreutils: cat file |frobnicate |sponge file.
Tobu

3
@Tobu: perché non presentarla come risposta separata?
Flimm

1
Probabilmente è bene notare che questo non preserva necessariamente i permessi dei file. Il tuo umask determina quali saranno i nuovi permessi.
wor

1
Tricky one. Puoi spiegare come funziona esattamente?
patryk.beza

2
@ patryk.beza: In ordine: l'ingresso FD viene aperto dal file originale; la voce di rubrica originale viene cancellata; il reindirizzamento viene elaborato, creando un nuovo file vuoto con lo stesso nome che aveva il vecchio; quindi il comando viene eseguito.
Charles Duffy

10

Il commento di Tobu sulla spugna merita di essere una risposta a sé stante.

Per citare dalla homepage di moreutils :

Probabilmente lo strumento più generico in moreutils finora è sponge (1), che ti consente di fare cose come questa:

% sed "s/root/toor/" /etc/passwd | grep -v joey | sponge /etc/passwd

Tuttavia, spongesoffre dello stesso problema commentato da Steve Jessop qui. Se uno qualsiasi dei comandi nella pipeline prima spongenon riesce, il file originale verrà sovrascritto.

$ mistyped_command my-important-file | sponge my-important-file
mistyped-command: command not found

Uh-oh, my-important-fileè andato.


1
Sponge sa che verrà utilizzato per sostituire il file di input e inizialmente crea un file temporaneo per evitare una condizione di competizione. Affinché questo funzioni, sponge deve essere l'ultimo elemento nella pipeline e deve essere consentito di creare il file di output stesso (al contrario del reindirizzamento dell'output a livello di shell, per esempio). BTW: Sembra che una facile correzione del codice sorgente per il caso di "errore" sarebbe non rinominare il file temporaneo in caso di errore di pipe (non so perché sponge non ha questa opzione).
Brent Bradburn

Penso che se aggiungi set -o pipefailall'inizio del tuo script, l'errore su mistyped_command my-important-filefarebbe uscire lo script immediatamente, prima dell'esecuzione sponge, preservando così il file importante.
Elouan Keryell-Even

6

Ecco qua, una riga:

sort temp.txt > temp.txt.sort && mv temp.txt.sort temp.txt

Tecnicamente non è possibile copiare in un file temporaneo e il comando "mv" dovrebbe essere istantaneo.


6
Hm. Chiamerei ancora temp.txt.sort un file temporaneo.
JesperE

5
Questo codice è rischioso, perché se l'ordinamento fallisce per qualsiasi motivo senza completare il suo lavoro, l'originale viene sovrascritto.
Steve Jessop

1
La mancanza di spazio su disco è una causa plausibile o un segnale (l'utente preme CTRL-C).
Steve Jessop

5
se vuoi usare qualcosa di simile usa && (logico e) invece di; perché usarlo assicurerà che se un comando fallisce il prossimo non verrà eseguito. ad esempio: cp backup.tar /root/backup.tar && rm backup.tar se non disponi dei diritti di copia sarai al sicuro poiché il file non verrà eliminato
daniels

1
ha cambiato la mia risposta per tenere conto dei tuoi suggerimenti, grazie
davr

4

mi piace il sort file -o file risposta ma non voglio digitare due volte lo stesso nome di file.

Utilizzando l' espansione della cronologia BASH :

$ sort file -o !#^

afferra il primo argomento della riga corrente quando si preme enter .

Un ordinamento unico sul posto:

$ sort -u -o file !#$

afferra l'ultimo argomento nella riga corrente.


3

Molti hanno menzionato -o opzione . Ecco la parte della pagina man.

Dalla pagina man:

   -o output-file
          Write output to output-file instead of to the  standard  output.
          If  output-file  is  one of the input files, sort copies it to a
          temporary file before sorting and writing the output to  output-
          file.

3

Ciò sarebbe fortemente limitato dalla memoria, ma potresti usare awk per archiviare i dati intermedi in memoria e quindi riscriverli.

uniq temp.txt | awk '{line[i++] = $0}END{for(j=0;j<i;j++){print line[j]}}' > temp.txt

Penso che sia possibile le >tronca il file prima del comando ( uniqin questo caso) lo legge.
Martin,

3

Un'alternativa al spongepiù comune sed:

sed -ni r<(command file) file

Funziona per qualsiasi comando ( sort, uniq, tac, ...) e utilizza il ben noto sed's -iopzione (modificare i file sul posto).

Avviso: prova command fileprima perché la modifica dei file sul posto non è sicura per natura.


Spiegazione

In primo luogo, si sta dicendo seddi non stampare il (originali) linee ( -nopzione ), e con l'aiuto del sed's rdi comando e bash' s Sostituzione di processo , il contenuto generato dagli <(command file)sarà l'output salvato in atto .


Rendere le cose ancora più facili

Puoi racchiudere questa soluzione in una funzione:

ip_cmd() { # in place command
    CMD=${1:?You must specify a command}
    FILE=${2:?You must specify a file}
    sed -ni r<("$CMD" "$FILE") "$FILE"
}

Esempio

$ cat file
d
b
c
b
a

$ ip_cmd sort file
$ cat file
a
b
b
c
d

$ ip_cmd uniq file
$ cat file
a
b
c
d

$ ip_cmd tac file
$ cat file
d
c
b
a

$ ip_cmd
bash: 1: You must specify a command
$ ip_cmd uniq
bash: 2: You must specify a file

1

Usa l'argomento --output=o-o

Appena provato su FreeBSD:

sort temp.txt -otemp.txt

Sebbene corretto, è semplicemente un duplicato di questa risposta
whoan

1

Per aggiungere la uniqcapacità, quali sono gli svantaggi di:

sort inputfile | uniq | sort -o inputfile


0

Se insisti a usare il sortprogramma, devi usare un file intermedio - non penso sortabbia un'opzione per l'ordinamento in memoria. Qualsiasi altro trucco con stdin / stdout fallirà a meno che tu non possa garantire che la dimensione del buffer per lo stdin di sort sia abbastanza grande da contenere l'intero file.

Modifica: vergogna su di me. sort temp.txt -o temp.txtfunziona bene.


Ho letto anche la Q come "sul posto" ma la seconda lettura mi ha fatto credere che non lo stesse chiedendo davvero
epatel

0

Un'altra soluzione:

uniq file 1<> file

Va notato però che il <>trucco funziona solo in questo caso perché uniqè speciale in quanto copia solo le righe di input su quelle di output, lasciandone alcune lungo il percorso. Se è sedstato utilizzato un altro comando (ad esempio ) che cambierebbe l'input (ad esempio, cambierebbe ogni ain aa), allora può sovrascrivere filein modi che non hanno alcun senso e persino eseguire un ciclo infinito, a condizione che l'input sia sufficientemente grande (più di un singolo buffer di lettura).
David
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.