Usuwanie utworzonych plików tymczasowych w nieoczekiwanym wyjściu bash

89

Tworzę pliki tymczasowe ze skryptu bash. Usuwam je pod koniec przetwarzania, ale skoro skrypt działa dość długo, jeśli go zabiję lub po prostu CTRL-C w trakcie działania, pliki tymczasowe nie są usuwane.
Czy istnieje sposób, w jaki mogę przechwycić te zdarzenia i wyczyścić pliki przed zakończeniem wykonywania?

Czy jest też jakaś najlepsza praktyka dotycząca nazewnictwa i lokalizacji tych plików tymczasowych?
Obecnie nie jestem pewien między użyciem:

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

i

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

A może są jakieś lepsze rozwiązania?

skinp
źródło

Odpowiedzi:

98

Możesz ustawić " pułapkę " do wykonania przy wyjściu lub na control-c w celu wyczyszczenia.

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

Alternatywnie, jednym z moich ulubionych unix-izmów jest otwarcie pliku, a następnie usunięcie go, gdy nadal jest otwarty. Plik pozostaje w systemie plików i możesz go czytać i zapisywać, ale gdy tylko program kończy pracę, plik znika. Nie jestem jednak pewien, jak to zrobiłbyś w bashu.

BTW: Jeden argument, który podam na korzyść mktemp zamiast używania własnego rozwiązania: jeśli użytkownik przewiduje, że twój program będzie tworzył ogromne pliki tymczasowe, może chcieć ustawić TMPDIRcoś większego, na przykład / var / tmp. mktemp rozpoznaje to, ale Twoje ręcznie tworzone rozwiązanie (druga opcja) nie. Na przykład często używam TMPDIR=/var/tmp gvim -d foo bar.

Paul Tomblin
źródło
8
Z Bash, exec 5<>$TMPFILEplik krawaty deskryptor 5 do $ tmpfile jak odczytu i zapisu, a można użyć <&5, >&5oraz /proc/$$/fd/5(Linux) później. Jedynym problemem jest to, że Bashowi brakuje seekfunkcji ...
ephemient
Zaakceptowałem Twoją odpowiedź, ponieważ podany przez Ciebie link najlepiej wyjaśnia, czego potrzebowałem. Dzięki
skinp
4
Kilka uwag na temat trap: nie ma sposobu na pułapkę SIGKILL(zgodnie z projektem, ponieważ natychmiast kończy wykonywanie). Więc jeśli tak się stanie, miej plan awaryjny (taki jak tmpreaper). Po drugie, pułapki nie kumulują się - jeśli masz więcej niż jedną akcję do wykonania, wszystkie muszą znajdować się w trapdowództwie. Jednym ze sposobów radzenia sobie z wieloma działań porządkowych jest zdefiniowanie funkcji (i można zdefiniować ją jako swoich wpływów programu, jeśli to konieczne) i odniesienia, który: trap cleanup_function EXIT.
Toby Speight
1
Musiałem użyć trap "rm -f $LOCKFILE" EXITlub otrzymałem nieoczekiwany błąd końca pliku.
Jaakko
3
Shellcheck ostrzegł przed użyciem pojedynczych cudzysłowów, że wyrażenie zostanie rozszerzone „teraz” podwójnymi cudzysłowami, a nie później, gdy zostanie wywołana pułapka.
LaFayette
110

Zwykle tworzę katalog, w którym umieszczam wszystkie moje pliki tymczasowe, a następnie natychmiast po utworzeniu programu obsługi EXIT, aby wyczyścić ten katalog po zakończeniu działania skryptu.

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

Jeśli umieścisz wszystkie pliki tymczasowe pod $MYTMPDIR, w większości przypadków zostaną one usunięte po zamknięciu skryptu. Zabicie procesu za pomocą SIGKILL (kill -9) zabija proces od razu, więc twoja obsługa EXIT nie będzie działać w tym przypadku.

