Rimozione dei file temporanei creati in un'uscita bash imprevista


89

Sto creando file temporanei da uno script bash. Li elimino alla fine dell'elaborazione, ma poiché lo script è in esecuzione per un periodo piuttosto lungo, se lo uccido o semplicemente CTRL-C durante l'esecuzione, i file temporanei non vengono eliminati.
C'è un modo per catturare quegli eventi e ripulire i file prima del termine dell'esecuzione?

Inoltre, esiste una sorta di best practice per la denominazione e la posizione di quei file temporanei?
Al momento non sono sicuro se usare:

TMP1=`mktemp -p /tmp`
TMP2=`mktemp -p /tmp`
...

e

TMP1=/tmp/`basename $0`1.$$
TMP2=/tmp/`basename $0`2.$$
...

O forse ci sono soluzioni migliori?


Risposte:


97

Puoi impostare una " trappola " da eseguire in uscita o su un control-c per ripulire.

trap "{ rm -f $LOCKFILE; }" EXIT

In alternativa, uno dei miei unix-ismi preferiti è aprire un file e quindi eliminarlo mentre lo hai ancora aperto. Il file rimane nel file system e puoi leggerlo e scriverlo, ma non appena il programma esce, il file scompare. Non sono sicuro di come faresti a bash, però.

BTW: Un argomento che darò a favore di mktemp invece di usare la tua soluzione: se l'utente prevede che il tuo programma creerà enormi file temporanei, potrebbe volerlo impostare TMPDIRda qualche parte più grande, come / var / tmp. mktemp lo riconosce, la tua soluzione fatta a mano (seconda opzione) no. Uso spesso TMPDIR=/var/tmp gvim -d foo bar, ad esempio.


8
Con Bash, exec 5<>$TMPFILEfile di legami descrittore 5 a $ TMPFILE come lettura-scrittura, ed è possibile utilizzare <&5, >&5e /proc/$$/fd/5(Linux) da allora in poi. L'unico problema è che Bash non ha seekfunzionalità ...
effimero

Ho accettato la tua risposta poiché il link che hai fornito è ciò che spiega al meglio ciò di cui avevo bisogno. Grazie
skinp

4
Un paio di note su trap: non c'è modo di intercettare SIGKILL(in base alla progettazione, poiché termina immediatamente l'esecuzione). Quindi, se ciò potrebbe accadere, prepara un piano di riserva (come tmpreaper). In secondo luogo, le trappole non sono cumulative: se hai più di un'azione da eseguire, devono essere tutte al trapcomando. Un modo per far fronte con più azioni di pulizia è quello di definire una funzione (e si può ridefinire come vostro programma procede, se necessario) e di riferimento che: trap cleanup_function EXIT.
Toby Speight,

1
Ho dovuto usare trap "rm -f $LOCKFILE" EXITo avrei ricevuto un errore di fine file inaspettato.
Jaakko

3
Shellcheck ha avvertito di utilizzare virgolette singole, che l'espressione sarebbe stata espansa "ora" con virgolette doppie piuttosto che successivamente quando viene invocata la trappola.
LaFayette

110

Di solito creo una directory in cui posizionare tutti i miei file temporanei e, subito dopo, creo un gestore EXIT per ripulire questa directory quando lo script esce.

MYTMPDIR=$(mktemp -d)
trap "rm -rf $MYTMPDIR" EXIT

Se metti tutti i tuoi file temporanei sotto $MYTMPDIR, nella maggior parte dei casi verranno eliminati tutti quando lo script viene chiuso. Tuttavia, uccidere un processo con SIGKILL (kill -9) interrompe immediatamente il processo, quindi il gestore EXIT non verrà eseguito in quel caso.


27
+1 Sicuramente usa una trappola in USCITA, non sciocco TERM / INT / HUP / qualsiasi altra cosa tu possa pensare. Anche se, ricordarsi di citare le vostre espansioni di parametri e vorrei anche Consiglieresti singola citazione tua trappola: trappola 'rm -rf '$ TMPDIR'' EXIT
lhunath

7
Virgolette singole, perché in tal caso la tua trappola funzionerà ancora se in seguito nel tuo script decidi di ripulire e modificare TMPDIR a causa di circostanze.
lhunath

1
@AaronDigulla Perché $ () rispetto ai backtick è importante?
Salmo dell'orco33


3
Il codice @AlexanderTorstling deve sempre essere racchiuso tra virgolette singole per evitare che l'iniezione comporti l'esecuzione di codice arbitrario. Se espandi i dati in un codice bash STRING, quei dati possono ora fare qualsiasi cosa faccia il codice che si traduce in bug innocenti rispetto a spazi bianchi ma anche bug distruttivi come la cancellazione del tuo homedir per ragioni bizzarre o l'introduzione di falle di sicurezza. Nota che trap accetta una stringa di codice bash che verrà valutata così com'è in seguito. Quindi, più tardi, quando si attiva la trappola, le virgolette singole saranno sparite e ci saranno solo le virgolette sintattiche.
lhunath

25

Si desidera utilizzare il comando trap per gestire l'uscita dallo script o segnali come CTRL-C. Vedi il Wiki di Greg per i dettagli.

Per i tuoi basename $0file temporanei , l'utilizzo è una buona idea, oltre a fornire un modello che fornisce spazio per un numero sufficiente di file temporanei:

tempfile() {
    tempprefix=$(basename "$0")
    mktemp /tmp/${tempprefix}.XXXXXX
}

TMP1=$(tempfile)
TMP2=$(tempfile)

trap 'rm -f $TMP1 $TMP2' EXIT

