Come posso eseguire qualsiasi comando modificando il suo file (argomento) & # 8220; in posizione & # 8221; usando bash?

StackOverflow https://stackoverflow.com/questions/146435

  •  02-07-2019
  •  | 
  •  

Domanda

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

Voglio che i risultati ordinati sostituiscano il file originale.

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

sortx temp.txt > temp.txt

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


EDIT: l'opzione -o è molto interessante per sort . Ho usato sort nella mia domanda come esempio. Ho riscontrato lo stesso problema con altri comandi:

uniq temp.txt > temp.txt.

Esiste una soluzione generale migliore?

È stato utile?

Soluzione

sort temp.txt -o temp.txt

Altri suggerimenti

Un sort deve vedere tutti gli input prima di poter iniziare a produrre. Per questo motivo, il programma sort può facilmente offrire un'opzione per modificare un file sul posto:

sort temp.txt -o temp.txt

In particolare, la della documentazione di GNU sort dice:

  

Normalmente, l'ordinamento legge tutti gli input prima di aprire il file di output, quindi puoi ordinare un file in sicurezza usando comandi come sort -o F F e cat F | ordina -o F . Tuttavia, sort con --merge ( -m ) può aprire il file di output prima di leggere tutti gli input, quindi un comando come cat F | sort -m -o F - G non è sicuro in quanto l'ordinamento potrebbe iniziare a scrivere F prima che cat abbia finito di leggerlo.

Mentre la documentazione di BSD sort dice:

  

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

Comandi come uniq possono iniziare a scrivere 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 risolve questo problema con un file temporaneo o se si desidera assolutamente evitare di disporre di 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 $/; 

Un sort deve vedere tutti gli input prima di poter iniziare a produrre. Per questo motivo, il programma sort può facilmente offrire un'opzione per modificare un file sul posto:

sort temp.txt -o temp.txt

In particolare, la della documentazione di GNU sort dice:

  

Normalmente, l'ordinamento legge tutti gli input prima di aprire il file di output, quindi puoi ordinare un file in sicurezza usando comandi come sort -o F F e cat F | ordina -o F . Tuttavia, sort con --merge ( -m ) può aprire il file di output prima di leggere tutti gli input, quindi un comando come cat F | sort -m -o F - G non è sicuro in quanto l'ordinamento potrebbe iniziare a scrivere F prima che cat abbia finito di leggerlo.

Mentre la documentazione di BSD sort dice:

  

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

Comandi come uniq possono iniziare a scrivere 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 risolve questo problema con un file temporaneo o se si desidera assolutamente evitare di disporre di un file intermedio, è possibile utilizzare un buffer per memorizzare il risultato completo prima di scriverlo. Ad esempio, con perl :

<*>

Qui, la parte perl legge l'output completo da uniq nella variabile $ _ e quindi sovrascrive il file originale con questi dati. Potresti fare lo stesso nel linguaggio di script che preferisci, forse anche in Bash. Tuttavia, sarà necessario memoria sufficiente per archiviare l'intero file, questo non è consigliabile quando si lavora con file di grandi dimensioni.

= <>; open(OUT,">temp.txt"); print OUT;'

Qui, la parte perl legge l'output completo da uniq nella variabile $ _ e quindi sovrascrive il file originale con questi dati. Potresti fare lo stesso nel linguaggio di script che preferisci, forse anche in Bash. Tuttavia, sarà necessario memoria sufficiente per archiviare l'intero file, questo non è consigliabile quando si lavora con file di grandi dimensioni.

Ecco un approccio più generale, funziona con uniq, sort e whatnot.

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

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

Per citare dalla moreutils homepage:

  

Probabilmente lo strumento più generico finora in moreutils è sponge (1), che ti permette di fare cose del genere:

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

Tuttavia, sponge soffre dello stesso problema Steve Steveop commenta qui. Se presente dei comandi nella pipeline prima che sponge fallisca, il file originale verrà sovrascritto.

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

Uh-oh, il mio file importante non c'è più.

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

Mi piace la risposta ordina file -o file ma non voglio digitare due volte lo stesso nome file.

Uso di BASH espansione della cronologia :

$ sort file -o !#^

prende il primo argomento della riga corrente quando si preme invio .

Un ordinamento unico sul posto:

$ sort -u -o file !#$

prende l'ultimo arg nella riga corrente.

Molti hanno menzionato l'opzione -o . 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.

Questo sarebbe fortemente limitato dalla memoria, ma potresti usare awk per memorizzare i dati intermedi in memoria, e poi riscriverli.

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

Un'alternativa a spugna con il sed più comune:

sed -ni r<(command file) file

Funziona con qualsiasi comando ( sort , uniq , tac , ...) e utilizza il ben noto sed -i > (modifica i file sul posto).

Avviso: prova prima file di comando perché la modifica dei file sul posto non è sicura per natura.


Spiegazione

In primo luogo, stai dicendo a sed di non stampare le righe (originali) ( -n opzione ) e con l'aiuto di r comando e bash 's Sostituzione processo , il contenuto generato da < (file di comando) sarà l'output salvato sul posto .


Semplifica le cose

Puoi avvolgere 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

Usa l'argomento --output = o -o

Ho appena provato su FreeBSD:

sort temp.txt -otemp.txt

Per aggiungere la funzionalità uniq , quali sono gli svantaggi di:

sort inputfile | uniq | sort -o inputfile

Leggi sull'editor non interattivo, ex .

Se insisti nell'usare il programma sort , devi usare un file intermedio - non credo che sort abbia 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 adattarsi all'intero file.

Modifica: vergogna per me. sort temp.txt -o temp.txt funziona in modo eccellente.

Un'altra soluzione:

uniq file 1<> file
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top