[ Content | View menu ]

Частые ошибки программирования на Bash (часть пятая, она же последняя)

Опубликовано 29.12.2008

Окончание перевода Bash Pitfalls. Все остальные части доступны здесь.

22. echo "Hello World!"

Проблема в том, что в интерактивной оболочке Bash эта команда вызовет ошибку:

bash: !": event not found

Это происходит потому, что при установках по умолчанию Bash выполняет подстановку истории команд в стиле csh с использованием восклицательного знака. В скриптах такой проблемы нет, только в интерактивной оболочке.

Очевидное решение здесь не работает:

$ echo "hi\!"
hi\!


Можно заключить эту строку в одинарные кавычки:

echo 'Hello World!'

Но самое подходящее решение здесь — временно выключить параметр histexpand. Это можно сделать командой set +H или set +o histexpand:

set +H
echo "Hello World!"

Почему же тогда всегда не пользоваться одиночными кавычками? Представьте, что вы хотите получить информацию об mp3-файлах:

mp3info -t "Don't Let It Show" ...
mp3info -t "Ah! Leah!" ...

Одинарные кавычки здесь не подходят, поскольку названия песен содержат апострофы в названиях, а использование двойных кавычек приведёт к проблеме с подстановкой истории команд (а если бы в именах файлов ещё и двойные ковычки содержались, получилось бы вообще черте что). Поскольку лично я (Greg Wooledge, автор текста) никогда не использую подстановку истории команд, я просто поместил команду set +H в свой .bashrc. Но это вопрос привычки и каждый решает для себя сам.

23. for arg in $*

В Bash’е, так же, как и в других оболочках семейства Bourne shell, есть специальный синтаксис для работы с позиционными параметрами по очереди, но $* и $@ не совсем то, что вам нужно: после подстановки параметров они становятся списком слов, переданных в аргументах, а не списком параметров по отдельности.

Вот правильный синтаксис:

for arg in "$@"

Или просто:

for arg

for arg соответствует for arg in "$@". Заключенная в двойные кавычки переменная "$@" — это специальная уличная магия, благодаря которой каждый аргумент командной строки заключается в двойные кавычки, так что он выглядит как отдельное слово. Другими словами, "$@" преобразуется в список "$1" "$2" "$3" и т.д. Этот трюк подойдёт в большинстве случаев.

Рассмотрим пример:

#!/bin/bash
# неправильно
for x in $*; do
echo "parameter: '$x'"
done

Этот код напечатает:

$ ./myscript 'arg 1' arg2 arg3
parameter: 'arg'
parameter: '1'
parameter: 'arg2'
parameter: 'arg3'

Вот как это должно выглядеть:

#!/bin/bash
# правильно!
for x in "$@"; do
    echo "parameter: '$x'"
done
$ ./myscript 'arg 1' arg2 arg3
parameter: 'arg 1'
parameter: 'arg2'
parameter: 'arg3'

24. function foo()

В некоторых шеллах это работает, но не во всех. Никогда не комбинируйте ключевое слово function со скобками (), определяя функцию.

Некоторые версии bash позволяют одновременно использовать и function, и (), но ни в одной другой оболочке так делать нельзя. Некоторые интерпретаторы, правда, воспримут function foo, но для максимальной совместимости лучше использовать:

foo() {
 ...
}

25. echo "~"

Замена тильды (tilde expansion) происходит только когда символ ~ не окружён кавычками. В этом примере echo выведет ~ в stdout, вместо того, чтобы вывести пользовательский домашний каталог.

Экранирование переменных с путями, которые должны быть выражены относительно домашнего каталога, должно производиться с использованием $HOME вместо ~.

"~/dir with spaces"       #  "~/dir with spaces"
~"/dir with spaces"       # "~/dir with spaces"
~/"dir with spaces"       # "/home/my photos/dir with spaces"
"$HOME/dir with spaces"   # "/home/my photos/dir with spaces"

26. local varname=$(command)

Определяя локальную переменную в функции, local сама работает как команда. Иногда это может непонятным образом взаимодействовать с остатком строки. Например, если в следующей команде вы хотите получить код возврата ($?) подставленной команды, вы его не получите: код возврата команды local его перекрывает.

Поэтому эти команды лучше разделять:

local varname
varname=$(command)
rc=$?
«
»