Chris AtLee
źródło
27
+1 Zdecydowanie użyj pułapki na EXIT, a nie głupiego TERM / INT / HUP / cokolwiek innego możesz wymyślić. Jednak pamiętaj, aby zacytować swoje rozszerzenia parametrów i polecam również pojedyncze cytowanie pułapki: trap 'rm -rf "$ TMPDIR"' EXIT
lhunath
7
Pojedyncze cudzysłowy, ponieważ wtedy twoja pułapka będzie nadal działać, jeśli później w swoim skrypcie zdecydujesz się wyczyścić i zmienić TMPDIR z powodu okoliczności.
lhunath
1
@AaronDigulla Dlaczego znaki $ () vs odwrotne znaki są ważne?
Ogre Psalm33
3
@ OgrePsalm33: stackoverflow.com/questions/4708549/…
Aaron Digulla
3
Kod @AlexanderTorstling powinien zawsze znajdować się w apostrofach, aby zapobiec iniekcji skutkującej wykonaniem dowolnego kodu. Jeśli rozszerzysz dane do kodu bash STRING, wtedy te dane mogą teraz robić wszystko, co robi kod, co skutkuje niewinnymi błędami na białych przestrzeniach, ale także destrukcyjnymi błędami, takimi jak wyczyszczenie twojego katalogu domowego z dziwnych powodów lub wprowadzenie luk w zabezpieczeniach. Zauważ, że pułapka pobiera ciąg kodu basha, który zostanie później oceniony w takiej postaci, w jakiej jest. Więc później, kiedy pułapka odpali, pojedyncze cudzysłowy znikną, a będą tylko składniowe podwójne cudzysłowy.
lhunath
25

Chcesz użyć polecenia trap do obsługi wyjścia ze skryptu lub sygnałów, takich jak CTRL-C. Zobacz wiki Grega po szczegóły.

W przypadku basename $0plików tymczasowych dobrym pomysłem jest użycie , a także zapewnienie szablonu, który zapewnia wystarczającą liczbę plików tymczasowych:

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

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

trap 'rm -f $TMP1 $TMP2' EXIT
Brian Campbell
źródło
1
Nie pułapki na TERM / INT. Pułapka na EXIT. Próba przewidzenia warunku wyjścia na podstawie otrzymanych sygnałów jest głupia i zdecydowanie nie jest chwytem.
lhunath
3
Drobny punkt: użyj $ () zamiast pojedynczych znaków odwrotnych. I umieść cudzysłowy wokół $ 0, ponieważ może zawierać spacje.
Aaron Digulla
Cóż, grawerunki działają dobrze w tym komentarzu, ale to słuszna uwaga, dobrze jest mieć zwyczaj używania $(). Dodano również podwójne cudzysłowy.
Brian Campbell
1
Możesz zamienić cały podprogram na TMP1 = $ (plik tymczasowy -s "XXXXXX")
Ruslan Kabalin
4
@RuslanKabalin Nie wszystkie systemy mają tempfilepolecenie, podczas gdy wszystkie rozsądne nowoczesne systemy, które znam, mają mktemppolecenie.
Brian Campbell
9

Pamiętaj tylko, że wybrana odpowiedź to bashism, co oznacza rozwiązanie jako

trap "{ rm -f $LOCKFILE }" EXIT

działałby tylko w bashu (nie złapie Ctrl + c, jeśli shell jest dashlub klasyczny sh), ale jeśli chcesz kompatybilności, nadal musisz wyliczyć wszystkie sygnały, które chcesz przechwycić.

Należy również pamiętać, że kiedy skrypt wychodzi z pułapki na sygnał "0" (aka EXIT) jest zawsze wykonywany, co skutkuje podwójnym wykonaniem trappolecenia.

To powód, aby nie układać wszystkich sygnałów w jednej linii, jeśli jest sygnał EXIT.

Aby lepiej to zrozumieć, spójrz na następujący skrypt, który będzie działał w różnych systemach bez zmian:

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

