Suppression des fichiers temporaires créés lors d'une sortie bash inattendue

89

Je crée des fichiers temporaires à partir d'un script bash. Je les supprime à la fin du traitement, mais comme le script est en cours d'exécution depuis assez longtemps, si je le tue ou simplement CTRL-C pendant l'exécution, les fichiers temporaires ne sont pas supprimés.
Existe-t-il un moyen d'attraper ces événements et de nettoyer les fichiers avant la fin de l'exécution?

Existe-t-il également une sorte de meilleure pratique pour la dénomination et l'emplacement de ces fichiers temporaires?
Je ne suis actuellement pas sûr d'utiliser:

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

et

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

Ou peut-être y a-t-il de meilleures solutions?

skinp
la source

Réponses:

97

Vous pouvez définir un « piège » à exécuter à la sortie ou sur un contrôle-c pour nettoyer.

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

Alternativement, l'un de mes unix-ismes préférés est d'ouvrir un fichier, puis de le supprimer pendant que vous l'avez encore ouvert. Le fichier reste sur le système de fichiers et vous pouvez le lire et l'écrire, mais dès que votre programme se termine, le fichier disparaît. Je ne sais pas comment vous feriez cela en bash, cependant.

BTW: Un argument que je donnerai en faveur de mktemp au lieu d'utiliser votre propre solution: si l'utilisateur prévoit que votre programme va créer d'énormes fichiers temporaires, il voudra peut-être définir un TMPDIRemplacement plus grand, comme / var / tmp. mktemp reconnaît que, votre solution roulée à la main (deuxième option) ne le fait pas. J'utilise fréquemment TMPDIR=/var/tmp gvim -d foo bar, par exemple.

Paul Tomblin
la source
8
Avec Bash, exec 5<>$TMPFILEdescripteur de fichier liens 5 à $ TMPFILE en lecture-écriture, et vous pouvez utiliser <&5, >&5et /proc/$$/fd/5(Linux) par la suite. Le seul problème est que Bash manque de seekfonction ...
éphémère
Accepté vous répondez puisque le lien que vous avez fourni est ce qui explique le mieux ce dont j'avais besoin. Merci
skinp
4
Quelques remarques sur trap: il n'y a aucun moyen de piéger SIGKILL(par conception, car cela met immédiatement fin à l'exécution). Donc, si cela peut arriver, ayez un plan de secours (tel que tmpreaper). Deuxièmement, les interruptions ne sont pas cumulatives - si vous avez plus d'une action à effectuer, elles doivent toutes figurer dans la trapcommande. Une façon de faire face à de multiples actions de nettoyage est de définir une fonction (et vous pouvez le redéfinir comme produit de votre programme, le cas échéant) et référence: trap cleanup_function EXIT.
Toby Speight
1
Je devais utiliser trap "rm -f $LOCKFILE" EXITou j'obtiendrais une erreur de fin de fichier inattendue.
Jaakko
3
Shellcheck a averti d'utiliser des guillemets simples, que l'expression serait développée «maintenant» avec des guillemets doubles plutôt que plus tard lorsque le trap est appelé.
LaFayette
110

Je crée généralement un répertoire dans lequel placer tous mes fichiers temporaires, puis, immédiatement après, crée un gestionnaire EXIT pour nettoyer ce répertoire lorsque le script se termine.

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

Si vous placez tous vos fichiers temporaires sous $MYTMPDIR, ils seront tous supprimés à la fin de votre script dans la plupart des cas. Tuer un processus avec SIGKILL (kill -9) tue le processus tout de suite, donc votre gestionnaire EXIT ne fonctionnera pas dans ce cas.

Chris AtLee
la source
27
+1 Utilisez certainement un piège sur EXIT, pas un terme idiot / INT / HUP / tout ce à quoi vous pouvez penser. Bien que, n'oubliez pas de citer vos extensions de paramètres et je aussi vous recommander simple devis votre piège: piège rm -rf « $ TMPDIR » 'EXIT
lhunath
7
Guillemets simples, car alors votre piège fonctionnera toujours si plus tard dans votre script vous décidez de nettoyer et de changer TMPDIR à cause des circonstances.
lhunath
1
@AaronDigulla Pourquoi $ () par rapport aux backticks est-il important?
Ogre Psalm33
3
@ OgrePsalm33: stackoverflow.com/questions/4708549/…
Aaron Digulla
3
Le code @AlexanderTorstling doit toujours être entre guillemets simples pour éviter que l'injection n'entraîne l'exécution de code arbitraire. Si vous développez des données dans un code bash STRING, ces données peuvent désormais faire tout ce que le code fait, ce qui entraîne des bogues innocents dans les espaces blancs, mais aussi des bogues destructeurs tels que la suppression de votre homedir pour des raisons bizarres ou l'introduction de failles de sécurité. Notez que trap prend une chaîne de code bash qui sera évaluée telle quelle plus tard. Ainsi, plus tard, lorsque le piège se déclenchera, les guillemets simples auront disparu et il n'y aura plus que les guillemets syntaxiques.
lhunath
25

Vous souhaitez utiliser la commande trap pour gérer la sortie du script ou des signaux tels que CTRL-C. Consultez le Wiki de Greg pour plus de détails.

Pour vos basename $0fichiers temporaires , utiliser est une bonne idée, ainsi que fournir un modèle qui offre suffisamment de place pour les fichiers temporaires:

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

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

