Безопасная функция rm -rf в сценарии оболочки - PullRequest
5 голосов
/ 14 июня 2009

Этот вопрос похож на Какой самый безопасный способ очистки каталога в * nix?

Я пишу скрипт bash, который определяет несколько констант пути и будет использовать их для манипулирования файлами и каталогами (копирование, переименование и удаление). Часто нужно будет сделать что-то вроде:

rm -rf "/${PATH1}"
rm -rf "${PATH2}/"*

При разработке этого скрипта я хотел бы защитить себя от опечаток, таких как PATH1 и PATH2, и избежать ситуаций, когда они раскрываются в пустую строку, что приводит к очистке всего диска. Я решил создать специальную обертку:

rmrf() {
    if [[ $1 =~ "regex" ]]; then
        echo "Ignoring possibly unsafe path ${1}"
        exit 1
    fi

    shopt -s dotglob
    rm -rf -- $1
    shopt -u dotglob
}

Который будет называться:

rmrf "/${PATH1}"
rmrf "${PATH2}/"*

Regex (или выражение sed) должно перехватывать пути типа "*", "/ *", "/ ** /", "/// *" и т. Д., Но разрешать пути типа "dir", "/ dir", "/ dir1 / dir2 /", "/ dir1 / dir2 / *". Также я не знаю, как включить глобирование оболочки в случае, как "/ dir с пробелом * *". Есть идеи?

РЕДАКТИРОВАТЬ: это то, что я придумал до сих пор:

rmrf() {
    local RES
    local RMPATH="${1}"
    SAFE=$(echo "${RMPATH}" | sed -r 's:^((\.?\*+/+)+.*|(/+\.?\*+)+.*|[\.\*/]+|.*/\.\*+)$::g')
    if [ -z "${SAFE}" ]; then
        echo "ERROR! Unsafe deletion of ${RMPATH}"
        return 1
    fi

    shopt -s dotglob
    if [ '*' == "${RMPATH: -1}" ]; then
        echo rm -rf -- "${RMPATH/%\*/}"*
        RES=$?
    else
        echo rm -rf -- "${RMPATH}"
        RES=$?
    fi
    shopt -u dotglob

    return $RES
}

Использование по назначению (обратите внимание на звездочку внутри цитаты):

rmrf "${SOMEPATH}"
rmrf "${SOMEPATH}/*"

, где $ SOMEPATH не является системным или / home каталогом (в моем случае все такие операции выполняются в файловой системе, смонтированной в каталоге / scratch).

ПРЕДОСТЕРЕЖЕНИЯ:

  • не проверено очень хорошо
  • не предназначен для использования с путями, возможно содержащими '..' или '.'
  • не следует использовать с указанными пользователем путями
  • rm -rf со звездочкой, вероятно, может завершиться ошибкой, если в $ SOMEPATH слишком много файлов или каталогов (из-за ограниченной длины командной строки) - это можно исправить с помощью цикла for или find

Ответы [ 10 ]

7 голосов
/ 14 июня 2009

С rm в bash я обнаружил большую опасность в том, что bash обычно не останавливается на ошибках. Это означает, что:

cd $SOMEPATH
rm -rf *

Это очень опасная комбинация, если не удается изменить каталог. Более безопасный способ был бы:

cd $SOMEPATH && rm -rf *

Что гарантирует, что rf не запустится, если вы не находитесь в $ SOMEPATH. Это не защитит вас от плохого $ SOMEPATH, но может быть объединено с советом, данным другими, чтобы сделать ваш сценарий более безопасным.

РЕДАКТИРОВАТЬ: @placeybordeaux отмечает, что если $ SOMEPATH не определен или пуст cd не рассматривает это как ошибку и возвращает 0. В свете этого этот ответ следует считать небезопасным, если только $ SOMEPATH не проверен как существующий и непустой первый. Я считаю, что cd без аргументов должно быть недопустимой командой, так как в лучшем случае она выполняет no-op, а в худшем случае это может привести к неожиданному поведению, но это так.

4 голосов
/ 23 июля 2009

Существует директива bash set -u, которая вызывает выход при использовании неинициализированной переменной. Я читал об этом здесь , с rm -rf в качестве примера. Я думаю, это то, что вы ищете. А вот руководство пользователя .

