- Частые ошибки программирования на Bash
- 1. for i in `ls *.mp3`
- 2. cp $file $target
- 3. [ $foo = «bar» ]
- 4. cd `dirname «$f»`
- 5. [ «$foo» = bar && «$bar» = foo ]
- 6. [[ $foo > 7 ]]
- 7. count=0; grep foo bar | while read line; do ((count++)); done; echo «number of lines: $count»
- 8. if [grep foo myfile]
- 9. if [bar=»$foo»]
- 10. if [ [ a = b ] && [ c = d ] ]
- Оператор if else в Bash
- Оператор if
- Оператор if else
- Оператор if elif else
- Вложенные операторы if
- Несколько условий
- Операторы тестирования
- Выводы
Частые ошибки программирования на Bash
Качество скриптов, используемых для автоматизации и оптимизации работы системы, является залогом ее стабильности и долголетия, а также сохраняет время и нервы администратора этой системы. Несмотря на кажущуюся примитивность bash как языка программирования, он полон подводных камней и хитрых течений, способных значительно подпортить настроение как разработчику, так и администратору.
Большинство имеющихся руководств посвящено тому, как надо писать. Я же расскажу о том, как писать НЕ надо 🙂
Данный текст является вольным переводом вики-страницы «Bash pitfalls» по состоянию на 13 декабря 2008 года. В силу викиобразности исходника, этот перевод может отличаться от оригинала. Поскольку объем текста слишком велик для публикации целиком, он будет публиковаться частями.
1. for i in `ls *.mp3`
Одна из наиболее часто встречающихся ошибок в bash-скриптах — это циклы типа такого:
Это не сработает, если в названии одного из файлов присутствуют пробелы, т.к. результат подстановки команды ls *.mp3 подвергается разбиению на слова. Предположим, что у нас в текущей директории есть файл 01 — Don’t Eat the Yellow Snow.mp3 . Цикл for пройдётся по каждому слову из названия файла и $i примет значения: «01» , «-» , «Don’t» , «Eat» , «the» , «Yellow» , «Snow.mp3» .
Заключить всю команду в кавычки тоже не получится:
Весь вывод теперь рассматривается как одно слово, и вместо того, чтобы пройтись по каждому из файлов в списке, цикл выполнится только один раз, при этом i примет значение, являющееся конкатенацией всех имён файлов через пробел.
На самом деле использование ls совершенно излишне: это внешняя команда, которая просто не нужна в данном случае. Как же тогда правильно? А вот так:
Предоставьте bash’у самому подставлять имена файлов. Такая подстановка не будет приводить к разделению строки на слова. Каждое имя файла, удовлетворяющее шаблону *.mp3 , будет рассматриваться как одно слово, и цикл пройдёт по каждому имени файла по одному разу.
Дополнительные сведения можно найти в п. 20 Bash FAQ.
Внимательный читатель должен был заметить кавычки во второй строке вышеприведённого примера. Это плавно подводит нас к подвоху №2.
2. cp $file $target
Что не так в этой команде? Вроде бы ничего особенного, если вы абсолютно точно знаете, что переменные $file и $target не содержат пробелов или подстановочных символов.
Но если вы не знаете, что за файлы вам попадутся, или вы параноик, или просто пытаетесь следовать хорошему стилю bash-программирования, то вы заключите названия ваших переменных в кавычки, чтобы не подвергать их разбиению на слова.
Без двойных кавычек скрипт выполнит команду cp 01 — Don’t Eat the Yellow Snow.mp3 /mnt/usb , и вы получите массу ошибок типа cp: cannot stat `01′: No such file or directory . Если в значениях переменных $file или $target содержатся символы *, ?, [..] или (..), используемые в шаблонах подстановки имен файлов («wildmats»), то в случае существования файлов, удовлетворяющих шаблону, значения переменных будут преобразованы в имена этих файлов. Двойные кавычки решают эту проблему, если только «$file» не начинается с дефиса — , в этом случае cp думает, что вы пытаетесь указать ему еще одну опцию командной строки.
Один из способов обхода — вставить двойной дефис ( — ) между командой cp и её аргументами. Двойной дефис сообщит cp , что нужно прекратить поиск опций:
Однако вам может попасться одна из систем, в которых такой трюк не работает. Или же команда, которую вы пытаетесь выполнить, не поддерживает опцию — . В таком случае читайте дальше.
Ещё один способ — убедиться, что названия файлов всегда начинаются с имени каталога (включая ./ для текущего). Например:
Даже если у нас есть файл, название которого начинается с «-«, механизм подстановки шаблонов гарантирует, что переменная будет содержать нечто вроде ./-foo.mp3 , что абсолютно безопасно для использования вместе с cp .
3. [ $foo = «bar» ]
В этом примере кавычки расставлены неправильно: в bash нет необходимости заключать строковой литерал в кавычки; но вам обязательно следует закавычить переменную, если вы не уверены, что она не содержит пробелов или знаков подстановки (wildcards).
Этот код ошибочен по двум причинам:
1. Если переменная, используемая в условии [ , не существует или пуста, строка
будет воспринята как
что вызовет ошибку «unary operator expected». (Оператор «=» бинарный, а не унарный, поэтому команда [ будет в шоке от такого синтаксиса)
2. Если переменная содержит пробел внутри себя, она будет разбита на разные слова перед тем, как будет обработана командой [ :
Даже если лично вам кажется, что это нормально, такой синтаксис является ошибочным.
Правильно будет так:
Но этот вариант не будет работать, если $foo начинается с — .
В bash для решения этой проблемы может быть использовано ключевое слово [[ , которое включает в себя и значительно расширяет старую команду test (также известную как [ )
Внутри [[ и ]] уже не нужно брать в кавычки названия переменных, поскольку переменные больше не разбиваются на слова и даже пустые переменные обрабатываются корректно. С другой стороны, даже если лишний раз взять их в кавычки, это ничему не повредит.
Возможно, вы видели код типа такого:
Хак x»$foo» требуется в коде, который должен работать в шеллах, не поддерживающих [[ , потому что если $foo начинается с — , команда [ будет дезориентирована.
Если одна из частей выражения — константа, можно сделать так:
Команду [ не волнует, что выражение справа от знака «=» начинается с — . Она просто использует это выражение, как строку. Только левая часть требует такого пристального внимания.
4. cd `dirname «$f»`
Пока что мы в основном говорим об одном и том же. Точно так же, как и с раскрытием значений переменных, результат подстановки команды подвергается разбиению на слова и раскрытию имен файлов (pathname expansion). Поэтому мы должны заключить команду в кавычки:
Что здесь не совсем очевидно, это последовательность кавычек. Программист на C мог бы предположить, что сгруппированы первая и вторая кавычки, а также третья и четвёртая. Однако в данном случае это не так. Bash рассматривает двойные кавычки внутри команды как первую пару, и наружные кавычки — как вторую.
Другими словами, парсер рассматривает обратные кавычки ( ` ) как уровень вложенности, и кавычки внутри него отделены от внешних.
Такого же эффекта можно достичь, используя более предпочтительный синтаксис $() :
Кавычки внутри $() сгруппированы.
5. [ «$foo» = bar && «$bar» = foo ]
Нельзя использовать && внутри «старой» команды test или её эквивалента [ . Парсер bash’а видит && вне скобок и разбивает вашу команду на две, перед и после && . Лучше используйте один из вариантов:
Обратите внимание, что мы поменяли местами константу и переменную внутри [ — по причинам, рассмотренным в предыдущем пункте.
То же самое относится и к || . Используйте [[ , или -o , или две команды [ .
6. [[ $foo > 7 ]]
Если оператор > используется внутри [[ ]] , он рассматривается как оператор сравнения строк, а не чисел. В некоторых случаях это может сработать, а может и не сработать (и это произойдёт как раз тогда, когда вы меньше всего будете этого ожидать). Если > находится внутри [ ] , всё ещё хуже: в данном случае это перенаправление вывода из файлового дескриптора с указанным номером. В текущем каталоге появится пустой файл с названием 7 , и команда test завершится с успехом, если только переменная $foo не пуста.
Поэтому операторы > и [ .. ] или [[ .. ]] использовать нельзя.
Если вы хотите сравнить два числа, используйте (( )) :
Если вы пишете для Bourne Shell (sh), а не для bash, правильным способом является такой:
Обратите внимание, что команда test . -gt . выдаст ошибку, если хотя бы один из её аргументов — не целое число. Поэтому уже не имеет значения, правильно ли расставлены кавычки: если переменная пустая, или содержит пробелы, или ее значение не является целым числом — в любом случае возникнет ошибка. Просто тщательно проверяйте значение переменной перед тем, как использовать её в команде test .
Двойные квадратные скобки также поддерживают такой синтаксис:
7. count=0; grep foo bar | while read line; do ((count++)); done; echo «number of lines: $count»
На первый взгляд этот код выглядит нормально. Но на деле переменная $count останется неизменной после выхода из цикла, к большому удивлению bash-разработчика. Почему так происходит?
Каждая команда в конвейере выполняется в отдельной подоболочке (subshell), и изменения в переменной внутри подоболочки не влияют на значение этой переменной в родительском экземпляре оболочки (т.е. в скрипте, который вызвал этот код).
В данном случае цикл for является частью конвейера и выполняется в отдельной подоболочке со своей копией переменной $count , инизиализированной значением переменной $count из родительской оболочки: «0». Когда цикл заканчивается, использованная в цикле копия $count отбрасывается и команда echo показывает неизменённое начальное значение $count («0»).
Обойти это можно несколькими способами.
Можно выполнить цикл в своей подоболочке (слегка кривовато, но так проще и понятней и работает в sh):
Чтобы полностью избежать создания подоболочки, используйте перенаправление (в Bourne shell (sh) для перенаправления также создаётся subshell, поэтому будьте внимательны, такой трюк сработает только в bash):
Предыдущий способ работает только для файлов, но что делать, если нужно построчно обработать вывод команды? Используйте подстановку процессов:
Ещё пара интересных способов разрешения проблемы с субшеллами обсуждается в Bash FAQ #24.
8. if [grep foo myfile]
Многих смущает практика ставить квадратные скобки после if и у новичков часто создаётся ложное впечатление, что [ является частью условного синтаксиса, так же, как скобки в условных конструкциях языка C.
Однако такое мнение — ошибка! Открывающая квадратная скобка ( [ ) — это не часть синтаксиса, а команда, являющаяся эквивалентом команды test , лишь за тем исключением, что последним аргументом этой команды должна быть закрывающая скобка ] .
Как видите, в синтаксисе if нет никаких [ или [[ !
Ещё раз, [ — это команда, которая принимает аргументы и выдаёт код возврата; как и все нормальные команды, она может выводить сообщения об ошибках, но, как правило, ничего не выдаёт в STDOUT.
if выполняет первый набор команд, и в зависимости от кода возврата последней команды из этого набора определяет, будет ли выполнен блок команд из секции «then» или же выполнение скрипта продолжится дальше.
Если вам необходимо принять решение в зависимости от вывода команды grep , вам не нужно заключать её в круглые, квадратные или фигурные скобки, обратные кавычки или любой другой синтаксический элемент. Просто напишите grep как команду после if :
Обратите внимание, что мы отбрасываем стандартный вывод grep : нам не нужен результат поиска, мы просто хотим знать, присутствует ли строка в файле. Если grep находит строку, он возвращает 0, и условие выполняется; в противном случае (строка в файле отсутствует) grep возвращает значение, отличное от 0. В GNU grep перенаправление >/dev/null можно заменить опцией -q , которая говорит grep ‘у, что ничего выводить не нужно.
9. if [bar=»$foo»]
Как было объяснено в предыдущем параграфе, [ — это команда. Как и в случае любой другой команды, bash предполагает, что после команды следует пробел, затем первый аргумент, затем снова пробел, и т.д. Поэтому нельзя писать всё подряд без пробелов! Правильно вот так:
bar , = , «$foo» (после подстановки, но без разделения на слова) и ] являются аргументами команды [ , поэтому между каждой парой аргументов обязательно должен присутствовать пробел, чтобы шелл мог определить, где какой аргумент начинается и заканчивается.
10. if [ [ a = b ] && [ c = d ] ]
Снова та же ошибка. [ — команда, а не синтаксический элемент между if и условием, и тем более не средство группировки. Вы не можете взять синтаксис C и переделать его в синтаксис bash простой заменой круглых скобок на квадратные.
Если вы хотите реализовать сложное условие, вот правильный способ:
Заметьте, что здесь у нас две команды после if, объединённые оператором &&. Этот код эквивалентент такой команде:
Если первая команда test возвращает значение false (любое ненулевое число), тело условия пропускается. Если она возвращает true , выполняется второе условие; если и оно возвращает true , то выполняется тело условия.
Продолжение следует.
Первая публикация этого перевода происходила на страницах моего блога.
Источник
Оператор if else в Bash
В этом руководстве мы познакомим вас с основами оператора if Bash и покажем, как использовать его в сценариях оболочки.
Принятие решений — одна из самых фундаментальных концепций компьютерного программирования. Как и в любом другом языке программирования, if..else if , if..else , if..elif..else и вложенные if в Bash могут использоваться для выполнения кода на основе определенного условия.
Оператор if
Bash, if условные выражения могут иметь разные формы. Самый простой оператор if принимает следующую форму:
Оператор if начинается с ключевого слова if за которым следует условное выражение и ключевое слово then . Заявление заканчивается ключевым словом fi .
Если TEST-COMMAND значение True , STATEMENTS выполняется. Если TEST-COMMAND возвращает False , ничего не происходит, STATEMENTS игнорируется.
В общем, рекомендуется всегда делать отступ в коде и разделять блоки кода пустыми строками. Большинство людей предпочитают использовать отступы с четырьмя или двумя пробелами. Отступы и пустые строки делают ваш код более читабельным и организованным.
Давайте посмотрим на следующий пример скрипта, который проверяет, больше ли заданное число 10:
Сохраните код в файл и запустите его из командной строки:
Скрипт предложит вам ввести номер. Если, например, вы введете 15, test команда будет иметь значение true потому что 15 больше 10, и будет выполнена команда echo внутри предложения then .
Оператор if else
Оператор if..else Bash принимает следующую форму:
Если TEST-COMMAND оценивается как True , STATEMENTS1 будет выполнен. В противном случае, если TEST-COMMAND возвращает значение False , то STATEMENTS2 будет выполнено. В заявлении может быть только одно предложение else .
Давайте добавим предложение else в предыдущий пример сценария:
Если вы запустите код и введете число, сценарий напечатает другое сообщение в зависимости от того, больше ли число / равно 10.
Оператор if elif else
Оператор if..elif..else Bash имеет следующую форму:
Если TEST-COMMAND1 вычисляет значение True , то STATEMENTS1 будет выполнено. Если TEST-COMMAND2 вычисляет значение True , то STATEMENTS2 будет выполнено. Если ни одна из тестовых команд не оценивается как True , выполняется STATEMENTS2 .
В операторе может быть одно или несколько предложений elif . Предложение else является обязательным.
Условия оцениваются последовательно. Как только условие возвращает True остальные условия не выполняются, и управление программой перемещается в конец операторов if .
Добавим в предыдущий скрипт предложение elif :
Вложенные операторы if
Bash позволяет гнездо , if заявления в if заявления. Вы можете разместить несколько операторов if внутри другого оператора if .
Следующий сценарий предложит вам ввести три числа и напечатает наибольшее число из трех.
Вот как будет выглядеть результат:
Несколько условий
Логические операторы OR и AND позволяют использовать несколько условий в операторах if .
Вот еще одна версия скрипта для печати наибольшего числа из трех. В этой версии вместо вложенных операторов if мы используем логический оператор AND ( && ).
Операторы тестирования
В Bash команда test принимает одну из следующих синтаксических форм:
Чтобы сделать сценарий переносимым, лучше использовать старую команду test [ которая доступна во всех оболочках POSIX. Новая обновленная версия test команды [[ (двойные скобки) поддерживается в большинстве современных систем, использующих Bash, Zsh и Ksh в качестве оболочки по умолчанию.
Чтобы отрицать тестовое выражение, используйте оператор логического NOT ( ! ). При сравнении строк всегда используйте одинарные или двойные кавычки, чтобы избежать проблем с разделением слов и подстановкой слов.
Ниже приведены некоторые из наиболее часто используемых операторов:
- -n VAR — Истинно, если длина VAR больше нуля.
- -z VAR — Истинно, если VAR пуста.
- STRING1 = STRING2 — Истина, если STRING1 и STRING2 равны.
- STRING1 != STRING2 — Истина, если STRING1 и STRING2 не равны.
- INTEGER1 -eq INTEGER2 — Истина, если INTEGER1 и INTEGER2 равны.
- INTEGER1 -gt INTEGER2 — Истина, если INTEGER1 больше INTEGER2 .
- INTEGER1 -lt INTEGER2 — Истина, если INTEGER1 меньше INTEGER2 .
- INTEGER1 -ge INTEGER2 — Истинно, если INTEGER1 больше или больше INTEGER2.
- INTEGER1 -le INTEGER2 — Истинно, если INTEGER1 меньше или меньше INTEGER2 .
- -h FILE — Истина, если FILE существует и является символической ссылкой.
- -r FILE — Истинно, если FILE существует и доступен для чтения.
- -w FILE — Истина, если FILE существует и доступен для записи.
- -x FILE — Истина, если FILE существует и является исполняемым.
- -d FILE — Истина, если FILE существует и является каталогом.
- -e FILE — Истина, если FILE существует и является файлом, независимо от типа (узел, каталог, сокет и т. д.).
- -f FILE — Истина, если FILE существует и является обычным файлом (не каталогом или устройством).
Выводы
Операторы if , if..else и if..elif..else позволяют вам управлять потоком выполнения сценария Bash, оценивая заданные условия.
Если у вас есть какие-либо вопросы или отзывы, не стесняйтесь оставлять комментарии.
Источник