Глава 31. Широко распространенные ошибки
Ё |
Turandot: Gli enigmi sono tre, la morte
una!
Caleph: No, no! Gli enigmi sono tre, una la
vita!
|
Ё |
Puccini |
Использование зарезервированных слов и служебных символов в
качестве имен переменных.
case=value0 # Может вызвать проблемы.
23skidoo=value1 # Тоже самое.
# Имена переменных, начинающиеся с цифр, зарезервированы командной оболочкой.
# Если имя переменной начинается с символа подчеркивания: _23skidoo=value1, то это не считается ошибкой.
# Однако... если имя переменной состоит из единственного символа подчеркивания, то это ошибка.
_=25
echo $_ # $_ -- это внутренняя переменная.
xyz((!*=value2 # Вызывает серьезные проблемы.
Использование дефиса, и других зарезервированных символов, в
именах переменных.
var-1=23
# Вместо такой записи используйте 'var_1'.
Использование одинаковых имен для переменных и функций. Это
делает сценарий трудным для понимания.
do_something ()
{
echo "Эта функция должна что-нибудь сделать с \"$1\"."
}
do_something=do_something
do_something do_something
# Все это будет работать правильно, но слишком уж запутанно.
Использование лишних пробелов. В отличие от других языков
программирования, Bash весьма привередлив по отношению к
пробелам.
var1 = 23 # Правильный вариант: 'var1=23'.
# В вышеприведенной строке Bash будет трактовать "var1" как имя команды
# с аргументами "=" и "23".
let c = $a - $b # Правильный вариант: 'let c=$a-$b' или 'let "c = $a - $b"'
if [ $a -le 5] # Правильный вариант: if [ $a -le 5 ]
# if [ "$a" -le 5 ] еще лучше.
# [[ $a -le 5 ]] тоже верно.
Ошибочным является предположение о том, что
неинициализированные переменные содержат "ноль". Неинициализированные
переменные содержат "пустое" (null) значение, а
не
ноль.
#!/bin/bash
echo "uninitialized_var = $uninitialized_var"
# uninitialized_var =
Часто программисты путают операторы сравнения = и -eq. Запомните,
оператор = используется для сравнения строковых
переменных, а -eq -- для сравнения целых чисел.
if [ "$a" = 273 ] # Как вы полагаете? $a -- это целое число или строка?
if [ "$a" -eq 273 ] # Если $a -- целое число.
# Иногда, такого рода ошибка никак себя не проявляет.
# Однако...
a=273.0 # Не целое число.
if [ "$a" = 273 ]
then
echo "Равны."
else
echo "Не равны."
fi # Не равны.
# тоже самое и для a=" 273" и a="0273".
# Подобные проблемы возникают при использовании "-eq" со строковыми значениями.
if [ "$a" -eq 273.0 ]
then
echo "a = $a'
fi # Исполнение сценария прерывается по ошибке.
# test.sh: [: 273.0: integer expression expected
Ошибки при сравнении целых чисел
и строковых значений.
Пример 31-1. Строки и числа нельзя сравнивать
напрямую
#!/bin/bash
# bad-op.sh: Попытка строкового сравнения для целых чисел.
echo
number=1
# Следующий цикл "while" порождает две ошибки:
#+ одна обнаруживается сразу, другая не так очевидна.
while [ "$number" < 5 ] # Ошибка! Должно быть: while [ "$number" -lt 5 ]
do
echo -n "$number "
let "number += 1"
done
# При попытке запустить этот сценарий на терминал выводится сообщение:
#+ bad-op.sh: line 10: 5: No such file or directory
# Внутри одиночных квадратных скобок, символ "<" должен экранироваться,
#+ но даже если соблюсти синтаксис, то результат сравнения все равно будет неверным.
echo "---------------------"
while [ "$number" \< 5 ] # 1 2 3 4
do #
echo -n "$number " # Здесь вроде бы нет ошибки, но . . .
let "number += 1" #+ фактически выполняется сравнение строк,
done #+ а не чисел.
echo; echo "---------------------"
# Это может породить определенные проблемы, например:
lesser=5
greater=105
if [ "$greater" \< "$lesser" ]
then
echo "число $greater меньше чем число $lesser"
fi # число 105 меньше чем число 5
# И действительно! Строка "105" меньше чем строка "5"!
#+ (при выполнении сравнения ASCII кодов).
echo
exit 0
Иногда, в операциях проверки, с использованием квадратных
скобок ([ ]), переменные необходимо брать в двойные кавычки.
См. Пример 7-6, Пример
16-4 и Пример 9-6.
Иногда сценарий не в состоянии выполнить команду из-за
нехватки прав доступа. Если пользователь не сможет запустить
команду из командной строки, то эта команда не сможет быть
запущена и из сценария. Попробуйте изменить атрибуты команды,
возможно вам придется установить бит suid.
Использование символа - в
качестве оператора перенаправления (каковым он не является)
может приводить к неожиданным результатам.
command1 2> - | command2 # Попытка передать сообщения об ошибках команде command1 через конвейер...
# ...не будет работать.
command1 2>& - | command2 # Так же бессмысленно.
Спасибо S.C.
Использование функциональных особенностей Bash версии 2 или выше, может привести к аварийному
завершению сценария, работающему под управлением Bash версии
1.XX.
#!/bin/bash
minimum_version=2
# Поскольку Chet Ramey постоянно развивает Bash,
# вам может потребоваться указать другую минимально допустимую версию $minimum_version=2.XX.
E_BAD_VERSION=80
if [ "$BASH_VERSION" \< "$minimum_version" ]
then
echo "Этот сценарий должен исполняться под управлением Bash, версии $minimum или выше."
echo "Настоятельно рекомендуется обновиться."
exit $E_BAD_VERSION
fi
...
Использование специфических особенностей Bash может
приводить к аварийному завершению сценария в Bourne shell
(#!/bin/sh). Как
правило, в дистрибутивах Linux, sh является псевдонимом bash, но это не всегда верно для Unix-систем
в целом.
Использование недокументированных возможностей Bash весьма
небезопасная практика. Предыдущие версии этой книги включали в
себя ряд сценариев , которые использовали такие "возможности", например -- возможность
возвращать через exit или return большие (по абсолютному значению)
отрицательные целые числа. К сожалению,
в версии 2.05b и более поздних, эта "лазейка" была
закрыта. См. Пример 22-8.
Сценарий, в котором строки отделяются друг от друга в стиле
MS-DOS (\r\n), будет
завершаться аварийно, поскольку комбинация #!/bin/bash\r\n считается
недопустимой. Исправить эту ошибку можно простым удалением
символа \r из сценария.
#!/bin/bash
echo "Начало"
unix2dos $0 # Сценарий переводит символы перевода строки в формат DOS.
chmod 755 $0 # Восстановление прав на запуск.
# Команда 'unix2dos' удалит право на запуск из атрибутов файла.
./$0 # Попытка запустить себя самого.
# Но это не сработает из-за того, что теперь строки отделяются
# друг от друга в стиле DOS.
echo "Конец"
exit 0
Сценарий, начинающийся с #!/bin/sh, не может работать
в режиме полной совместимости с Bash. Некоторые из
специфических функций, присущих Bash, могут оказаться
запрещенными к использованию. Сценарий, который требует полного
доступа ко всем расширениям, имеющимся в Bash, должен
начинаться строкой #!/bin/bash.
"Лишние" пробелы перед
строкой-ограничителем, завершающей встроенный документ, будут приводить к
ошибкам в работе сценария.
Сценарий не может экспортировать переменные родительскому процессу - оболочке. Здесь как в
природе, потомок может унаследовать черты родителя, но не
наооборот.
WHATEVER=/home/bozo
export WHATEVER
exit 0
bash$ echo $WHATEVER
bash$
Будьте уверены -- при выходе в командную строку переменная
$WHATEVER останется неинициализированной.
Использование в подоболочке переменных с теми же именами,
что и в родительской оболочке может не давать ожидаемого
результата.
Пример 31-2. Западня в подоболочке
#!/bin/bash
# Западня в подоболочке.
outer_variable=внешняя_переменная
echo
echo "outer_variable = $outer_variable"
echo
(
# Запуск в подоболочке
echo "внутри подоболочки outer_variable = $outer_variable"
inner_variable=внутренняя_переменная # Инициализировать
echo "внутри подоболочки inner_variable = $inner_variable"
outer_variable=внутренняя_переменная # Как думаете? Изменит внешнюю переменную?
echo "внутри подоболочки outer_variable = $outer_variable"
# Выход из подоболочки
)
echo
echo "за пределами подоболочки inner_variable = $inner_variable" # Ничего не выводится.
echo "за пределами подоболочки outer_variable = $outer_variable" # внешняя_переменная.
echo
exit 0
Передача вывода от echo по
конвейеру команде read может давать неожиданные результаты. В этом
сценарии, команда read
действует так, как будто бы она была запущена в подоболочке.
Вместо нее лучше использовать команду set
(см. Пример 11-15).
Пример 31-3. Передача вывода от команды echo
команде read, по конвейеру
#!/bin/bash
# badread.sh:
# Попытка использования 'echo' и 'read'
#+ для записи значений в переменные.
a=aaa
b=bbb
c=ccc
echo "один два три" | read a b c
# Попытка записать значения в переменные a, b и c.
echo
echo "a = $a" # a = aaa
echo "b = $b" # b = bbb
echo "c = $c" # c = ccc
# Присваивания не произошло.
# ------------------------------
# Альтернативный вариант.
var=`echo "один два три"`
set -- $var
a=$1; b=$2; c=$3
echo "-------"
echo "a = $a" # a = один
echo "b = $b" # b = два
echo "c = $c" # c = три
# На этот раз все в порядке.
# ------------------------------
# Обратите внимание: в подоболочке 'read', для первого варианта, переменные присваиваются нормально.
# Но только в подоболочке.
a=aaa # Все сначала.
b=bbb
c=ccc
echo; echo
echo "один два три" | ( read a b c;
echo "Внутри подоболочки: "; echo "a = $a"; echo "b = $b"; echo "c = $c" )
# a = один
# b = два
# c = три
echo "-------"
echo "Снаружи: "
echo "a = $a" # a = aaa
echo "b = $b" # b = bbb
echo "c = $c" # c = ccc
echo
exit 0
Фактически, как указывает Anthony Richardson, передача
вывода по конвейеру в любой цикл, может порождать аналогичные
проблемы.
# Проблемы с передачей данных в цикл по конвейеру.
# Этот пример любезно предоставил Anthony Richardson.
foundone=false
find $HOME -type f -atime +30 -size 100k |
while true
do
read f
echo "Файл $f имеет размер более 100KB и не использовался более 30 дней"
echo "Подумайте о перемещении этого файла в архив."
foundone=true
done
# Переменная foundone всегда будет иметь значение false, поскольку
#+ она устанавливается в пределах подоболочки
if [ $foundone = false ]
then
echo "Не найдено файлов, которые требуют архивации."
fi
# =====================А теперь правильный вариант:=================
foundone=false
for f in $(find $HOME -type f -atime +30 -size 100k) # Здесь нет конвейера.
do
echo "Файл $f имеет размер более 100KB и не использовался более 30 дней"
echo "Подумайте о перемещении этого файла в архив."
foundone=true
done
if [ $foundone = false ]
then
echo "Не найдено файлов, которые требуют архивации."
fi
Подобные же проблемы возникают при попытке записать вывод от
tail -f в конвейере с grep.
tail -f /var/log/messages | grep "$ERROR_MSG" >> error.log
# Ни одна запись не попадет в файл "error.log".
--
Огромный риск, для безопасности системы, представляет
использование в скриптах команд, с установленным битом
"suid".
Использование сценариев в качестве CGI-приложений может
приводить к серьезным проблемам из-за отсутствия контроля типов
переменных. Более того, они легко могут быть заменены
взломщиком на его собственные сценарии.
Bash не совсем корректно обрабатывает строки, содержащие
двойной слэш (//).
Сценарии на языке Bash, созданные для Linux или BSD систем,
могут потребовать доработки, перед тем как они смогут быть
запущены в коммерческой версии Unix. Такие сценарии, как
правило, используют GNU-версии команд и утилит, которые имеют
лучшую функциональность, нежели их аналоги в Unix. Это особенно
справедливо для таких утилит обработки текста, как tr.
|
Danger is near thee --
Beware, beware, beware, beware.
Many brave hearts are asleep in the deep.
So beware --
Beware.
|
|
A.J. Lamb
and H.W. Petrie |
Назад | Вперед
Содержание (общее) | Содержание раздела
Если Вы не нашли что искали, то рекомендую воспользоваться поиском по сайту:
|