[ Content | View menu ]

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

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

Продолжаю перевод Bash Pitfalls. С предыдущими частями можно ознакомиться здесь.

11. cat file | sed s/foo/bar/ > file

Нельзя читать из файла и писать в него в одном и том же конвейере. В зависимости от того, как построен конвейер, файл может обнулиться (или оказаться усечённым до размера, равному объёму буфера, выделяемого операционной системой для конвейера), или неограниченно увеличиваться до тех пор, пока он не займёт всё доступное пространство на диске, или не достигнет ограничения на размер файла, заданного операционной системой или квотой, и т.д.

Если вы хотите произвести изменение в файле, отличное от добавления данных в его конец, вы должны в какой-то промежуточный момент создать временный файл. Например (этот код работает во всех шеллах):

sed 's/foo/bar/g' file > tmpfile && mv tmpfile file

Следующий фрагмент будет работать только при использовании GNU sed 4.x и выше:

sed -i 's/foo/bar/g' file

Обратите внимание, что при этом тоже создаётся временный файл и затем происходит переименование — просто это делается незаметно.

В *BSD-версии sed необходимо обязательно указывать расширение, добавляемое к запасной копии файла. Если вы уверены в своем скрипте, можно указать нулевое расширение:

sed -i '' 's/foo/bar/g' file

Также можно воспользоваться perl 5.x, который, возможно, встречается чаще, чем sed 4.x:

perl -pi -e 's/foo/bar/g' file

Различные аспекты задачи массовой замены строк в куче файлов обсуждаются в Bash FAQ #21.

12. echo $foo

Эта относительно невинно выглядящая команда может привести к неприятным последствиям. Поскольку переменная $foo не заключена в кавычки, она будет не только разделена на слова, но и возможно содержащийся в ней шаблон будет преобразован в имена совпадающих с ним файлов. Из-за этого bash-программисты иногда ошибочно думают, что их переменные содержат неверные значения, тогда как с переменными всё в порядке — это команда echo отображает их согласно логике bash, что приводит к недоразумениям.

MSG="Please enter a file name of the form *.zip"
echo $MSG

Это сообщение разбивается на слова и все шаблоны, такие, как *.zip, раскрываются. Что подумают пользователи вашего скрипта, когда увидят фразу:

Please enter a file name of the form freenfss.zip lw35nfss.zip

Вот ещё пример:

VAR=*.zip       # VAR содержит звёздочку, точку и слово "zip"
echo "$VAR"     # выведет *.zip
echo $VAR       # выведет список файлов, чьи имена заканчиваются на .zip

На самом деле, команда echo вообще не может быть использована абсолютно безопасно. Если переменная содержит только два символа «-n», команда echo будет рассматривать их как опцию, а не как данные, которые нужно вывести на печать, и абсолютно ничего не выведет. Единственный надёжный способ напечатать значение переменной — воспользоваться командой printf:
printf "%s\n" "$foo"

13. $foo=bar

Нет, вы не можете создать переменную, поставив «$» в начале её названия. Это не Perl. Достаточно написать:

foo=bar

14. foo = bar

Нет, нельзя оставлять пробелы вокруг «=», присваивая значение переменной. Это не C. Когда вы пишете foo = bar, оболочка разбивает это на три слова, первое из которых, foo, воспринимается как название команды, а оставшиеся два — как её аргументы.

По этой же причине нижеследующие выражения также неправильны:

foo= bar    # НЕПРАВИЛЬНО!
foo =bar    # НЕПРАВИЛЬНО!
$foo = bar  # АБСОЛЮТНО НЕПРАВИЛЬНО!
foo=bar     # Правильно.

15. echo <<EOF

Встроенные документы полезны для внедрения больших блоков текстовых данных в скрипт. Когда интерпретатор встречает подобную конструкцию, он направляет строки вплоть до указанного маркера (в данном случае — EOF) на входной поток команды. К сожалению, echo не принимает данные с STDIN.

# Неправильно:
echo <<EOF
Hello world
EOF
# Правильно:
cat <<EOF
Hello world
EOF

16. su -c ’some command’

В Linux этот синтаксис корректен и не вызовет ошибки. Проблема в том, что в некоторых системах (например, FreeBSD или Solaris) аргумент -c команды su имеет совершенно другое назначение. В частности, в FreeBSD ключ -c указывает класс, ограничения которого применяются при выполнении команды, а аргументы шелла должны указываться после имени целевого пользователя. Если имя пользователя отсутствует, опция -c будет относиться к команде su, а не к новому шеллу. Поэтому рекомендуется всегда указывать имя целевого пользователя, вне зависимости от системы (кто знает, на каких платформах будут выполняться ваши скрипты…):

su root -c 'some command' # Правильно.

продолжение следует…

«
»

3 комментария

Write a comment - TrackBack - RSS Comments

  1. Comment by AJAX:

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

    29.12.2008 @ 20:51
  2. Comment by Hubbitus:

    Для 11 совета:

    Во-первых, в sed была опция -i, но в новых версиях от ее снова отказались, так что это устаревшая информация, не универсальная, и более того, не в стиле UNIX-WAY (полагаю поэтому и отказались, но это мое личное предположение).

    Вариант с временным файлом универсален, но утомителен. собственно в moreutils специально для эттого есть команда sponge, например:
    cat file | sed ’s/foo/bar/’ | sponge file
    ну или просто:
    sed ’s/foo/bar/’ file | sponge file
    и все вопросы снимаются.

    19.02.2009 @ 15:24
  3. Comment by bappoy:

    Внимательно прочитал changelog последнего sed’а, но не заметил там ничего, что говорило бы о смене курса партии и отмене -i. И вообще, эта опция используется слишком часто, чтобы ее в одночасье волевым решением отменить. Так что если не затруднит, приведите источник, подтверждающий Ваши слова.

    Способов редактирования файлов на месте довольно много, очень рад познакомиться еще с одним.

    19.02.2009 @ 23:51
Write comment

Я не робот.