Удаление созданных временных файлов при неожиданном выходе из bash

89

Я создаю временные файлы из сценария bash. Я удаляю их в конце обработки, но так как скрипт работает довольно долго, если я его убью или просто CTRL-C во время запуска, временные файлы не удаляются.
Есть ли способ поймать эти события и очистить файлы до завершения выполнения?

Кроме того, есть ли какая-то передовая практика для именования и расположения этих временных файлов?
В настоящее время я не уверен, использовать ли:

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

а также

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

Или, может быть, есть лучшие решения?

скинп
источник

Ответы:

98

Вы можете установить « ловушку » на выполнение при выходе или на Control-c для очистки.

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

В качестве альтернативы, один из моих любимых unix-isms - открыть файл, а затем удалить его, пока он еще открыт. Файл остается в файловой системе, и вы можете читать и записывать его, но как только ваша программа завершает работу, файл исчезает. Хотя не знаю, как это сделать в bash.

Кстати: один аргумент, который я приведу в пользу mktemp вместо использования вашего собственного решения: если пользователь ожидает, что ваша программа будет создавать огромные временные файлы, он может захотеть установить TMPDIRчто-то большее, например / var / tmp. mktemp понимает это, а ваше ручное решение (второй вариант) - нет. Я TMPDIR=/var/tmp gvim -d foo bar, например, часто использую .

Пол Томблин
источник
8
С Bash, exec 5<>$TMPFILEсвязывает дескриптор файла 5 до $ TMPFILE чтения и записи, и вы можете использовать <&5, >&5и /proc/$$/fd/5(Linux) в дальнейшем. Единственная проблема в том, что Bash не seekработает ...
ephemient
Принял ваш ответ, поскольку предоставленная вами ссылка как нельзя лучше объясняет то, что мне нужно. Спасибо
skinp
4
Несколько замечаний по trapповоду: ловушки нет SIGKILL(по замыслу, так как выполнение немедленно прекращается). Итак, если это может произойти, разработайте запасной план (например, tmpreaper). Во-вторых, ловушки не суммируются - если вам нужно выполнить более одного действия, все они должны быть в trapкоманде. Один из способов справиться с несколькими действиями очистки заключается в определении функции (и вы можете переопределить его как ваша программа продолжается, если это необходимо) и ссылка , что: trap cleanup_function EXIT.
Тоби Спейт
1
Мне пришлось использовать, trap "rm -f $LOCKFILE" EXITиначе я получу неожиданную ошибку конца файла.
Jaakko
3
Shellcheck предупредил об использовании одинарных кавычек, чтобы выражение было расширено «сейчас» двойными кавычками, а не позже, когда срабатывает ловушка.
LaFayette
110

Обычно я создаю каталог, в который помещаю все мои временные файлы, а затем сразу же после этого создаю обработчик EXIT для очистки этого каталога при выходе из сценария.

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

Если вы поместите все свои временные файлы внутрь $MYTMPDIR, то в большинстве случаев они все будут удалены при выходе из сценария. Однако завершение процесса с помощью SIGKILL (kill -9) приводит к немедленному уничтожению процесса, поэтому ваш обработчик EXIT не будет работать в этом случае.

Крис Атли
источник
27
+1 Обязательно используйте ловушку на EXIT, а не глупые TERM / INT / HUP / все, что вы можете придумать. Тем не менее, не забудьте процитировать ваши расширения параметров, и я также рекомендовал бы вам использовать одинарные кавычки для вашей ловушки: trap 'rm -rf "$ TMPDIR"' EXIT
lhunath
7
Одиночные кавычки, потому что тогда ваша ловушка все равно будет работать, если позже в вашем скрипте вы решите очистить и изменить TMPDIR из-за обстоятельств.
lhunath
1
@AaronDigulla Почему важны $ () и обратные кавычки?
Ogre Psalm33
3
@ OgrePsalm33: stackoverflow.com/questions/4708549/…
Аарон Дигулла
3
Код @AlexanderTorstling всегда должен заключаться в одинарные кавычки, чтобы предотвратить внедрение, приводящее к выполнению произвольного кода. Если вы расширяете данные в bash-код STRING, то эти данные теперь могут делать все, что делает код, что приводит к невинным ошибкам в отношении пробелов, а также к деструктивным ошибкам, таким как очистка вашего домашнего каталога по странным причинам или введение дыр в безопасности. Обратите внимание, что trap принимает строку кода bash, которая позже будет оцениваться как есть. Поэтому позже, когда сработает ловушка, одинарные кавычки исчезнут, и останутся только синтаксические двойные кавычки.
lhunath
25

Вы хотите использовать команду trap для обработки выхода из сценария или сигналов типа CTRL-C. См. Подробности в Greg's Wiki .

Для ваших basename $0временных файлов использование - хорошая идея, так же как и предоставление шаблона, который предоставляет место для достаточного количества временных файлов:

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

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

