Entfernen von erstellten temporären Dateien bei unerwartetem Bash-Exit

89

Ich erstelle temporäre Dateien aus einem Bash-Skript. Ich lösche sie am Ende der Verarbeitung, aber da das Skript ziemlich lange läuft, werden die temporären Dateien nicht gelöscht, wenn ich es während des Laufs töte oder einfach STRG-C.
Gibt es eine Möglichkeit, diese Ereignisse abzufangen und die Dateien zu bereinigen, bevor die Ausführung endet?

Gibt es auch eine bewährte Methode für die Benennung und den Speicherort dieser temporären Dateien?
Ich bin mir derzeit nicht sicher, ob ich Folgendes verwenden soll:

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

und

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

Oder gibt es vielleicht bessere Lösungen?

skinp
quelle

Antworten:

97

Sie können eine " Falle " festlegen , die beim Beenden ausgeführt wird, oder eine Steuerung-c, um sie zu bereinigen.

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

Alternativ besteht einer meiner Lieblings-Unix-Ismen darin, eine Datei zu öffnen und sie dann zu löschen, während Sie sie noch geöffnet haben. Die Datei bleibt im Dateisystem und Sie können sie lesen und schreiben. Sobald Ihr Programm beendet wird, verschwindet die Datei. Ich bin mir jedoch nicht sicher, wie Sie das in Bash machen würden.

Übrigens: Ein Argument, das ich für mktemp geben werde, anstatt Ihre eigene Lösung zu verwenden: Wenn der Benutzer erwartet, dass Ihr Programm große temporäre Dateien erstellen wird, möchte er möglicherweise einen TMPDIRgrößeren Ort wie / var / tmp festlegen . mktemp erkennt, dass Ihre handgerollte Lösung (zweite Option) dies nicht tut. Ich benutze TMPDIR=/var/tmp gvim -d foo barzum Beispiel häufig.

Paul Tomblin
quelle
8
Mit Bash, exec 5<>$TMPFILE5 Bindungen Dateideskriptors TMPFILE als Schreib-Lese-Dollar, und Sie verwenden können <&5, >&5und /proc/$$/fd/5(Linux) danach. Das einzige Problem ist, dass Bash keine seekFunktion hat ...
Ephemient
Akzeptiert, dass Sie antworten, da der von Ihnen angegebene Link das Beste erklärt, was ich brauchte. Danke
Skinp
4
Ein paar Anmerkungen zu trap: Es gibt keine Möglichkeit zum Einfangen SIGKILL(beabsichtigt, da die Ausführung sofort beendet wird). Wenn dies passieren könnte, haben Sie einen Fallback-Plan (z. B. tmpreaper). Zweitens sind Traps nicht kumulativ. Wenn Sie mehr als eine Aktion ausführen müssen, müssen sie alle im trapBefehl enthalten sein. Eine Möglichkeit, mit mehreren Bereinigungsaktionen umzugehen, besteht darin, eine Funktion zu definieren (und Sie können sie bei Bedarf im Verlauf Ihres Programms neu definieren) und darauf zu verweisen : trap cleanup_function EXIT.
Toby Speight
1
Ich musste verwenden trap "rm -f $LOCKFILE" EXIToder ich würde einen unerwarteten Fehler am Ende der Datei bekommen.
Jaakko
3
Shellcheck warnte vor der Verwendung von einfachen Anführungszeichen, dass der Ausdruck "jetzt" mit doppelten Anführungszeichen erweitert wird und nicht später, wenn der Trap aufgerufen wird.
LaFayette
110

Normalerweise erstelle ich ein Verzeichnis, in dem alle meine temporären Dateien abgelegt werden sollen, und erstelle anschließend einen EXIT-Handler, um dieses Verzeichnis beim Beenden des Skripts zu bereinigen.

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

Wenn Sie alle Ihre temporären Dateien unter ablegen $MYTMPDIR, werden sie alle gelöscht, wenn Ihr Skript in den meisten Fällen beendet wird. Wenn Sie einen Prozess mit SIGKILL (kill -9) beenden, wird der Prozess jedoch sofort beendet, sodass Ihr EXIT-Handler in diesem Fall nicht ausgeführt wird.

