Intereting Posts
Как расширить раздел на все нераспределенное пространство в VPS? Полный дистрибутив Linux менее 50 МБ для компакт-диска с визитной карточкой Постоянное выполнение команды терминала Уведомление Chrome заполняет мой systray Верните правую клавишу Alt в Ctrl Программное обеспечение для регулировки кривой скорости MIDI-контроллера Получить файлы для данного пакета Вопрос по IPv4 при установке DragonFly latest / 5.2.2 Включить убийство X.org с помощью специальной комбинации клавиш Как вернуть активный пользователь / сеанс на рабочем столе Linux? Параметры списка опций zsh apt Как подключиться к экземпляру CentOS в Openstack с помощью предварительно сгенерированного закрытого ключа PEM с помощью Cygwin Синтаксис командной строки для перенаправления аргументов? Поиск элементов в массиве в bash Как я могу использовать arptables для ограничения количества исходящих запросов ARP в секунду?

Как я могу защитить bash-скрипты от нанесения вреда при их изменении в будущем?

Итак, я удалил свою домашнюю папку (или, точнее, все файлы, к которым у меня был доступ для записи). Что случилось то, что у меня было

build="build" ... rm -rf "${build}/"* ...  

в скрипте bash и, после того как больше не требуется $build , удаляет объявление и все его использования – кроме rm . Bash счастливо расширяется до rm -rf /* . Да.

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

Теперь я задаюсь вопросом: каковы методы написания сценариев bash, чтобы такие ошибки не возникали или, по крайней мере, менее вероятны? Например, если бы я написал

 FileUtils.rm_rf("#{build}/*") 

в сценарии на Ruby интерпретатор жаловался на то, что build не объявлен, поэтому язык защищает меня.

То, что я рассмотрел в bash, помимо corraling rm (который, как упоминают многие ответы в смежных вопросах, не является беспроблемным):

  1. rm -rf "./${build}/"*
    Это убило бы мою текущую работу (repository Git), но ничего больше.
  2. Вариант / параметризация rm который требует взаимодействия при работе вне текущего каталога. (Не удалось найти.) Подобный эффект.

Это так или есть другие способы написания bash-скриптов, которые в этом смысле «надежны»?

 set -u 

или же

 set -o nounset 

Это заставит текущую оболочку рассматривать расширения неустановленных переменных как ошибку:

 $ unset build $ set -u $ rm -rf "$build"/* bash: build: unbound variable 

set -u и set -o nounset – это параметры оболочки POSIX .

Пустое значение не приведет к ошибке.

Для этого используйте

 $ rm -rf "${build:?Error, variable is empty or unset}"/* bash: build: Error, variable is empty or unset 

Расширение ${variable:?word} будет расширяться до значения variable если она не пуста или не установлена. Если оно пустое или не установлено, word будет отображаться при стандартной ошибке, и shell будет рассматривать расширение как ошибку (команда не будет выполнена, и если она выполняется в неинтерактивной оболочке, это завершится). Оставление : out вызовет ошибку только для неустановленного значения, как в set -u .

${variable:?word} является расширением параметра POSIX .

Ни один из них не приведет к завершению интерактивной оболочки, если только не set -e (или set -o errexit ). ${variable:?word} заставляет сценарии завершаться, если переменная пуста или не установлена. set -u приведет к выходу скрипта, если используется вместе с set -e .


Что касается вашего второго вопроса. Нет способа ограничить rm чтобы он не работал за пределами текущего каталога.

Реализация rm GNU имеет --one-file-system которая останавливает его от рекурсивного удаления смонтированных файловых систем, но это настолько близко, насколько я думаю, мы можем получить, не rm вызов rm в функцию, которая фактически проверяет аргументы.


В качестве примечания: ${build} в точности эквивалентен $build если только расширение не происходит как часть строки, где непосредственно следующий символ является допустимым символом в имени переменной, например в "${build}x" .

Я собираюсь предложить нормальные проверки с использованием test / [ ]

Вы были бы в безопасности, если бы написали свой сценарий так:

 build="build" ... [ -n "${build}" ] || exit 1 rm -rf "${build}/"* ... 

[ -n "${build}" ] проверяет, что "${build}" является строкой ненулевой длины .

|| является логическим оператором ИЛИ в bash. Это вызывает выполнение другой команды, если первая не удалась.

Таким образом, ${build} был пустым / неопределенным / и т.д. скрипт завершился бы (с кодом возврата 1, что является общей ошибкой).

Это также защитило бы вас, если бы вы удалили все использования ${build} потому что [ -n "" ] всегда будет ложным.


Преимущество использования test / [ ] состоит в том, что есть много других более значимых проверок, которые он также может использовать.

Например:

 [ -f FILE ] True if FILE exists and is a regular file. [ -d FILE ] True if FILE exists and is a directory. [ -O FILE ] True if FILE exists and is owned by the effective user ID. 

В вашем конкретном случае я переработал «удаление» в прошлом, чтобы вместо этого переместить файлы / каталоги (предполагая, что / tmp находится в том же разделе, что и ваш каталог):

 # mktemp -d is also a good, reliable choice trashdir=/tmp/.trash-$USER/trash-`date` mkdir -p "$trashdir" ... mv "${build}"/* "$trashdir" ... 

За кулисами это перемещает ссылки верхнего уровня на файлы / $trashdir из источника в структуры $trashdir назначения $trashdir на одном и том же разделе и не тратит время на $trashdir структуры каталогов и освобождение дисковых блоков для файлов прямо там и тогда. , Это приводит к гораздо более быстрой очистке, когда система активно используется, в обмен на немного более медленную перезагрузку (/ tmp очищается при перезагрузках).

Кроме того, запись cron для периодической очистки /tmp/.trash-$USER будет препятствовать заполнению / tmp процессов (например, сборок), которые занимают много дискового пространства. Если ваш каталог находится в другом разделе как / tmp, вы можете создать аналогичный / tmp-подобный каталог в вашем разделе и использовать вместо этого cron.

Но самое главное, что если вы облажаете переменные каким-либо образом, вы можете восстановить содержимое до того, как произойдет очистка.

Используйте замену параметра bash, чтобы назначить значения по умолчанию, когда переменная неинициализирована, например:

rm -rf $ {переменная: – “/ несуществующая”}

Я всегда пытаюсь запустить мои скрипты Bash с помощью строки #!/bin/bash -ue .

-e означает «ошибка при первой неуправляемой ошибке»;

-u означает «сбой при первом использовании необъявленной переменной».

Более подробную информацию можно найти в большой статье. Используйте неофициальный строгий режим Bash (если вы не любите отладку) . Также автор рекомендует использовать set -o pipefail; IFS=$'\n\t' set -o pipefail; IFS=$'\n\t' но для моих целей это излишне.

Общий совет, чтобы проверить, установлена ​​ли ваша переменная, является полезным инструментом для предотвращения такого рода проблем. Но в этом случае было более простое решение.

Скорее всего, нет необходимости перемещать содержимое каталога $build , чтобы удалить их, но не сам каталог $build . Таким образом, если вы пропустили постороннее * то неустановленное значение превратится в rm -rf / которое по умолчанию большинство реализаций rm за последнее десятилетие откажутся выполнять (если вы не отключите эту защиту с помощью опции GNU rm --no-preserve-root ) ,

Пропуск завершающего / также приведет к rm '' что приведет к сообщению об ошибке:

 rm: can't remove '': No such file or directory 

Это работает, даже если ваша команда rm не реализует защиту для / .

Вы думаете с точки зрения языка программирования, но bash – это язык сценариев 🙂 Итак, используйте совершенно другую парадигму инструкций для совершенно другой языковой парадигмы.

В этом случае:

 rmdir ${build} 

Так как rmdir откажется удалить непустую директорию, вы должны сначала удалить участников. Вы знаете, что это за члены, верно? Они, вероятно, являются параметрами вашего сценария или получены из параметра, поэтому:

 rm -rf ${build}/${parameter} rmdir ${build} 

Теперь, если вы поместите туда какие-нибудь другие файлы или каталоги, например временный файл или что-то, чего не должно быть, rmdir выдаст ошибку. Обращайтесь с этим правильно, тогда:

 rmdir ${build} || build_dir_not_empty "${build}" 

Такой образ мышления хорошо послужил мне, потому что … да, был там, сделал это.