To rozwiązanie da ci większą kontrolę, ponieważ możesz uruchomić część kodu na wystąpieniu rzeczywistego sygnału tuż przed końcowym wyjściem ( preExitfunkcja), a jeśli to konieczne, możesz uruchomić kod na rzeczywistym sygnale EXIT (końcowy etap wyjścia)

Alex
źródło
4

Alternatywą użycia przewidywalnej nazwy pliku z $$ jest ziejąca luka w zabezpieczeniach i nigdy, przenigdy nie powinieneś myśleć o jej użyciu. Nawet jeśli jest to zwykły osobisty skrypt na komputerze jednego użytkownika. Jest to bardzo zły nawyk, którego nie powinieneś nabywać. BugTraq jest pełen incydentów związanych z "niebezpiecznymi plikami tymczasowymi". Zobacz tutaj , tutaj i tutaj, aby uzyskać więcej informacji na temat aspektu bezpieczeństwa plików tymczasowych.

Początkowo myślałem o zacytowaniu niezabezpieczonych przypisań TMP1 i TMP2, ale po zastanowieniu prawdopodobnie nie byłby to dobry pomysł .

hlovdal
źródło
Dałbym, gdybym mógł: +1 za poradę dotyczącą bezpieczeństwa i kolejne +1 za niezacytowanie złego pomysłu i odniesienia
TMG
2

Nie mogę uwierzyć, że tak wiele osób zakłada, że ​​nazwa pliku nie będzie zawierała spacji. Świat się załamie, jeśli $ TMPDIR zostanie kiedykolwiek przypisany do „katalogu tymczasowego”.

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

Spacje i inne znaki specjalne, takie jak apostrofy i znaki powrotu karetki w nazwach plików, powinny być traktowane w kodzie jako wymóg przyzwoitego nawyku programowania.

Paweł
źródło
Należy zauważyć, że ${parameter@operator}rozszerzenia zostały dodane w Bash 4.4 (wydany we wrześniu 2016 r.).
Robin A. Meade
Odpowiednik dla Bash <v4.4:trap "rm -f $(printf %q "$zTemp")" EXIT
Robin A. Meade
To najlepsza odpowiedź, ponieważ nie ma potrzeby martwić się o wartość zTemppóźniejszych zmian w skrypcie. Ponadto, zTempmoże być uznana localdo funkcji; nie musi to być globalna zmienna skryptu.
Robin A. Meade
1

Wolę używać, tempfilektóry tworzy plik w / tmp w bezpieczny sposób i nie musisz martwić się o jego nazewnictwo:

tmp=$(tempfile -s "your_sufix")
trap "rm -f '$tmp'" exit
Ruslan Kabalin
źródło
Tempfile jest niestety bardzo nieporęczny, chociaż bezpieczniejszy, dlatego często lepiej go unikać lub przynajmniej naśladować.
lericson
-4

Nie musisz martwić się usuwaniem tych plików tmp utworzonych za pomocą mktemp. I tak zostaną później usunięte.

Jeśli możesz, użyj mktemp, ponieważ generuje on więcej unikalnych plików niż prefiks „$$”. Wygląda na bardziej wieloplatformowy sposób tworzenia plików tymczasowych, a następnie jawne umieszczanie ich w / tmp.

Mykoła Golubyev
źródło
4
Usunięte przez kogo lub przez co?
innaM
Usunięte przez operację | sam system plików po pewnym czasie
Mykoła Gołubjew
4
Magia? Cronjob? Albo zrestartowany komputer Solaris?
innaM
Prawdopodobnie jeden z nich. Jeśli plik tymczasowy nie został usunięty przez jakąś przerwę (nie będzie to zbyt często), pewnego dnia pliki tmp zostaną usunięte - dlatego nazwali temp.
Mykoła Gołubjew
22
Nie możesz, nie powinieneś, nie możesz zakładać, że coś wstawionego do / tmp pozostanie tam na zawsze; jednocześnie nie należy zakładać, że magicznie odejdzie.
innaM