Как стать автором
Обновить

Shell-скрипт, который удалил базу данных, и история о том, как ShellCheck мог бы помочь это предотвратить

Время на прочтение 6 мин
Количество просмотров 13K
Всего голосов 62: ↑59 и ↓3 +56
Комментарии 52

Комментарии 52

Запускаешь без -eux готовься к неприятностям

Вот что происходит, когда привык к С, а тебя заставляют писать на bash.

Это не C программист писал, а какой-то PHP гавнокодер. Нормальный кодер сразу видит и понимает потенциальные проблемы кода. Про что статья, я писал практически под каждым постом про shell скрипт, какие проблемы могут возникнуть и как от них защититься. Но тут на хабре действительно больше гавнокодеров, чем думающих и практикующих, и меня тупо заминусовали люди, которые не пишут правильно скрипты. Такое ощущение, что люди никогда даже в глаза официальную документацию не видели.

А вы, как я понимаю, полубог, пишущий без ошибок с первого раза?

dwdraugr причем тут полубог? Бессмертными богами считают себя люди, которые не читают документацию, но с пеной у рта пишут, что bash не безопасный язык программирования и при этом не понимают самых простых вещей. Подобные вещи из unpredictable behavior описываются в документации, и на нормальных курсах по программированию. Даже есть специальные задания на курсах по программированию на тренировку подобных кейсов. Такие вещи есть не только в shell скрипте, а во всех языках. Часто при трудоустройстве в солидную компанию, есть задания на понимание их и вас просто не примут, если вы их не знаете! С опытом такие вещи сразу бросаются в глаза, потому что это не какая-то супер сложная ошибка, а самая простая и детская. Конечно в шараж конторе ушатать сервер может вам сойдет с рук, но если убьете кластер в крупной компании, с вас за убытки могут взыскать очень приличную сумму и трудовую вам напишут не приятную запись, с которой вас почти никуда не возьмут… Да и best practice это использовать shellcheсk.
НЛО прилетело и опубликовало эту надпись здесь
0xd34df00d, а причем тут haskell? Это не я вам должен по UB говорить, а вы мне, так как вы тут утверждаете, что знаете haskell.
НЛО прилетело и опубликовало эту надпись здесь
В языках программирования, на которых мне приходилось писать, везде есть UB. Глубоко сомневаюсь, что UB нет в haskell и там нельзя выстрелить себе в ногу.
В случае с bash тут безграмотность на лицо и не знание техники безопасности, обработки исключений, и правильного написания кода.

Простой пример, вы взяли бензопилу спилить дерево, не зная техники безопасности и правил работы с бензопилой приступили к работе, в итоге бензопила отскочила и отрезала вам ногу, или дерево упало вам на голову. Кто виноват Бензопила!??? Дерево!??? Вы сами? Значит ли это, что бензопила плохая и не безопасная? Подходит ли бензопила выкручивать шурупы? А можете ли вы спилить дерево отверткой?
НЛО прилетело и опубликовало эту надпись здесь
Судя по вашему ответу вы мебель в иномарке кабриолете возите… А мешок картошки только складываете в багажник мерседеса… Это называется «Вася не учился в автошколе, но купил себе феррари и разбился...» Про скоростной режим в дождь ничего не слышали? Чем лучше пилить дерево бензопилой или лобзиком?
НЛО прилетело и опубликовало эту надпись здесь
Может и PHP. Что-то выдавало Штирлица: то ли комментарии через //, то ли уверенность, что при ошибке программа не скомпилируется или хотя бы упадёт… то ли волочащийся сзади парашют :D
В Golang комментарии аналогично оформляются
Hes дело не только в комментариях, в php не любят обрабатывать исключения. В нормальных языках это основа из основ, где-то даже не даст скомпилировать пока исключение не обработаешь. На лицо в скрипте отсутствие обработки исключений. Кто программирует на нормальном языке пытается всегда обработать исключения.
в php не любят обрабатывать исключения

Громкое заявление.

Совершали ли вы опасные ошибки при написании shell-скриптов?

Конечно, не совершает ошибок только тот кто ничего не делает. И сервера удаленные убивал и систему на рабочем компе. Правда было это лет 20 назад. Наверное самые распространенные грабли на которые наступал, это были правила файервола на удаленном сервере.

А по-моему, самым безрассудным было даже не игнорирование возможности проверки линтером, а игнорирование проверок вообще - лично я банально поостерёгся бы использовать подобный скрипт, не проверив его на имитации боевого окружения (ну и линтером бы, конечно, не побрезговал)