10 комментариев

Write a comment - TrackBack - RSS Comments

  1. Comment by Даник:

    ~$ echo «Hello World!»
    echo «Hello World»
    Hello World
    ~$

    ~$ bash –version
    GNU bash, version 3.2.48(1)-release (x86_64-pc-linux-gnu)
    Copyright (C) 2007 Free Software Foundation, Inc.
    ~$

    действительно подстановка идёт, но не совсем понял КАК!

    24.08.2009 @ 02:58
  2. Comment by Даник:

    ~$ echo hello world!
    hello world!
    ~$

    24.08.2009 @ 03:04
  3. Comment by flavi:

    с большим удовольствие читал ваши статьи, может быть вы еще одну ошибку разъясните? Например я пишу
    $ /bin/bash –noprofile –norc –posix —
    и это работает. Но когда я пытаюсь запустить скрипт, который начинается со строчки
    #!/bin/bash –noprofile –norc –posix —
    происходит ошибка… Пишет только: «/bin/bash: –noprofile –norc –posix –: неправильная опция» и выводит хелп… без завершающего — то же самое. Более того, если оставить только одну любую опцию – это работает, но уже для любых(не только перечисленных) двух опций происходит та же самая ошибка…

    25.12.2009 @ 04:38
  4. Comment by bappoy:

    Опции --noprofile и --norc имеют смысл только в том случае, если bash вызывается как интерактивная оболочка. А если он вызывается как интерпретатор, то файлы bash.profile и .bashrc не читаются и эти опции бессмысленны.

    25.12.2009 @ 15:08
  5. Comment by flavi:

    Действительно, спасибо, тут я не подумал… Но все равно если писать
    #!/bin/bash –verbose –posix
    то это тоже не работает. При этом если оставить только одну из опций, то работает нормально…

    25.12.2009 @ 22:07
  6. Comment by bappoy:

    Стандартом POSIX на шелл (http://www.opengroup.org/onlinepubs/009695399/index.html) допускаются только короткие опции (-v), поэтому и ругается на --verbose.

    26.12.2009 @ 13:38
  7. Comment by flavi:

    нет, с короткими опциями то же самое, кроме того, одна длинная опция воспринимается им нормально, как, впрочем, и короткая… Вообще, в линукс вроде как совместимость с посикс какая то чисто формальная т.е. она есть но не используется… везде много чего дополнительного добавляют и предоставляют режим совместимости с посикс, но поведением по умолчанию он не является. Но ладно, раз уж вы уже гуглите, то я лучше сам поищу ответ. Все равно английский надо когда-нибудь изучать…

    26.12.2009 @ 14:42
  8. Comment by bappoy:

    я не гуглю, у меня ссылка на стандарты POSIX всегда под рукой :)

    Попробую объяснить еще раз. Опция --posix влючает режим совместимости со стандартом, и все остальные опции (кроме этой) должны соответствовать, поэтому он ругается на --verbose. Если --posix не задано, то ругаться не на что и все опции, описанные в мане, воспринимаются нормально. По умолчанию режим POSIX выключен, т.к. он слишком сильно ограничивает функциональность. А кто хочет совместимости (чтобы скрипт работал на разных системах, в т.ч. в FreeBSD, Solaris, HPUX и т.д. со своими реализациями sh, а не GNU), тот полагается только на функциональность, определённую в стандарте.

    26.12.2009 @ 15:42
  9. Comment by flavi:

    да я же не дурак, я об этом подумал, но на деле это не так, в консоли:
    /bin/bash –posix –verbose –norc # в консоли нормально работает…
    /bin/bash –verbose -r # тоже работает
    в файле, без флага посикс:
    #!/bin/bash –verbose -r
    выводит вышеописанную ошибку, причем выводит ее, как я уже написал при любых опциях больше одной в том числе коротких. Не верите, попробуйте сами:

    echo \#\!/bin/bash –verbole -r > test
    chmod u+x test
    ./test
    rm test

    я проверял на двух машинах с дебиан и дженту.

    27.12.2009 @ 02:16
  10. Comment by bappoy:

    Опубликовал развёрнутый ответ отдельным постом. Спасибо за наводку на интересную тему :-)

    28.12.2009 @ 00:31
Write comment

Я не робот.