Chris AtLee
quelle
27
+1 Verwenden Sie auf jeden Fall eine Falle bei EXIT, nicht dummes TERM / INT / HUP / was auch immer Sie sich sonst vorstellen können. Obwohl, denken Sie daran zu zitieren Ihre Parameter Erweiterungen und ich würde auch empfehlen Ihnen , einzelnes Zitat Ihrer Falle: trap ‚rm -rf‚$ TMPDIR‘‘ EXIT
lhunath
7
Einfache Anführungszeichen, da Ihre Falle dann immer noch funktioniert, wenn Sie später in Ihrem Skript aufgrund von Umständen beschließen, TMPDIR zu bereinigen und zu ändern.
lhunath
1
@AaronDigulla Warum ist $ () vs Backticks wichtig?
Oger Psalm33
3
@ OgrePsalm33: stackoverflow.com/questions/4708549/…
Aaron Digulla
3
@ AlexanderTorstling-Code sollte immer in einfache Anführungszeichen gesetzt werden, um eine Injektion zu verhindern, die zu einer beliebigen Codeausführung führt. Wenn Sie Daten zu einem Bash-Code STRING erweitern, können diese Daten jetzt alles tun, was Code tut, was zu unschuldigen Fehlern im Leerraum führt, aber auch zu zerstörerischen Fehlern wie dem Löschen Ihres Homedirs aus bizarren Gründen oder dem Einführen von Sicherheitslücken. Beachten Sie, dass Trap eine Zeichenfolge aus Bash-Code verwendet, die später so wie sie ist ausgewertet wird. Wenn also später die Falle ausgelöst wird, sind die einfachen Anführungszeichen weg und es gibt nur die syntaktischen doppelten Anführungszeichen.
lhunath
25

Sie möchten den Befehl trap verwenden , um das Beenden des Skripts oder von Signalen wie STRG-C zu handhaben. Weitere Informationen finden Sie im Greg's Wiki .

Für Ihre temporären Dateien ist die Verwendung basename $0eine gute Idee sowie die Bereitstellung einer Vorlage, die Platz für genügend temporäre Dateien bietet:

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

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

trap 'rm -f $TMP1 $TMP2' EXIT
Brian Campbell
quelle
1
Nicht auf TERM / INT fangen. Falle beim Verlassen. Der Versuch, den Ausgangszustand anhand der empfangenen Signale vorherzusagen, ist albern und definitiv kein Haken.
lhunath
3
Kleiner Punkt: Verwenden Sie $ () anstelle einzelner Backticks. Und setzen Sie doppelte Anführungszeichen um $ 0, weil es Leerzeichen enthalten könnte.
Aaron Digulla
1
Sie können Ihre gesamte Unterroutine durch nur TMP1 = $ (tempfile -s "XXXXXX")
ersetzen
4
@ RuslanKabalin Nicht alle Systeme haben einen tempfileBefehl, während alle vernünftigen modernen Systeme, die ich kenne, einen mktempBefehl haben.
Brian Campbell
1
@tripleee Du hast recht. Ich habe den Verweis auf Gregs Wiki aktualisiert, das im Allgemeinen eine viel bessere Quelle ist. Es gibt bereits einen Link zur maßgeblichen Dokumentation, aber ich wollte auch einen Link mit weiteren Erklärungen und Beispielen bereitstellen. Ich finde Gregs Wiki viel besser als ABS im Allgemeinen. Ich hoffe, Sie finden, dass dies eine bessere Quelle ist.
Brian Campbell
9

Denken Sie daran, dass die gewählte Antwort lautet bashism, was Lösung als bedeutet

trap "{ rm -f $LOCKFILE }" EXIT

würde nur in bash funktionieren (es wird Strg + c nicht abfangen, wenn Shell dashoder klassisch ist sh), aber wenn Sie Kompatibilität wünschen, müssen Sie immer noch alle Signale auflisten, die Sie abfangen möchten.

Denken Sie auch daran, dass beim Beenden des Skripts die Falle für das Signal "0" (auch bekannt als EXIT) immer ausgeführt wird, was zu einer doppelten Ausführung des trapBefehls führt.

Das ist der Grund, nicht alle Signale in einer Zeile zu stapeln, wenn es ein EXIT-Signal gibt.

Zum besseren Verständnis sehen Sie sich das folgende Skript an, das ohne Änderungen auf verschiedenen Systemen funktioniert:

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