P.S. не знаю, насколько прославлен rm -rf среди программистов, а среди сисадминов - точно

rm -rf // вообще первое, подозрительное в скрипте :) что бросилось в глаза сразу :)
Получился шикарный мем из скрипта :)
Вывод — тестировать все «в песочнице», даже то, что на первый взгляд абсолютно не предвещает беды. Время и ресурсы на песочницу должны быть всегда — пусть хоть древний десктоп на селероне — как минимум; как максимум — среда полностью аналогичная production-у

Странный кейс описан в статье. Корневая причина заключается в том, что исполнитель просто не знает инструмент, которым решил воспользоваться. Ни одному человеку, который хоть немного писал на шелле, просто в голову не придёт так отмечать комментарии.

Линтер - это, конечно, симптоматичное решение. Но системно этот вопрос решается по другому. Выше правильно говорили про тестирование, но повторюсь - совершенно не ясно, почему не знающий шелла программист (и не важно, пишет он на Си или ПХП) вообще решал эту задачу.

Да даже если и писать коменты, то не понятно зачем на каждую строку.

ну мне кажется это вполне нормальная практика, когда слабо владеешь инструментом и раз в год приходится что-то такое написать при помощи гугла на каждую команду.
Баш скрипты в целом плохо читаются, поэтому для профанов польза явно есть.
Другой вопрос конечно, почему новичок в баше лазает своими ручонками по серверу с БД-шкой…
Кстати, перевел гугл транслейтом:
Временный каталог для файлов резервных копий
Официальный каталог файлов резервных копий
Файл резервной копии будет сохранен во время резервного копирования.
Оператор базы данных
Пароль оператора базы данных
Сохраните последнюю резервную копию 14-летней давности
Формат имен файлов резервных копий
Создать папку
Очистите временный каталог
Создайте эту резервную папку
Выполнить команду резервного копирования
Упакуйте файл резервной копии в официальный каталог
Удалите старый Beizhou 14 дней назад

Ну да, но там DB_USER, DB_PASS вроде вполне лаконично и понятно без комментариев.

НЛО прилетело и опубликовало эту надпись здесь
Из-за опасений подобного подобного я перешёл на концепцию «написать на нормальном языке вместо шелла». Надо по расписанию скопировать чего из фтп, положить в локальную папку и послать письмо — проще наваять на (в зависимости от окружения) чём-то легкопишущемся (сишарп, ява, неважно). Отлаживаемость, поддерживаемость, прдсказуемость, логирование — you name it, намного лучше

Человек с другой стороны:

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

Мой сарказм к тому, что все это лишь инструменты. Да, какой-нибудь новомодный Bosch будет лучше просто молотка. Но бывают моменты, когда нужен именно простой и надежный молоток. А то, что ты можешь им отбить себе палец - уже твои проблемы.
И подобные рассуждения - уровень джуна, который выучил новую фичу и теперь думает что он познал дзен и все знает.

А точно ли он будет запускаться везде? К примеру в линуксе и в маке есть одинаковый набор утилит типа grep, sed и тп но с немного разными возможностями и написать скрипт, одинаково работающий и там, и там - как минимум непросто.

Да, какой-нибудь новомодный Bosch

Начав читать это предложение я уже начал переживать, что новый shell какой-то придумали c интересным названием
Ну так давно уже. Bash — Bosch Amazing SHell.
скорее всего скрипт написали и отладили, а в самом конце перед выкладкой решили причесать и добавить комментов…

Причем добавляли комментарии в редакторе без подсветки синтаксиса. Иначе по цвету символов можно догадаться, что с комментариями что то не так.

В скрипте плохо всё.
— Отсутствие проверок существования каталогов
— Безусловное выполнение rm -rf вместо выполнения с операндом && (cd dir && rm ...) — при отсутствии директории rm выполнится в текущей
— Использование rm с wildcard — можно было бы удалять просто OUT_DIR и создавать её в начале скрипта, как и другие каталоги при их отсутствии
ну а еще сам код, DUMP переменная используется только один раз, зачем она там? если можно было бы просто юзать ее значение, переменные рекомендуют писать в виде ${VAR} а не $VAR, вместо `command` для более удобной читаемости я бы использовал $(), значение переменных желательно в "" оформлять, ну и я думал что // это просто шутка, и хз как это вообще баш по синтаксису схавал

Не догоняю, как скрипт продолжил выполняться? После третьей строки


OUT_DIR=/data/backup/mongod/tmp     // 备份文件临时目录