1
Non intrappolare su TERM / INT. Trap su EXIT. Cercare di prevedere la condizione di uscita in base ai segnali ricevuti è sciocco e sicuramente non è un problema.
lhunath

3
Punto minore: usa $ () invece di singoli backtick. E inserisci le virgolette doppie intorno a $ 0 perché potrebbe contenere spazi.
Aaron Digulla

Bene, i backtick funzionano bene in questo commento, ma questo è un punto giusto, è bene avere l'abitudine di usare $(). Aggiunte anche le virgolette doppie.
Brian Campbell

1
Puoi sostituire l'intera subroutine con TMP1 = $ (tempfile -s "XXXXXX")
Ruslan Kabalin

4
@RuslanKabalin Non tutti i sistemi hanno un tempfilecomando, mentre tutti i sistemi moderni ragionevoli che conosco hanno un mktempcomando.
Brian Campbell

9

Tieni presente che la risposta scelta è bashism, che significa soluzione come

trap "{ rm -f $LOCKFILE }" EXIT

funzionerebbe solo in bash (non catturerà Ctrl + c se la shell è dasho classica sh), ma se vuoi la compatibilità, devi comunque enumerare tutti i segnali che vuoi intercettare.

Inoltre, tieni presente che quando lo script esce dalla trappola per il segnale "0" (noto anche come EXIT) viene sempre eseguita la doppia esecuzione del trapcomando.

Questo è il motivo per non impilare tutti i segnali in una riga se c'è il segnale di USCITA.

Per comprenderlo meglio, guarda il seguente script che funzionerà su diversi sistemi senza modifiche:

#!/bin/sh

on_exit() {
  echo 'Cleaning up...(remove tmp files, etc)'
}

on_preExit() {
  echo
  echo 'Exiting...' # Runs just before actual exit,
                    # shell will execute EXIT(0) after finishing this function
                    # that we hook also in on_exit function
  exit 2
}


trap on_exit EXIT                           # EXIT = 0
trap on_preExit HUP INT QUIT TERM STOP PWR  # 1 2 3 15 30


sleep 3 # some actual code...

exit 

Questa soluzione ti darà un maggiore controllo poiché puoi eseguire parte del tuo codice al verificarsi del segnale effettivo appena prima dell'uscita finale ( preExitfunzione) e, se necessario, puoi eseguire del codice al segnale di USCITA effettivo (fase finale di uscita)


4

L'alternativa di utilizzare un nome di file prevedibile con $$ è una falla nella sicurezza e non dovresti mai, mai, mai pensare di usarlo. Anche se è solo un semplice script personale sul tuo PC singolo utente. È una pessima abitudine che non dovresti ottenere. BugTraq è pieno di incidenti relativi a "file temporanei non sicuri". Vedi qui , qui e qui per ulteriori informazioni sull'aspetto della sicurezza dei file temporanei.

Inizialmente stavo pensando di citare i compiti insicuri di TMP1 e TMP2, ma ripensandoci probabilmente non sarebbe stata una buona idea .


Darei se potessi: +1 per il consiglio di sicurezza e un altro +1 per non citare una cattiva idea e il riferimento
TMG

1

Preferisco usare tempfileche crea un file in / tmp in modo sicuro e non devi preoccuparti della sua denominazione:

tmp=$(tempfile -s "your_sufix")
trap "rm -f '$tmp'" exit

tempfile è purtroppo molto non portabile sebbene più sicuro, quindi spesso è meglio evitarlo o almeno emularlo.
lericson

1

Non riesco a credere che così tante persone presumono che un nome di file non conterrà uno spazio. Il mondo andrà in crash se $ TMPDIR viene mai assegnato alla "directory temporanea".

zTemp=$(mktemp --tmpdir "$(basename "$0")-XXX.ps")
trap "rm -f ${zTemp@Q}" EXIT

Gli spazi e altri caratteri speciali come virgolette singole e ritorni a capo nei nomi di file dovrebbero essere considerati nel codice come un requisito per un'abitudine di programmazione decente.


+1 Sebbene le virgolette singole trap 'rm -f "${zTemp}"' EXITgestiscano correttamente spazi e altri caratteri speciali, la soluzione di questa risposta non rimanda la valutazione di zTemp. Non è quindi necessario preoccuparsi del valore di zTempessere modificato in seguito nello script. Inoltre, zTemppuò essere dichiarato locale a una funzione; non è necessario che sia una variabile di script globale.
Robin A. Meade

Le virgolette doppie attorno alla destra del compito non sono necessarie.
Robin A. Meade

Va notato che le ${parameter@operator}espansioni sono state aggiunte in Bash 4.4 (rilasciato a settembre 2016).
Robin A. Meade

-4

Non devi preoccuparti di rimuovere quei file tmp creati con mktemp. Verranno comunque cancellati in seguito.

Usa mktemp se puoi poiché genera più file univoci quindi il prefisso "$$". E sembra più un modo multipiattaforma per creare file temporanei e poi inserirli esplicitamente in / tmp.


4
Cancellato da chi o cosa?
innaM

Eliminato dall'operazione | file system stesso dopo un certo periodo di tempo
Mykola Golubyev

4
Magia? Un cronjob? O una macchina Solaris riavviata?
innaM

Probabilmente uno di loro. Se il file temp non è stato rimosso da qualche interruzione (non sarà troppo spesso) un giorno i file tmp verranno rimossi - ecco perché hanno chiamato temp.
Mykola Golubyev

22
Non puoi, non dovresti, non devi presumere che qualcosa messo in / tmp rimarrà lì per sempre; allo stesso tempo, non dovresti presumere che scomparirà magicamente.
innaM
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.