Безопасные временные файлы и каталоги блокировок
Используйте mktemp и flock для создания временных ресурсов без состязаний и предотвращения одновременного запуска скриптов
«Безопасные временные файлы и каталоги блокировок» — бесплатный урок DevOps Bootcamp на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DevOps Bootcamp, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DevOps Bootcamp содержит 4 уроков всего.
Почему временные файлы представляют угрозу безопасности
Сценариям Bash часто требуется временное хранилище — для промежуточных результатов, маркеров блокировки и областей подготовки. Но неосторожное создание временных файлов открывает серьёзные уязвимости.
- Состояния гонки: другой процесс может предсказать имя файла и создать его первым, перенаправив Вашу запись.
- Атаки через символические ссылки: злоумышленник создаёт символическую ссылку по ожидаемому пути, указывающую на важный файл, например
/etc/passwd. - Оставшиеся файлы: если сценарий аварийно завершится, временные файлы будут накапливаться и могут раскрыть конфиденциальные данные.
Два основных инструмента, устраняющих эти проблемы, — mktemp и flock. В этом уроке Вы узнаете, как безопасно и надёжно использовать оба инструмента.
Безопасное создание временных файлов с помощью mktemp
mktemp создаёт временный файл со случайным непредсказуемым именем и возвращает его путь. Файл создаётся атомарно, поэтому другой процесс не сможет первым занять это имя.
- Синтаксис:
mktemp [TEMPLATE]— шаблон должен заканчиваться как минимум тремя символамиX. - Каждый символ
Xзаменяется случайным символом, в результате чего получается уникальное имя, например/tmp/script.aB3kQz. - Файл автоматически создаётся с правами
0600(читать его может только владелец).
Всегда сразу сохраняйте возвращённый путь в переменной, чтобы позднее обращаться к файлу и удалять его.
#!/usr/bin/env bash
set -euo pipefail
# Create a secure temp file
TMPFILE=$(mktemp /tmp/myapp.XXXXXX)
echo "Temp file created at: $TMPFILE"
# Write data to it
echo "some intermediate result" > "$TMPFILE"
# Read it back
cat "$TMPFILE"
# Clean up
rm -f "$TMPFILE"Всегда выполняйте очистку с помощью trap
Если сценарий неожиданно завершится — из-за ошибки, сигнала или срабатывания set -e, — временные файлы останутся, если не зарегистрировать обработчик очистки.
Встроенная команда trap выполняет команду, когда оболочка получает сигнал или завершается. Канонический шаблон очистки временного файла:
- Зарегистрируйте ловушку сразу после создания временного файла.
- Установите ловушку для
EXIT, чтобы очистка выполнялась и при штатном, и при аварийном завершении. - Также установите ловушки для
INTиTERM, если сценарий работает долго или взаимодействует с пользователем.
Это гарантирует отсутствие бесхозных файлов, даже если сценарий будет принудительно завершён в процессе выполнения.
#!/usr/bin/env bash
set -euo pipefail
TMPFILE=$(mktemp /tmp/report.XXXXXX)
# Register cleanup before doing any real work
cleanup() {
rm -f "$TMPFILE"
echo "Cleaned up $TMPFILE" >&2
}
trap cleanup EXIT
# Do work — even if this fails, cleanup() will run
echo "Processing..." > "$TMPFILE"
grep "result" "$TMPFILE" || true
echo "Done. File will be removed on exit."Создание временных каталогов с помощью mktemp -d
Иногда Вам нужен целый каталог для размещения нескольких файлов — например, для создания архива или распаковки tar-архива перед обработкой. Используйте mktemp -d, чтобы создать безопасный временный каталог.
- Каталог создаётся с разрешениями
0700(доступ только владельцу). - Удаляйте его с помощью
rm -rfв обработчикеtrap— внимательно следите за тем, чтобы удалять только переменную, а не жёстко заданный путь. - Используйте двойные кавычки и перед вызовом
rm -rfпроверяйте, что переменная не пуста, в качестве дополнительной меры безопасности.
#!/usr/bin/env bash
set -euo pipefail
TMPDIR=$(mktemp -d /tmp/extract.XXXXXX)
cleanup() {
# Guard: only rm if variable is set and non-empty
[[ -n "${TMPDIR:-}" ]] && rm -rf "$TMPDIR"
}
trap cleanup EXIT
echo "Working in $TMPDIR"
# Simulate staging files
echo "file one" > "$TMPDIR/part1.txt"
echo "file two" > "$TMPDIR/part2.txt"
ls "$TMPDIR"
echo "All done."Проблема одновременного запуска скриптов
Задания cron, таймеры systemd и скрипты, запущенные вручную, могут легко одновременно запустить несколько экземпляров одного и того же скрипта. Это приводит к следующему:
- Повторная обработка: одни и те же записи базы данных или файлы обрабатываются дважды.
- Повреждение результата: два экземпляра одновременно записывают данные в один и тот же выходной файл.
- Взаимные блокировки или частично изменённое состояние: оба экземпляра изменяют общие ресурсы в перемешанном непредсказуемом порядке.
Традиционным решением было записать PID-файл и проверить его при запуске, но между проверкой и записью возникает окно состязания. Правильное современное решение — flock, использующий рекомендательный механизм блокировок ядра для гарантированной блокировки без состязаний.
Блокировка с помощью flock: однострочный шаблон
flock устанавливает рекомендательную блокировку дескриптора файла перед выполнением команды. Самый простой способ использования — обернуть весь скрипт в командной строке:
flock -n /var/lock/myscript.lock bash myscript.sh
-n(неблокирующий режим): немедленно завершает работу со статусом 1, если блокировка уже установлена, вместо ожидания.- Без
-nкомандаflockблокируется до освобождения блокировки — это удобно для постановки в очередь. - Сам файл блокировки является лишь маркером; его содержимое не имеет значения. Безопасно сохранять его между запусками.
- Когда процесс, удерживающий блокировку, завершает работу, ядро автоматически освобождает блокировку — ручная очистка не требуется.
#!/usr/bin/env bash
# launcher.sh — prevents concurrent runs of worker.sh
set -euo pipefail
LOCKFILE="/tmp/myworker.lock"
if ! flock -n "$LOCKFILE" bash -c 'echo "Running worker..."; sleep 2; echo "Done."'; then
echo "Another instance is already running. Exiting." >&2
exit 1
fiИспользование flock внутри скрипта с дескриптором файла
Чтобы установить блокировку внутри скрипта, а не оборачивать его снаружи, используйте exec для открытия дескриптора файла, а затем вызовите flock для этого дескриптора. Это стандартный шаблон для рабочих скриптов.
exec 200>"$LOCKFILE"открывает файл для записи через дескриптор 200 (и создаёт его при необходимости).flock -n 200пытается установить неблокирующую блокировку дескриптора 200.- Поскольку блокировка связана с дескриптором файла, а не с именем файла, она автоматически освобождается при завершении процесса оболочки.
- Номера дескрипторов 200–299 обычно используют, чтобы избежать конфликтов с stdin/stdout/stderr.
#!/usr/bin/env bash
set -euo pipefail
LOCKFILE="/tmp/myjob.lock"
# Open lock file on FD 200
exec 200>"$LOCKFILE"
# Attempt non-blocking lock
if ! flock -n 200; then
echo "ERROR: Another instance of this script is running." >&2
exit 1
fi
echo "Lock acquired. Starting work..."
sleep 1
echo "Work complete. Lock will be released on exit."Объединение mktemp и flock в одном скрипте
Настоящим защитным скриптам нужны оба механизма: блокировка для предотвращения одновременных запусков и безопасные временные файлы для промежуточных данных. Ниже приведён полный шаблон, объединяющий оба приёма:
- Сначала установите блокировку — до создания временных файлов, — чтобы работу вообще выполнял только один экземпляр.
- Создавайте временные ресурсы после подтверждения блокировки.
- Сразу после создания временных ресурсов зарегистрируйте
trap, чтобы очистка гарантированно выполнялась независимо от способа завершения скрипта. - Файл блокировки никогда не помещайте во временный каталог — он должен сохраняться между запусками, чтобы
flockмог обращаться к нему.
#!/usr/bin/env bash
set -euo pipefail
LOCKFILE="/tmp/report_builder.lock"
exec 200>"$LOCKFILE"
if ! flock -n 200; then
echo "Already running — aborting." >&2
exit 1
fi
# Now safe to create temp resources
TMPDIR=$(mktemp -d /tmp/report.XXXXXX)
TMPLOG=$(mktemp /tmp/report_log.XXXXXX)
cleanup() {
rm -rf "${TMPDIR:-}"
rm -f "${TMPLOG:-}"
}
trap cleanup EXIT
echo "Building report in $TMPDIR" | tee "$TMPLOG"
echo "Step 1 complete" >> "$TMPLOG"
cat "$TMPLOG"Блокировка каталогами как альтернативный механизм
В системах, где flock недоступен (например, во встраиваемых системах или сетевых файловых системах, таких как NFS), вместо него можно использовать каталоги блокировки. В системах POSIX команда mkdir выполняется атомарно: она завершается успешно, только если каталога ещё не существует.
- Создайте каталог блокировки с помощью
mkdir /tmp/myscript.lock.d— если другой экземпляр уже создал его, командаmkdirнемедленно завершится с ошибкой. - Храните внутри каталога метаданные (например, PID) для диагностики.
- Всегда удаляйте каталог в обработчике
trapдляEXIT. - Важно: в отличие от
flock, блокировка каталогом не освобождается автоматически, если процесс завершён с помощью-9или компьютер перезагружается. Добавьте проверку устаревшей блокировки.
#!/usr/bin/env bash
set -euo pipefail
LOCKDIR="/tmp/myscript.lock.d"
# Atomic mkdir — fails if directory already exists
if ! mkdir "$LOCKDIR" 2>/dev/null; then
# Check if the holding PID is still alive
HOLDER_PID=$(cat "$LOCKDIR/pid" 2>/dev/null || echo "")
if [[ -n "$HOLDER_PID" ]] && kill -0 "$HOLDER_PID" 2>/dev/null; then
echo "Locked by PID $HOLDER_PID. Exiting." >&2
exit 1
else
echo "Stale lock detected. Removing and continuing." >&2
rm -rf "$LOCKDIR"
mkdir "$LOCKDIR"
fi
fi
echo "$$" > "$LOCKDIR/pid"
trap 'rm -rf "$LOCKDIR"' EXIT
echo "Lock acquired via directory. Running..."
sleep 1
echo "Done."Ожидание с ограничением времени с помощью flock
Иногда нужно дождаться блокировки, а не немедленно завершить работу с ошибкой, но ждать бесконечно тоже нельзя. flock поддерживает ограничение времени с помощью флага -w.
flock -w 10 200ждёт блокировку не более 10 секунд, а затем завершается со статусом 1, если она всё ещё недоступна.- Это идеально подходит для скриптов, которые должны встать в очередь за короткоживущим предшествующим процессом, но завершить ожидание, если тот завис.
- Сочетайте
-wс содержательным сообщением об ошибке, включающим контекст — путь к файлу блокировки и длительность ожидания, — чтобы операторы могли быстро диагностировать зависания.
#!/usr/bin/env bash
set -euo pipefail
LOCKFILE="/tmp/data_sync.lock"
TIMEOUT=15
exec 200>"$LOCKFILE"
echo "Waiting up to ${TIMEOUT}s for lock on $LOCKFILE..."
if ! flock -w "$TIMEOUT" 200; then
echo "ERROR: Could not acquire lock after ${TIMEOUT}s." \
"Another instance may be hung." >&2
exit 1
fi
echo "Lock acquired. Syncing data..."
sleep 1
echo "Sync complete."Контрольный список защиты: безопасные временные ресурсы
Перед выпуском любого скрипта, использующего временные файлы или блокировки, пройдите этот контрольный список:
- Используйте
mktemp, а не жёстко заданные пути — путь/tmp/myapp.tmpпредсказуем и может быть использован злоумышленником. - Сразу сохраните путь — выполните
TMPFILE=$(mktemp ...)до любой другой команды. - Сразу после создания зарегистрируйте
trap cleanup EXIT— не откладывайте это до конца скрипта. - Заключайте все обращения к переменным в двойные кавычки — используйте
rm -f "$TMPFILE", а неrm -f $TMPFILE. - Предпочитайте
flockPID-файлам — блокировкой управляет ядро, и она автоматически освобождается при аварийном завершении. - По умолчанию используйте неблокирующий режим
-n— молчаливое ожидание блокировок скрывает проблемы с производительностью. - Размещайте файл блокировки вне временного каталога — тогда он сохранится после очистки в обработчике
trap. - Проверьте поведение очистки — запустите скрипт и выполните для него
kill -9во время работы; убедитесь, что не осталось лишних файлов (для скриптов на основеflock; блокировки каталогами требуют дополнительных мер).
Проверка знаний: поведение флагов flock
Задание cron запускается каждую минуту и обрабатывает общий файл. Вы хотите, чтобы любой новый запуск немедленно завершался с ошибкой, если предыдущий запуск ещё активен, не дожидаясь его завершения. Какой вызов flock правильно это реализует?
Итоги: безопасные временные файлы и каталоги блокировки
В этом уроке Вы изучили два основных инструмента безопасного управления ресурсами в Bash:
mktempсоздаёт непредсказуемые временные файлы (0600) и каталоги (0700) с безопасными разрешениями, устраняя состязания и атаки через символические ссылки, характерные для жёстко заданных путей.trap cleanup EXITгарантирует удаление временных файлов при любом завершении — обычном, вызванном ошибкой или сигналом, — если зарегистрировать его сразу после создания.flockпредоставляет рекомендательную блокировку, обеспечиваемую ядром: используйте-nдля немедленного завершения при конфликте,-w Nдля ожидания с ограничением времени и шаблонexec 200>fileдля блокировки внутри скрипта, которую ядро автоматически освобождает при завершении процесса.- Каталоги блокировки (
mkdir) служат переносимым резервным вариантом в средах, гдеflockнедоступен, но требуют явной проверки устаревших блокировок. - Всегда храните файл блокировки вне временного каталога и заключайте каждое используемое при очистке обращение к переменной в двойные кавычки.
Сочетание mktemp + flock + trap позволяет создавать скрипты, защищённые от одновременных запусков, непредсказуемых сбоев и злонамеренных манипуляций с файловой системой.
Часто задаваемые вопросы
Урок «Безопасные временные файлы и каталоги блокировок» бесплатный?
Да — полный текст урока «Безопасные временные файлы и каталоги блокировок» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс DevOps Bootcamp, подпишись на CoddyKit PRO. Курс DevOps Bootcamp содержит 4 уроков всего.
Чему я научусь в уроке «Безопасные временные файлы и каталоги блокировок»?
Используйте mktemp и flock для создания временных ресурсов без состязаний и предотвращения одновременного запуска скриптов Ты практикуешь DevOps Bootcamp с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать DevOps Bootcamp?
Предыдущий опыт не требуется. DevOps Bootcamp на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Безопасные временные файлы и каталоги блокировок»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке DevOps Bootcamp?
Да. Каждый урок DevOps Bootcamp включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Строгий режим с set -euo pipefail
- Обработчики trap для очистки и сигналов
- Безопасные временные файлы и каталоги блокировок
- Идемпотентные скрипты и логика повторных попыток с задержкой