Leggere e scrivere un file: comando tee


10

È noto che un comando come questo:

cat filename | some_sed_command >filename

cancella il nome file, poiché il reindirizzamento dell'output, eseguito prima del comando, provoca il troncamento del nome file.

Si potrebbe risolvere il problema nel modo seguente:

cat file | some_sed_command | tee file >/dev/null

ma non sono sicuro che funzionerebbe in ogni caso: cosa succede se il file (e il risultato del comando sed) è molto grande? Come può il sistema operativo evitare di sovrascrivere alcuni contenuti che non sono ancora stati letti? Vedo che esiste anche un comando spugna che dovrebbe funzionare in ogni caso: è "più sicuro" del tee?


Qual è il tuo obiettivo principale? (in parole semplici)
Sergiy Kolodyazhnyy

@Serg capisce semplicemente come funzionano le cose ... La risposta scritta da kos chiarisce la questione
VeryHardCoder

Risposte:


10

Si potrebbe risolvere il problema nel modo seguente:

cat file | some_sed_command | tee file >/dev/null

No .

Le probabilità fileverranno troncate, ma non ci sono garanzie cat file | some_sed_command | tee file >/dev/nullche non troncano file.

Tutto dipende da quale comando viene elaborato per primo, al contrario di quello che ci si può aspettare, i comandi in una pipe non vengono elaborati da sinistra a destra . Non vi è alcuna garanzia su quale comando verrà scelto per primo, quindi si potrebbe anche solo pensarlo come scelto casualmente e non fare affidamento sulla shell che non sceglie quello offensivo.

Poiché le possibilità che il comando incriminato venga selezionato per primo tra tre comandi sono inferiori alle possibilità che il comando incriminato venga selezionato per primo tra due comandi, è meno probabile che filevenga troncato, ma continuerà a succedere .

script.sh:

#!/bin/bash
for ((i=0; i<100; i++)); do
    cat >file <<-EOF
    foo
    bar
    EOF
    cat file |
        sed 's/bar/baz/' |
        tee file >/dev/null
    [ -s file ] &&
        echo 'Not truncated' ||
        echo 'Truncated'
done |
    sort |
    uniq -c
rm file
% bash script.sh
 93 Not truncated
  7 Truncated
% bash script.sh
 98 Not truncated
  2 Truncated
% bash script.sh
100 Not truncated

Quindi non usare mai qualcosa del genere cat file | some_sed_command | tee file >/dev/null. Usa spongecome suggerito da Oli.

In alternativa, per ambienti più forti e / o file relativamente piccoli, è possibile utilizzare una stringa qui e una sostituzione comando per leggere il file prima di eseguire qualsiasi comando:

$ cat file
foo
bar
$ for ((i=0; i<100; i++)); do <<<"$(<file)" sed 's/bar/baz/' >file; done
$ cat file
foo
baz

9

In sedparticolare, è possibile utilizzare l' -iargomento sul posto. Salva solo il file che ha aperto, ad esempio:

sed -i 's/ /-/g' filename

Se vuoi fare qualcosa di più robusto, supponendo che tu stia facendo più di sed, sì, puoi bufferizzare il tutto con sponge(dal moreutilspacchetto) che "assorbirà" tutto lo stdin prima di scrivere nel file. È come teema con meno funzionalità. Per l'utilizzo di base, tuttavia, è praticamente una sostituzione drop-in:

cat file | some_sed_command | sponge file >/dev/null

È più sicuro? Decisamente. Probabilmente ha dei limiti, quindi se stai facendo qualcosa di colossale (e non riesci a modificarlo sul posto con sed), potresti voler fare le tue modifiche su un secondo file e poi mvtornare al file originale. Dovrebbe essere atomico (quindi tutto ciò che dipende da questi file non si romperà se hanno bisogno di un accesso costante).


0

Puoi usare Vim in modalità Ex:

ex -sc '%!some_sed_command' -cx filename
  1. % seleziona tutte le righe

  2. ! Esegui comando

  3. x Salva ed esci


0

Oh, ma spongenon è l'unica opzione; non è necessario ottenere moreutilsper farlo funzionare correttamente. Qualsiasi meccanismo funzionerà purché soddisfi i seguenti due requisiti:

  1. Accetta il nome del file di output come parametro.
  2. Crea il file di output solo dopo che tutto l'input è stato elaborato.

Vedete, il problema ben noto a cui si riferisce l'OP è che la shell creerà tutti i file necessari per il funzionamento delle pipe prima ancora di iniziare a eseguire i comandi nella pipeline, quindi è la shell che effettivamente tronca il file di output (che sfortunatamente è anche il file di input) prima che uno qualsiasi dei comandi abbia persino avuto la possibilità di iniziare l'esecuzione.

Il teecomando non funziona, anche se soddisfa il primo requisito, perché non soddisfa il secondo requisito: creerà sempre il file di output immediatamente all'avvio, quindi è essenzialmente dannoso come creare una pipe direttamente nel file di output. (In realtà è peggio, perché il suo utilizzo introduce un ritardo casuale non deterministico prima che il file di output venga troncato, quindi potresti pensare che funzioni, mentre in realtà non lo fa.)

Quindi, tutto ciò di cui abbiamo bisogno per risolvere questo problema è qualche comando che bufferizzerà tutto il suo input prima di produrre qualsiasi output, e che è in grado di accettare il nome del file di output come parametro, in modo da non dover convogliare il suo output in il file di output. Uno di questi comandi è shuf. Quindi, il seguente compirà la stessa cosa che spongefa:

    shuf --output=file --random-source=/dev/zero 

La --random-source=/dev/zeroparte inizia shufa fare le sue cose senza fare alcun rimescolamento, quindi bufferizza i tuoi input senza alterarli.

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.