интерпретатор (zsh) останавливается из-за попытки запуска // с переменной OUT_DIR=/data/backup/mongod/tmp.


test.sh: 3: test.sh: //: Permission denied

В bash:


test.sh: line 3: /: Is a directory

До запуска rm -rf $OUT_DIR/* с пустой переменной $OUT_DIR не доходит.


Всё это попахивает выдуманной историей.


Ссылка на StackOverflow: Deleted database accidentally by a bash script, rescue please

У вас, судя по всему, включен errexit в профиле оболочки, а у китайского разработчика - нет.

Да, всё верно. Я даже не знал, что shell-интерпретатор способен продолжать работать после ненулевого $?.

sergio_nsk человек про потенциальные проблемы написал, ниже вам правильно ответили.
По коду сразу понятно, что человек просто не владеет shell скриптом и программирует на другом языке, так не делают люди, которые пишут хорошо скрипты и майтейнерят крупные сервера, программы, и линукс дистрибутивы. Уже не однократно писал на хабре про обработку исключений в shell скриптах, как обычно в скрипте их, отсюда куча проблем. Почему-то в нормальных языках исключения обрабатывают, а в shell про них просто забывают. В shell немного другая логика чем в привычных языках программирования, некоторые вещи с виду и без опыта делаются не удобно и не стоит шуроповертом пытаться забивать гвозди, у bash только одна основная функция автоматизация выполнения команд, куда еще для удобства входят конвеерное выполнение команд и потоковое редактирование. Когда говорят про безопасность bash это такая же ересь, как к примеру сказать, что на R нельзя написать десктоп приложения и синтаксис его не удобен и поэтому язык плохой, там нет безопасности, и он не создан вообще для сложных приложений. Shell скрипт так же как R заточен строго под свои функции и синтаксис их для этого создан. Вы когда нибудь программировали на R?

Вот примерно как делается правильно:
pushd "${TAR_DIR:?}" || (
  echo -e "\E[1m\E[31mFailed\E[0m"
  exit 1
)

rm -fr tmp
mkdir tmp

popd || (
  echo -e "\E[1m\E[31Failed!\E[0m"
  exit 1
)
НЛО прилетело и опубликовало эту надпись здесь
и что shellcheck должен был тут сказать? Вполне валидный код. А надо ли вам удалять / или нет — это уже не его забота, имхо
НЛО прилетело и опубликовало эту надпись здесь

Но ведь shellcheck не серебряная пуля. При желании его можно обмануть. Вопрос в том, что его задача не ловить целенаправленные зловреды, а ловить ошибки и недостатки, сделанные незлономеренно. И даже в этом случае, он не может гарантировать отлов вообще всех проблем. Но лучше уж с ним, чем без него.

НЛО прилетело и опубликовало эту надпись здесь
А я же всего лишь указал на то, что инструмент — не панацея.
а разве кто-то утверждал что shellcheck это панацея?

в то время, когда детектирование удаления системных директорий как раз таки заложено в его функциональность.
только если оно явное
Это если у вас идет явное удаление, а если имя директории пользователь вводит с консоли? Или оно получается динамически во время работы скрипта. Это уже немного другая область

Это как раз мой случай безбэкапнойзаботной юности. Только у меня была опечатка в имени переменной. Как известно для оболочки это не ошибка


TIPO='/tmp/dir'
echo ${TIPO}
rm -rf ${TYPO}/*

Единственное замечание шелчека — использовать двойные кавычки с переменной.

Странно, у меня вот так получается (Debian 10):

In backup.sh line 3:
rm -rf ${TYPO}/*
^-- SC2115: Use "${var:?}" to ensure this never expands to /* .
^-- SC2153: Possible misspelling: TYPO may not be assigned, but TIPO is.
^-- SC2086: Double quote to prevent globbing and word splitting.

Я была не права, не думала что опции могут повлиять на вывод шелчека. Рука не поднялась поставить -rf, оригинал:


#!/bin/bash
DIR='/tmp/dir'
rm -i "${DIRS}"/*
echo ${DIR}

Спасибо

Обычно tmp не чистят. И вообще, если что-то можно не удалять, не надо удалять, пусть лежит, мало ли. А если лежит, то лежать должно в правильном месте, т.е. в /tmp, которая почистится сама (когда-нибудь).

/tmp чистится при перезагрузке, которая может произойти через годы

Согласна с "обычаями" в готовой программе. Но есть такая вещь как отладка и приходится многократно проверять создание и удалять в ручную или при помощи скрипта.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.