2 голосов
/ 14 июня 2009

Обычно, когда я разрабатываю команду с такими операциями, как 'rm -fr', я нейтрализую удаление во время разработки. Один из способов сделать это:

RMRF="echo rm -rf"
...
$RMRF "/${PATH1}"

Это показывает мне, что должно быть удалено, но не удаляет его. Я сделаю ручную очистку, пока все в разработке - это небольшая цена за то, что не рискуешь все испортить.

Обозначение '"/${PATH1}"' немного необычно; обычно вы должны убедиться, что PATH1 просто содержит абсолютный путь.

Использование метасимвола с "${PATH2}/"* неразумно и не нужно. Единственная разница между использованием этого и использованием только '"${PATH2}"' заключается в том, что если каталог, указанный в PATH2, содержит какие-либо файлы или каталоги с именами, начинающимися с точки, то эти файлы или каталоги не будут удалены. Такой дизайн маловероятен и довольно хрупок. Было бы намного проще просто передать PATH2 и позволить рекурсивному удалению выполнить свою работу. Добавление косой черты не обязательно является плохой идеей; система должна убедиться, что $PATH2 содержит имя каталога, а не просто имя файла, но дополнительная защита является минимальной.

Использование сглаживания с 'rm -fr' обычно является плохой идеей. Вы хотите быть точным, ограничительным и ограничивающим в том, что он делает - для предотвращения несчастных случаев. Конечно, вы никогда не запускаете команду (сценарий оболочки, которую вы разрабатываете) как root, пока она находится в стадии разработки - это самоубийство. Или, если права суперпользователя абсолютно необходимы, вы удаляете операцию удаления, пока не убедитесь, что она пуленепробиваема.

2 голосов
/ 14 июня 2009

Я бы рекомендовал использовать realpath (1), а не аргумент команды напрямую, чтобы вы могли избегать таких вещей, как /A/B/../ или символических ссылок.

2 голосов
/ 14 июня 2009

Я думаю, что в команде "rm" есть параметр, чтобы избежать удаления "/". Проверьте это.

1 голос
/ 28 октября 2009

Вам не нужно использовать регулярные выражения.
Просто назначьте каталоги, которые вы хотите защитить, переменной, а затем выполните итерацию по переменной. например:

protected_dirs="/ /bin /usr/bin /home $HOME"
for d in $protected_dirs; do
    if [ "$1" = "$d" ]; then
        rm=0
        break;
    fi
done
if [ ${rm:-1} -eq 1 ]; then
    rm -rf $1
fi
1 голос
/ 15 июня 2009

Вы можете использовать

set -f    # cf. help set 

для отключения генерации имени файла (*).

1 голос
/ 15 июня 2009

Если это возможно, вы должны попытаться поместить все в папку с жестко запрограммированным именем, которое вряд ли можно найти где-либо еще в файловой системе, например «foofolder». Тогда вы можете написать свою rmrf() функцию как:

rmrf() {
    rm -rf "foofolder/$PATH1"
    # or
    rm -rf "$PATH1/foofolder"
}

Эта функция никак не может удалить что-либо, кроме файлов, которые вы хотите.

1 голос
/ 14 июня 2009

Тем временем я нашел этот проект perl: http://code.google.com/p/safe-rm/

0 голосов
/ 11 марта 2018

Добавьте следующие коды к вашему ~/.bashrc

# safe delete
move_to_trash () { now="$(date +%Y%m%d_%H%M%S)"; mv "$@" ~/.local/share/Trash/files/"$@_$now"; }
alias del='move_to_trash'

# safe rm
alias rmi='rm -i'

Каждый раз, когда вам нужно rm что-то, сначала подумайте del, вы можете изменить папку корзины Если вам нужно что-то rm, вы можете перейти в папку «Корзина» и использовать rmi.

Одна небольшая ошибка для del состоит в том, что когда del папка, например, my_folder, это должна быть del my_folder, но не del my_folder/, так как для возможного более позднего восстановления я прикрепляю информацию о времени в конце ("$@_$now"). Для файлов работает нормально.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...