29 декабря 2008 в 16:57

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

Окончание перевода Bash Pitfalls. Предыдущие части доступны в блоге «Оболочки» (часть 1, часть 2) и в моём блоге.

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=$?
Перевод: Greg Wooledge
Владимир Бредников @bappoy
карма
65,7
рейтинг 0,0
Самое читаемое Администрирование

Комментарии (7)

  • 0
    Очевидное решение здесь не работает:
    $ echo «hi\!»
    hi\!


    а в zsh работает ;)
    $ echo «hello\!»
    hello!

    зы: цикл статей познавательный, так что спасибо за проделанную работу
    • +7
      Возможно это потому, что статья называется «Частые ошибки программирования на Bash»?
    • 0
      echo «hi»'!'
    • +2
      Работает еще более очевидное решение
      echo hi!
      • +3
        Во-во! А то можно было написать, не работает очевидное решение:
        echo `(dd if=/dev/random bs=16384 count=16384000| grep Hi;echo !)`

        Типа того :))))
        Соврал я конечно, но идея, думаю, ясна :)
        • 0
          :)
          Если верить терверу, то рано или поздно
          dd if=/dev/random bs=16384 count=16384000| grep Война и Мир
          :)
      • +1
        Ваш способ будет работать идеально, в случае, когда вам действительно нужно вывести статичное «hi!». Здесь же автор простым примером предостерегает пользователей от проблем при выводе, например, имён файлов, в которых может содержаться восклицательный знак (о котором заранее, естественно, не известно).

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