Diese Lösung gibt Ihnen mehr Kontrolle, da Sie einen Teil Ihres Codes beim Auftreten des tatsächlichen Signals kurz vor dem endgültigen Beenden ( preExitFunktion) ausführen können und bei Bedarf Code beim tatsächlichen EXIT-Signal (letzte Stufe des Beenden) ausführen können.

Alex
quelle
4

Die Alternative, einen vorhersehbaren Dateinamen mit $$ zu verwenden, ist eine klaffende Sicherheitslücke, und Sie sollten niemals daran denken, ihn zu verwenden. Auch wenn es sich nur um ein einfaches persönliches Skript auf Ihrem Einzelbenutzer-PC handelt. Es ist eine sehr schlechte Angewohnheit, die Sie nicht erhalten sollten. BugTraq ist voll von Vorfällen mit "unsicheren temporären Dateien". Weitere Informationen zum Sicherheitsaspekt von temporären Dateien finden Sie hier , hier und hier .

Ich dachte anfangs daran, die unsicheren TMP1- und TMP2-Zuweisungen zu zitieren, aber beim zweiten Gedanken wäre das wahrscheinlich keine gute Idee .

hlovdal
quelle
Ich würde geben, wenn ich könnte: +1 für den Sicherheitshinweis und einen weiteren +1 für das Nichtzitieren einer schlechten Idee und der Referenz
TMG
1

Ich bevorzuge die Verwendung tempfileeiner Datei in / tmp auf sichere Weise, und Sie müssen sich keine Gedanken über deren Benennung machen:

tmp=$(tempfile -s "your_sufix")
trap "rm -f '$tmp'" exit
Ruslan Kabalin
quelle
tempfile ist leider sehr unsportlich, obwohl sicherer, daher ist es oft besser, es zu vermeiden oder zumindest zu emulieren.
Lericson
1

Ich kann nicht glauben, dass so viele Leute davon ausgehen, dass ein Dateiname kein Leerzeichen enthält. Die Welt wird abstürzen, wenn $ TMPDIR jemals einem "temporären Verzeichnis" zugewiesen wird.

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

Leerzeichen und andere Sonderzeichen wie einfache Anführungszeichen und Zeilenumbrüche in Dateinamen sollten im Code als Voraussetzung für eine angemessene Programmiergewohnheit betrachtet werden.

Paul
quelle
+1 Während in einfachen Anführungszeichen trap 'rm -f "${zTemp}"' EXITLeerzeichen und andere Sonderzeichen korrekt behandelt werden, verschiebt die Lösung dieser Antwort die Bewertung von nicht zTemp. Sie müssen sich daher keine Gedanken darüber machen, welchen Wert zTempes hat, später im Skript geändert zu werden. Auch zTempkann erklärt lokal für eine Funktion werden; Es muss keine globale Skriptvariable sein.
Robin A. Meade
Die doppelten Anführungszeichen um die RHS der Zuordnung sind nicht erforderlich.
Robin A. Meade
Es ist zu beachten, dass die ${parameter@operator}Erweiterungen in Bash 4.4 (veröffentlicht im September 2016) hinzugefügt wurden.
Robin A. Meade
-4

Sie müssen sich nicht die Mühe machen, die mit mktemp erstellten tmp-Dateien zu entfernen. Sie werden später trotzdem gelöscht.

Verwenden Sie mktemp, wenn Sie können, da es mehr eindeutige Dateien als das Präfix '$$' generiert. Und es sieht nach einer plattformübergreifenden Methode aus, temporäre Dateien zu erstellen, als sie explizit in / tmp abzulegen.

Mykola Golubyev
quelle
4
Von wem oder was gelöscht?
InnaM
Nach einiger Zeit vom Operation | Dateisystem selbst gelöscht
Mykola Golubyev
4
Magie? Ein Cronjob? Oder eine neu gestartete Solaris-Maschine?
InnaM
Wahrscheinlich einer von ihnen. Wenn temporäre Dateien nicht durch eine Unterbrechung entfernt wurden (es wird nicht zu oft sein), werden eines Tages tmp-Dateien entfernt - deshalb haben sie temp genannt.
Mykola Golubyev
21
Sie können, sollten nicht, dürfen nicht davon ausgehen, dass etwas, das in / tmp eingegeben wird, für immer dort bleibt; Gleichzeitig sollten Sie nicht davon ausgehen, dass es auf magische Weise verschwindet.
InnaM