$put_perv_real = "/home/www/dvakompa-ru/dopol/"; ?>
Эти приемы были описаны во внутреннем проекте компании Google «Testing on the Toilet» (Тестируем в туалете — распространение листовок в туалетах, что бы напоминать разработчикам о тестах).
В данной статье они были пересмотрены и дополнены.
Я начинаю каждый скрипт со следующих строк
#!/bin/bash set -o nounset set -o errexit
Это защищает от двух частых ошибок
Если команда может завершиться аварийно, и нас это устраивает, можно использовать следующий код:
if ! ; then echo "failure ignored" fi
Нужно помнить что некоторые команды не возвращают код аварийного завершения например “mkdir -p
” и “rm -f
”.
Так же есть сложности с вызовом цепочек подпрограмм (command1 | command 2 | command3) из скрипта, для обхода этого ограничения можно использовать следующую конструкцию:
(./failing_command && echo A)
В этом случае оператор '&&' не позволит выполниться следующей команде, подробнее — http://fvue.nl/wiki/Bash:_Error_handling
Bash позволяет использовать функции как обычные команды, это очень сильно повышает читаемость вашего кода:
Пример 1:
ExtractBashComments() { egrep "^#" } cat myscript.sh | ExtractBashComments | wccomments=$(ExtractBashComments < myscript.sh)
Пример 2:
SumLines() { # iterating over stdin - similar to awk local sum=0 local line=”” while read line ; do sum=$((${sum} + ${line})) done echo ${sum} } SumLines < data_one_number_per_line.txt
Пример 3:
log() { # classic logger local prefix="[$(date +%Y/%m/%d\ %H:%M:%S)]: " echo "${prefix} $@" >&2 } log "INFO" "a message"
Попробуйте перенести весь ваш код в функции оставив только глобальные переменные/константы и вызов функции «main» в которой будет вся высокоуровневая логика.
Bash позволяет объявлять переменные нескольких типов, самые важные:
## Если DEFAULT_VAL уже объявлена, то использовать ее значение, иначе использовать '-7' readonly DEFAULT_VAL=${DEFAULT_VAL:-7}myfunc() { # Использование локальной переменной со значением глобальной local some_var=${DEFAULT_VAL} ... }
Есть возможность сделать переменную типа readonly из уже объявленной:
x=5 x=6 readonly x x=7 # failure
Стремитесь к тому что бы все ваши переменные были либо local, либо readonly, это улучшит читаемость и снизит количество ошибок.
Используйте $()
вместо обратных кавычек ``
Обратные кавычки плохо читаются и в некоторых шрифтах легко могут быть перепутаны с одинарными кавычками.
Конструкция $() так же позволяет использовать вложенные вызовы без головной боли с экранированием:
# обе команды выводят: A-B-C-D echo "A-`echo B-\`echo C-\\\`echo D\\\`\``" echo "A-$(echo B-$(echo C-$(echo D)))"
Используйте двойные квадратные скобки [[]]
вместо одинарных []
Двойные квадратные скобки позволяют избежать непреднамеренного использования путей вместо переменных:
$ [ a < b ] -bash: b: No such file or directory $ [[ a < b ]]
В некоторых случаях упрощают синтаксис:
[ "${name}" \> "a" -o ${name} \< "m" ] [[ "${name}" > "a" && "${name}" < "m" ]]
А так же предоставляют дополнительную функциональность:
Новые операторы:
Дополненные/измененные операторы:
Примеры:
t="abc123" [[ "$t" == abc* ]] # true (globbing) [[ "$t" == "abc*" ]] # false (literal matching) [[ "$t" =~ [abc]++ ]] # true (regular expression) [[ "$t" =~ "abc*" ]] # false (literal matching)
Начиная с версии bash 3.2 регулярные выражения или выражения с подстановкой не должны заключаться в кавычки, если ваше выражение содержит пробелы, вы можете поместить его в пеерменную:
r="a b+" [[ "a bbb" =~ $r ]] # true
Сравнение строковых переменных с подстановкой так же доступно в операторе case:
case $t in abc*) ;; esac
В bash встроено несколько (недооцененных) возможностей работы со строковыми переменными:
Базовые:
f="path1/path2/file.ext" len="${#f}" # = 20 (длина строковой переменной) # выделение участка из переменной: ${<переменная>:<начало_участка>} или ${<переменная>:<начало_участка>:<размер_участка>} slice1="${f:6}" # = "path2/file.ext" slice2="${f:6:5}" # = "path2" slice3="${f: -8}" # = "file.ext" #(обратите внимание на пробел перед знаком '-') pos=6 len=5 slice4="${f:${pos}:${len}}" # = "path2"
Замена с подстановкой:
f="path1/path2/file.ext" single_subst="${f/path?/x}" # = "x/path2/file.ext" #(замена первого совпадения) global_subst="${f//path?/x}" # = "x/x/file.ext" #(замена всех совпадений)
Разделение переменных:
f="path1/path2/file.ext" readonly DIR_SEP="/" array=(${f//${DIR_SEP}/ }) second_dir="${arrray}" # = path2
Удаление с подстановкой:
# Удаление с начала строки, до первого совпадения f="path1/path2/file.ext" extension="${f#*.}" # = "ext"
Удаление с начала строки, до последнего совпадения
f="path1/path2/file.ext" filename="${f##*/}" # = "file.ext"
Удаление с конца строки, до первого совпадения
f="path1/path2/file.ext" dirname="${f%/*}" # = "path1/path2"
Удаление с конца строки, до последнего совпадения
f="path1/path2/file.ext" root="${f%%/*}" # = "path1"
Некоторые команды ожидают на вход имя файла, с ними нам поможет оператор '<()', он принимает на вход команду и преобразует в нечто что можно использовать как имя файла:
# скачать два URLa и передать их в diff diff <(wget -O - url1) <(wget -O - url2)
Использование маркера для передачи многострочных переменных:
# MARKER — любое слово. command << MARKER ... ${var} $(cmd) ... MARKER
Если нужно избежать подстановки, то маркер можно взять в кавычки:
# конструкция вернет '$var' а не значение переменной var="text" cat << 'MARKER' ... $var ... MARKER
Как правило:
Пример:
for i in "$@"; do echo '$@ param:' $i; donefor i in "$*"; do echo '$! param:' $i; done
вывод:
bash ./parameters.sh arg1 arg2
$@ param: arg1
$@ param: arg2
$! param: arg1 arg2
Проверка синтаксиса (экономит время если скрипт выполняется дольше 15 секунд):
bash -n myscript.sh
Трассировка:
bash -v myscripts.sh
Трассировка с раскрытием сложных команд:
bash -x myscript.sh
Параметры -v и -x можно задать в коде, это может быть полезно если ваш скрипт работает на одной машине а журналирование ведется на другой:
set -o verbose set -o xtrace
Признаки того, что вы не должны использовать shell скрипты:
Если ваш проект соответствует пунктам из этого списка, рассмотрите для него языки языки Python или Ruby.