trap 'rm -f $TMP1 $TMP2' EXIT
Brian Campbell
la source
1
Ne pas piéger sur TERM / INT. Piège sur EXIT. Essayer de prédire la condition de sortie en fonction des signaux reçus est stupide et certainement pas un fourre-tout.
lhunath
3
Point mineur: utilisez $ () au lieu de simples backticks. Et mettez des guillemets doubles autour de 0 $ car il pourrait contenir des espaces.
Aaron Digulla
Eh bien, les backticks fonctionnent bien dans ce commentaire, mais c'est un bon point, il est bon d'avoir l'habitude d'utiliser $(). Ajout des guillemets doubles également.
Brian Campbell
1
Vous pouvez remplacer tout votre sous-programme par juste TMP1 = $ (tempfile -s "XXXXXX")
Ruslan Kabalin
4
@RuslanKabalin Tous les systèmes n'ont pas de tempfilecommande, alors que tous les systèmes modernes raisonnables que je connais ont une mktempcommande.
Brian Campbell
9

Gardez simplement à l'esprit que la réponse choisie est bashism, ce qui signifie solution comme

trap "{ rm -f $LOCKFILE }" EXIT

ne fonctionnerait que dans bash (il n'attrapera pas Ctrl + c si shell est dashou classique sh), mais si vous voulez la compatibilité, vous devez toujours énumérer tous les signaux que vous souhaitez intercepter.

Gardez également à l'esprit que lorsque le script sort, le trap pour le signal "0" (aka EXIT) est toujours exécuté, ce qui entraîne une double exécution de la trapcommande.

C'est la raison pour ne pas empiler tous les signaux sur une seule ligne s'il y a un signal EXIT.

Pour mieux le comprendre, regardez le script suivant qui fonctionnera sur différents systèmes sans changement:

#!/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 

Cette solution vous donnera plus de contrôle puisque vous pouvez exécuter une partie de votre code sur l'occurrence du signal réel juste avant la sortie finale ( preExitfonction) et si nécessaire, vous pouvez exécuter du code au signal EXIT réel (étape finale de la sortie)

Alex
la source
4

L'alternative d'utiliser un nom de fichier prévisible avec $$ est une faille de sécurité béante et vous ne devriez jamais, jamais, jamais penser à l'utiliser. Même s'il ne s'agit que d'un simple script personnel sur votre PC mono-utilisateur. C'est une très mauvaise habitude que vous ne devriez pas prendre. BugTraq est plein d'incidents de "fichiers temporaires non sécurisés". Voir ici , ici et ici pour plus d'informations sur l'aspect sécurité des fichiers temporaires.

Je pensais au départ citer les affectations non sécurisées TMP1 et TMP2, mais à la réflexion, ce ne serait probablement pas une bonne idée .

Hlovdal
la source
Je donnerais si je pouvais: +1 pour les conseils de sécurité et un autre +1 pour ne pas citer de mauvaise idée et la référence
TMG
1

Je préfère utiliser tempfilequi crée un fichier dans / tmp de manière sûre et vous n'avez pas à vous soucier de son nom:

tmp=$(tempfile -s "your_sufix")
trap "rm -f '$tmp'" exit
Ruslan Kabalin
la source
tempfile est malheureusement très peu portable bien que plus sûr, il est donc souvent préférable de l'éviter ou du moins de l'émuler.
lerics
1

Je ne peux pas croire que tant de gens supposent qu'un nom de fichier ne contiendra pas d'espace. Le monde plantera si $ TMPDIR est assigné à un "répertoire temporaire".

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

Les espaces et autres caractères spéciaux comme les guillemets simples et les retours chariot dans les noms de fichiers doivent être considérés dans le code comme une exigence d'une habitude de programmation décente.

Paul
la source
+1 Bien que les guillemets simples trap 'rm -f "${zTemp}"' EXITgèrent correctement les espaces et autres caractères spéciaux, la solution de cette réponse ne reporte pas l'évaluation de zTemp. Il n'y a donc pas lieu de s'inquiéter de la valeur d' zTempêtre modifié plus tard dans le script. En outre, zTemppeut être déclaré local à une fonction; il n'a pas besoin d'être une variable de script globale.
Robin A. Meade le
Les guillemets doubles autour du RHS de l'affectation ne sont pas nécessaires.
Robin A. Meade le
Il convient de noter que les ${parameter@operator}extensions ont été ajoutées dans Bash 4.4 (publié en septembre 2016).
Robin A. Meade le
-4

Vous n'avez pas à vous soucier de supprimer les fichiers tmp créés avec mktemp. Ils seront supprimés de toute façon plus tard.

Utilisez mktemp si vous le pouvez car il génère plus de fichiers uniques que le préfixe «$$». Et cela ressemble à une manière plus multiplateforme de créer des fichiers temporaires, puis de les mettre explicitement dans / tmp.

Mykola Golubyev
la source
4
Supprimé par qui ou quoi?
innaM
Supprimé par l'opération | le système de fichiers lui-même après un certain temps
Mykola Golubyev
4
La magie? Un cronjob? Ou une machine Solaris redémarrée?
innaM
Probablement l'un d'entre eux. Si le fichier temporaire n'a pas été supprimé par une interruption (ce ne sera pas trop souvent) un jour, les fichiers tmp seront supprimés - c'est pourquoi ils ont appelé temp.
Mykola Golubyev
22
Vous ne pouvez pas, ne devriez pas, ne devez pas supposer que quelque chose mis dans / tmp y restera pour toujours; en même temps, vous ne devez pas supposer qu'il disparaîtra comme par magie.
innaM