trap 'rm -f $TMP1 $TMP2' EXIT
Брайан Кэмпбелл
источник
1
Не ловите TERM / INT. Ловушка на ВЫХОДЕ. Пытаться предсказать условия выхода на основе полученных сигналов глупо и определенно не является уловкой.
lhunath
3
Незначительный момент: используйте $ () вместо одиночных обратных кавычек. И заключите $ 0 в двойные кавычки, потому что он может содержать пробелы.
Аарон Дигулла
Что ж, обратные кавычки в этом комментарии работают нормально, но это справедливый момент, хорошо иметь привычку использовать $(). Также добавлены двойные кавычки.
Брайан Кэмпбелл
1
Вы можете заменить всю подпрограмму только на TMP1 = $ (tempfile -s "XXXXXX")
Руслан Кабалин
4
@RuslanKabalin Не во всех системах есть tempfileкоманда, в то время как все разумные современные системы, о которых я знаю, имеют mktempкоманду.
Брайан Кэмпбелл
9

Просто имейте в виду, что выбранный ответ - bashismэто решение как

trap "{ rm -f $LOCKFILE }" EXIT

будет работать только в bash (он не будет ловить Ctrl + c, если оболочка является dashили классической sh), но если вам нужна совместимость, вам все равно нужно перечислить все сигналы, которые вы хотите перехватить.

Также имейте в виду, что при выходе из сценария всегда выполняется ловушка для сигнала «0» (он же EXIT), что приводит к двойному выполнению trapкоманды.

Это причина не складывать все сигналы в одну строку, если есть сигнал EXIT.

Чтобы лучше понять это, посмотрите следующий сценарий, который будет работать в разных системах без изменений:

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

Это решение предоставит вам больше контроля, поскольку вы можете запустить часть своего кода при возникновении фактического сигнала непосредственно перед окончательным выходом ( preExitфункция), и, если это необходимо, вы можете запустить некоторый код при фактическом сигнале EXIT (последний этап выхода)

Alex
источник
4

Альтернатива использования предсказуемого имени файла с $$ - это зияющая дыра в безопасности, и вам никогда и никогда не следует думать об ее использовании. Даже если это простой персональный скрипт на вашем однопользовательском ПК. Это очень плохая привычка, которой не следует приобретать. BugTraq полон инцидентов, связанных с "небезопасным временным файлом". См. Здесь , здесь и здесь для получения дополнительной информации об аспектах безопасности временных файлов.

Сначала я подумывал процитировать небезопасные назначения TMP1 и TMP2, но, если подумать, это, вероятно, не было бы хорошей идеей .

Hlovdal
источник
Я бы дал, если бы мог: +1 за совет по безопасности и еще один за то, что не цитирую плохую идею и ссылку
TMG
2

Я не могу поверить, что так много людей предполагают, что имя файла не будет содержать пробела. Мир рухнет, если $ TMPDIR когда-либо будет назначен «временный каталог».

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

Пробелы и другие специальные символы, такие как одинарные кавычки и символы возврата каретки в именах файлов, следует рассматривать в коде как требование хорошей привычки программирования.

Павел
источник
Следует отметить, что ${parameter@operator}расширения были добавлены в Bash 4.4 (выпущен в сентябре 2016 г.).
Робин А. Мид,
Эквивалент для Bash <v4.4:trap "rm -f $(printf %q "$zTemp")" EXIT
Робин А. Мид
Это лучший ответ, потому что не нужно беспокоиться о том, что значение zTempбудет изменено позже в сценарии. Кроме того, zTempможно объявить localфункцию; это не обязательно должна быть глобальная переменная сценария.
Робин А. Мид,
1

Я предпочитаю использовать, tempfileкоторый создает файл в / tmp безопасным способом, и вам не нужно беспокоиться о его именовании:

tmp=$(tempfile -s "your_sufix")
trap "rm -f '$tmp'" exit
Руслан Кабалин
источник
tempfile, к сожалению, очень непереносимый, хотя и более безопасный, поэтому часто лучше его избегать или, по крайней мере, имитировать.
lericson
-4

Вам не нужно беспокоиться об удалении этих файлов tmp, созданных с помощью mktemp. Они все равно будут удалены позже.

По возможности используйте mktemp, поскольку он генерирует больше уникальных файлов, чем префикс $$. И это похоже на более кроссплатформенный способ создания временных файлов, а затем их явного помещения в / tmp.

Николай Голубев
источник
4
Кем или чем удалено?
innaM
Удалено операцией | сама файловая система через некоторое время
Николай Голубев
4
Магия? Cronjob? Или перезагруженный Солярис?
innaM
Наверное, один из них. Если временный файл не был удален из-за какого-то прерывания (это будет не так часто), когда-нибудь файлы tmp будут удалены - поэтому они вызвали temp.
Николай Голубев,
22
Вы не можете, не должны, не должны предполагать, что что-то, помещенное в / tmp, останется там навсегда; при этом не стоит предполагать, что он уйдет волшебным образом.
innaM