Страницы

Поиск по вопросам

суббота, 30 ноября 2019 г.

Как избежать опечатки при выполнении удаления

#linux #bash


Иногда мне необходимо вручную обновить написанные мной программы на своем сервере.
Процесс обновления выглядит следующим образом:


Копирую выполняемый файл на сервер командой scp.
Захожу на сервер через ssh в терминале. 
Перехожу в папку с исполняемым файлом. 
Останавливаю приложение, если необходимо.
Удаляю старую версию программы.
Копирую новую в нужную директорию.
Запускаю обновленное приложение.


Программу удаляю с ресурсными файлами примерно так:

rm -rf ./


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

Подскажите, что бы вы сделали, чтобы отловить такую или подобные ошибки и не допустить
выполнение нежелательного удаления?
    


Ответы

Ответ 1



Утилита rm из более новых coreutils такого не допустит: $ rm -rf / rm: it is dangerous to operate recursively on '/' rm: use --no-preserve-root to override this failsafe $ rm -rf . rm: refusing to remove '.' or '..' directory: skipping '.' Проверить man rm: --preserve-root do not remove `/' (default) В этом топике более подробно. Может самое время, запланировать обновление. Чтобы максимально снизить вероятность опечатки, не делайте того, что может к ней привести. Никогда не пользуйтесь неявным указанием путей, таких как ./ или ./*, т.е. как минимум выйти из текущего каталога $ cd ... Всегда проверяйте, что же будет удалено, хотя бы так: $ #rm -rf myfolder /TabTab и если только ничего подозрительного нет, как например: bin/ etc/ proc/ usr/ , то HomeDelEnter.

Ответ 2



Думал, что единственно верным ответом на вопрос будет старый добрый «think before you type», и «дважды подумай, если напечатал rm» — благо, обычно после пары выстрелов в ногу вырабатывается рефлекс стойкой сосредоточенности как и после команд dd if=/dev/zero of=/dev/sdX, mkfs, досовского format c: и т.п. Но вот давеча сам поймал себя на том, что уже давно пользуюсь банальной «корзиной» и mv вместо rm: mkdir -p /tmp/trash mv foo/* /tmp/trash #... mv baz/* /tmp/trash Пара замечаний: Способ хорошо подходит для небольших объёмов не критичных к безопасности данных, а также предохраняет от других опечаток или ошибок. Каталог /tmp сам очищается при следующей загрузке или при отключении (если смонтирован в tmpfs)... Cамо собой, его также можно удалять вручную. Не заставляет играть в «ответь “да” на 1000 вопросов» как rm -i. Не полностью предохраняет от ошибки класса «rm . /*» от root'а, но позволяет всё восстановить парой хитрых команд, если осталась root'овая оболочка... Любители скриптов могут обернуть это в функцию в ~/.bashrc: rms() { mkdir -p /tmp/trash mv -t /tmp/trash -- "$@" } Или даже расширить его до чего-то подобного: rms() { local trash dt trash=/tmp/trash mkdir -p "$trash" for f in "$@"; do if [ ! -e "$trash/$(basename "$f")" ]; then mv -t "$trash" -- "$f" || return else dt="$(date --iso=s)" if [ ! -e "$trash/$(basename "$f")-$dt" ]; then mv -- "$f" "$trash/$(basename "$f")-$dt" || return else mv -- "$f" "$(mktemp -u "$trash/$(basename "$f")-$dt-XXXX")" || return fi fi done } Помимо прочего, если файл с заданным именем в корзине уже существует, этот скриптик попробует скопировать его в файл с текущей датой, а также со случайным именем.* Для любителей всё усложнять также есть консольные менеджеры корзины вроде gio trash, trash-cli и другие. * Параноик скажет, что здесь возможно состояние гонки, если будут работать одновременно несколько экземпляров скрипта, но это всего лишь нудный клачёчек, написанный на коленке за 5 минут.

Ответ 3



если уж зашла речь про привычки, то могу порекомендовать отличную привычку: всегда указывать полный путь к файлам/каталогам. минусы: дольше вводить команду (но далеко не всегда — см. ниже). плюсы (благодаря истории команд оболочки): в комплексе с tab-дополнением пути ещё более снижается вероятность описанной в вопросе ошибки команду можно будет легче повторить вне зависимости от текущего каталога (скорректировав при необходимости аргументы) в истории видно, с какими именно файлами/каталогами производились манипуляции (т.к. в истории команд не сохраняется значение каталога, который был текущим на момент выполнения команды, то не всегда очевидно, какие именно файлы/каталоги были затронуты командой).

Ответ 4



Кто про что, а я про функции: remove (){ #Текущий каталог, либо каталог указанный вручную dir=${1:-"$PWD/"} #Запрос подтверждения read -p "Будет удалено `du -sh $dir`, продолжить?: Y/N" -s -n1 rec #Регист ответа res=`sed 's/./\U&/g' <<< $rec` #Проверяем подтверждение if [[ x$res == xY ]]; then #Удаляем rm -rf $dir else echo "Удаление не производилось" fi } Функция записана в bash_profile пользователя. Потом из каталога вызывать remove без ключа - это удалить каталог в котором мы находимся remove /work/all/tt удалить каталог /work/all/tt Это лишь пример, каждый может добавить или изменить под себя.

Ответ 5



Я таки уточнил некоторые детали и теперь могу точно сказать, что имеем классическую ошибку молотка. Соответственно решать надо не как удалять, а как лучше запакетировать, чтобы ничего не сломать. Есть старый, но всё ещё развивающийся проект fpm, который позволяет легко (намного легче, чем писать .spec или шаманить с dh-make) упаковать папку со структурой в deb`rpm` пакет. Я упаковывал готовый бинарник CoreDNS в rpm для CentOS 7, потому что версии. Мой вариант работающий внутри локалки на работе (тавтология, ага): #!/usr/bin/env bash # WORK # LANG=ru_RU.UTF-8 # Собираем ГОТОВЫЙ бинарник coredns в пакет для установки с помощью fpm. Он уже должен быть установлен. # https://github.com/jordansissel/fpm # https://fpm.readthedocs.io/en/latest/installing.html MAIN_SERVER="10.10.10.10" WEB_SERVER="$MAIN_SERVER" WEB_NAME_OF_MODULE_FOR_SOFT="soft" PACKAGE_NAME="coredns" PACKAGE_USER="$PACKAGE_NAME" PACKAGE_GROUP="$PACKAGE_NAME" PACKAGE_VERSION="$(curl http://$WEB_SERVER/$WEB_NAME_OF_MODULE_FOR_SOFT/coredns/latest)" # https://github.com/coredns/coredns/blob/master/LICENSE PACKAGE_LICENSE="Apache License 2.0" PACKAGE_CONFIG_DIR="/etc/coredns" # PACKAGE_HOMEDIR="/var/lib/$PACKAGE_USER" PACKAGE_CONFIG_FILE="Corefile" PACKAGE_ARCHITECTURE="x86_64" PACKAGE_MAINTAINER="don Rumata" # https://github.com/coredns/coredns PACKAGE_DESCRIPTION="CoreDNS is a DNS server/forwarder, written in Go, that chains plugins. Each plugin performs a (DNS) function." PACKAGE_URL="https://github.com/coredns/coredns" PACKAGE_WORKDIR="/tmp/$PACKAGE_NAME" PACKAGE_SCRIPT_BEFORE_INSTALL="$PACKAGE_NAME-before-install.sh" PACKAGE_SCRIPT_AFTER_INSTALL="$PACKAGE_NAME-after-install.sh" PACKAGE_FORMAT="rpm" # Создаём и переходим в папку, где будет твориться магия^W сборка. mkdir -p "$PACKAGE_WORKDIR" && cd "$PACKAGE_WORKDIR" || exit 1 # Для бинарника. mkdir -p ./usr/bin # Для файла настроек. mkdir -p ./"$PACKAGE_CONFIG_DIR" # Для юнита systemd. mkdir -p ./usr/lib/systemd/system # Для временных скриптов, которых пока один. mkdir -p ./"$PACKAGE_WORKDIR" #--- Бинарник ---# cd ./usr/bin wget "http://$WEB_SERVER/$WEB_NAME_OF_MODULE_FOR_SOFT/coredns/coredns_latest_linux_$(arch).tgz" || exit 1 tar -xvf coredns_latest_linux_x86_64.tgz rm coredns_latest_linux_x86_64.tgz cd "$PACKAGE_WORKDIR" #----------------# #--- Создаём скрипт, который будет создавать нам ограниченную системную учётку. ---# cd ./"$PACKAGE_WORKDIR" cat > "$PACKAGE_SCRIPT_BEFORE_INSTALL" << EOF #! /bin/bash adduser --system --shell /sbin/nologin --comment 'CoreDNS user' $PACKAGE_USER # --home-dir $PACKAGE_HOMEDIR groupadd $PACKAGE_GROUP usermod -g $PACKAGE_GROUP $PACKAGE_USER # mkdir $PACKAGE_HOMEDIR # chown $PACKAGE_USER:$PACKAGE_GROUP $PACKAGE_HOMEDIR # chmod 775 $PACKAGE_HOMEDIR EOF chmod +x "$PACKAGE_SCRIPT_BEFORE_INSTALL" cd "$PACKAGE_WORKDIR" #----------------------------------------------------------------------------------# #--- Создаём скрипт, который будет конфигурить сервис сразу после его установки. ---# cd ./"$PACKAGE_WORKDIR" cat > "$PACKAGE_SCRIPT_AFTER_INSTALL" << EOF #!/usr/bin/env bash systemctl enable coredns.service systemctl start coredns.service EOF chmod +x "$PACKAGE_SCRIPT_AFTER_INSTALL" cd "$PACKAGE_WORKDIR" #---------------------------------------------------------------------------------------# #--- Простейший пример конфига, который работает (проверял на 7-м Центе). Да. Начинается с ".:53". ---# cd ./"$PACKAGE_CONFIG_DIR" cat > "$PACKAGE_CONFIG_FILE" << EOF .:53 { forward . 8.8.8.8 1.1.1.1 { } cache { success 5000 denial 2500 } log . {combined} { class denial error } errors cancel } EOF cd "$PACKAGE_WORKDIR" #-----------------------------------------------------------------------------------------------------# #--- SystemD Unit ---# cd ./usr/lib/systemd/system wget --output-document coredns.service "http://$WEB_SERVER/$WEB_NAME_OF_MODULE_FOR_SOFT/coredns/coredns.service" || exit 1 cd "$PACKAGE_WORKDIR" #--------------------# #--- BUILD ---# cd "$PACKAGE_WORKDIR" # Внимание на "./", потому что относительные пути очень важны! # https://www.debian.org/doc/manuals/maint-guide/dother.ru.html#conffiles fpm --force \ --name "$PACKAGE_NAME" \ --version "$PACKAGE_VERSION" \ --license "$PACKAGE_LICENSE" \ --config-files ./"$PACKAGE_CONFIG_DIR"/"$PACKAGE_CONFIG_FILE" \ --architecture "$PACKAGE_ARCHITECTURE" \ --maintainer "$PACKAGE_MAINTAINER" \ --description "$PACKAGE_DESCRIPTION" \ --url "$PACKAGE_URL" \ --before-install ./"$PACKAGE_WORKDIR"/"$PACKAGE_SCRIPT_BEFORE_INSTALL" \ --after-install ./"$PACKAGE_WORKDIR"/"$PACKAGE_SCRIPT_AFTER_INSTALL" \ --no-depends \ --no-auto-depends \ --input-type dir --output-type "$PACKAGE_FORMAT" ./ #-------------# Копия на gist. Дальше можно уже прикручивать aptly и createrepo.

Комментариев нет:

Отправить комментарий