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

Возможность тестирования с помощью кода ниже, используя Perl, чтобы избежать буферизации, попробуйте, работает ли это для вас

Пример версии P

$ cat /tmp/P
#!/bin/bash
read input
if [[ $input = "Hello" ]]
then
    echo "Why?"
else
    exit 1
fi
echo "Got Hello from client, working ..."
sleep 10
echo "Need to read some input"
read x
echo "Got: $x"

Программа-оболочка

$ cat /tmp/wrapper 
#!/usr/bin/zsh
coproc /tmp/P
print -p Hello  # Send Hello to P
read -pr line   # Read what P has to say
if [[ "$line" = "Why?" ]]; then
    perl -e '$|=1;print $_ while(<>);' <& p &
    perl -e '$|=1;print $_ while(<>);' >& p
else
    echo "Could not get P's attention."
fi

Тестовый запуск

 $ / tmp / wrapper {{1 }} Получил привет от клиента, работает ... 
Необходимо прочитать вводные данные 
Привет, P! <== Набрано на теминале 
Получил: привет, П! 
 
46
11.07.2018, 17:22
7 ответов
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будет отображаться при стандартной ошибке, и оболочка будет рассматривать расширение как ошибку (, команда не будет выполнена, а при запуске в не -интерактивной оболочке это будет прекратить ). Отсутствие :вызовет ошибку только для неустановленного значения, как и в set -u.

${variable:?word}— это расширение параметра POSIX .

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


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

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


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

77
27.01.2020, 19:34

Я собираюсь предложить обычные проверки с использованием 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.
12
27.01.2020, 19:34

Вы мыслите в терминах языка программирования, но bash — это язык сценариев :-)Итак, используйте совершенно другую парадигму инструкций для совершенно другого языка парадигма.

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

rmdir ${build}

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

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

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

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

Этот способ мышления сослужил мне хорошую службу, потому что... да, я был там, делал это.

-3
27.01.2020, 19:34

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

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

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

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

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

4
27.01.2020, 19:34

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

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

2
27.01.2020, 19:34

Я всегда стараюсь начинать свои сценарии Bash со строки #!/bin/bash -ue.

-eозначает «сбой при первой непосещенной e ошибке»;

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

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

1
27.01.2020, 19:34

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

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

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

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

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

1
27.01.2020, 19:34

Теги

Похожие вопросы