Этот скрипт бэкапа тихо архивирует не те пути — как отладить и исправить?
Скрипт бэкапа должен архивировать три каталога в один тарбол, но итоговый архив содержит не те пути, а скрипт всё равно печатает backup done.
Ограничения: список каталогов фиксирован и одно имя каталога содержит пробел. Объясните, как вы нашли бы причину (трассировка / статический анализ), и дайте фикс.
#!/usr/bin/env bash
# Архивировать список каталогов в один тарбол.
paths="/var/log /srv/My App /etc"
tar -czf backup.tgz $paths
echo "backup done"
Найдите и исправьте ошибку.
Запустите его через bash -x script (или добавьте set -x) — и видно, как tar разворачивается в /srv/My и App как отдельные аргументы: неэкранированный $paths дробится по каждому пробелу. shellcheck помечает это как SC2086. Исправление — хранить пути в массиве и передавать "${paths[@]}", что оставляет каждый путь одним аргументом.
- ✗Считать, что несколько путей безопасно умещаются в одну переменную через пробел
- ✗Не запускать
bash -xилиshellcheck, из-за чего тихий word-split остаётся незамеченным - ✗Оставлять
$pathsбез кавычек, из-за чегоMy Appделится на два аргумента
- →О чём предупреждает код
shellcheckSC2086 и как его правильно заглушить? - →Почему
set -xвскрывает баг word-splitting, который не виден при чтении исходника?
Решение
Скрипт держит несколько путей в одной скалярной переменной через пробел, а затем передаёт её в tar без кавычек. Неэкранированный $paths проходит word-splitting, поэтому /srv/My App распадается на два аргумента — /srv/My и App. Скрипт при этом печатает backup done, потому что tar завершается успешно (он архивирует не те, но существующие пути), а код возврата никто не проверяет.
Отладка:
bash -x ./backup.sh # трассировка: видно tar ... /srv/My App как разные аргументы
shellcheck ./backup.sh # SC2086: Double quote to prevent globbing and word splitting
Исправление — хранить пути в массиве и передавать его в кавычках:
#!/usr/bin/env bash
set -euo pipefail
paths=(/var/log "/srv/My App" /etc)
tar -czf backup.tgz "${paths[@]}"
echo "backup done"
Массив хранит каждый путь как отдельный элемент, а "${paths[@]}" раскрывается ровно в столько аргументов, сколько элементов, сохраняя пробелы. set -euo pipefail делает будущие сбои громкими, а не тихими.