В этом руководстве мы рассмотрим основы функций Bash и покажем, как их использовать в сценариях оболочки.
Определение функций Bash
Синтаксис объявления функции bash прост. Функции могут быть объявлены в двух разных форматах:
Первый формат начинается с имени функции, за которым следуют скобки. Это предпочтительный и более используемый формат.
Несколько моментов, которые следует отметить:
Чтобы лучше это понять, взглянем на следующий пример:
Давайте проанализируем код построчно:
Область действия переменных
Локальные переменные могут быть объявлены в теле функции с local ключевым словом и могут использоваться только внутри этой функции. Вы можете иметь локальные переменные с одинаковыми именами в разных функциях.
Чтобы лучше проиллюстрировать, как работает область видимости переменных в Bash, давайте рассмотрим этот пример:
Если вы запустите скрипт, вы должны увидеть следующий вывод:
Из приведенного выше вывода можно сделать вывод, что:
Возвращаемые значения
Передача аргументов в функции Bash
Чтобы передать любое количество аргументов функции bash, просто поместите их сразу после имени функции, разделив их пробелом. Хорошей практикой является двойная кавычка аргументов, чтобы избежать неправильного разбора аргумента с пробелами в нем.
Вывод
Вы также можете прочитать о том, как использовать функцию Bash для создания запоминающейся команды быстрого доступа для более длинной команды.
Вторая форма записи ближе к сердцу C-программистам (она же более переносимая).
Как и в языке C, скобка, открывающая тело функции, может помещаться на следующей строке.
Вызов функции осуществляется простым указанием ее имени в тексте сценария.
Пример 22-1. Простая функция
Функция должна быть объявлена раньше, чем ее можно будет использовать. К сожалению, в Bash нет возможности «опережающего объявления» функции, как например в C.
Допускается даже создание вложенных функций, хотя пользы от этого немного.
Объявление функции может размещаться в самых неожиданных местах.
22.1. Сложные функции и сложности с функциями
Функции могут принимать входные аргументы и возвращать код завершения.
Пример 22-2. Функция с аргументами
Команда shift вполне применима и к аргументам функций (см. Пример 33-10).
В отличие от других языков программирования, в сценариях на языке командной оболочке, в функции передаются аргументы по значению. [1] Если имена переменных (которые фактически являются указателями) передаются функции в виде аргументов, то они интерпретируются как обычные строки символов и не могут быть разыменованы. Функции интерпретируют свои аргументы буквально.
Exit и Return
Пример 22-3. Наибольшее из двух чисел
Для случаев, когда функция должна возвращать строку или массив, используйте специальные переменные.
Пример 22-4. Преобразование чисел в римскую форму записи
Пример 22-5. Проверка возможности возврата функциями больших значений
Пример 22-6. Сравнение двух больших целых чисел
Упражнение: Используя только что полученные знания, добавьте в предыдущий пример, преобразования чисел в римскую форму записи, возможность обрабатывать большие числа.
Перенаправление
Перенаправление ввода для функций
Пример 22-7. Настоящее имя пользователя
Ниже приводится альтернативный, и возможно менее запутанный, способ перенаправления ввода для функций. Он заключается в использовании перенаправления ввода для блока кода, заключенного в фигурные скобки, в пределах функции.
Примечания
Механизм косвенных ссылок на переменные (см. Пример 34-2) слишком неудобен для передачи аргументов по ссылке.
Функции командной оболочки являются способом группирования команд, которые затем можно выполнить, указав одно имя, присвоенное всей группе или последовательности команд (routine). Имя последовательности команд должно быть уникальным в командной оболочке или в скрипте. Все команды, составляющие функцию, выполняются как обычные команды. Когда происходит обращение к функции, как к простому имени команды, будет выполнен список команд, ассоциированный с этим именем функции. Функция выполняется внутри командной оболочки, в которой она была объявлена; при интерпретации этих команд новый процесс не создается.
При поиске команд сначала ищутся специальные встроенные команды, а затем — функции командной оболочки. Специальными встроенными командами являются следующие: break, :, ., continue, eval, exec, exit, export, readonly, return, set, shift, trap и unset.
Синтаксис функций
Синтаксис объявления функций следующий
В обоих случаях объявляется функция командной оболочки FUNCTION. Использование встроенной команды function необязательно, однако если она не используется, нужно указывать скобки.
Команды, указанные внутри фигурных скобок, представляют собой тело функции. Каждый раз, когда FUNCTION указывается как имя команды, эти команды выполняются. Кодом возврата будет код возврата последней команды, выполненной в теле функции.
Обычные ошибки
Фигурные скобки и тело функции должны разделяться пробелами, в противном случае они интерпретируются неправильным образом.
Тело функции должно заканчиваться точкой с запятой или символом новой строки.
Позиционные параметры в функциях
Функции напоминают мини-скрипты: у них могут быть параметры, в них можно пользоваться переменными, известными только внутри функции (с помощью встроенной команды local), и они могут возвращать значения в вызывающую оболочку.
В функции также есть система интерпретации позиционных параметров. Однако, позиционные параметры передаются в функцию не так, как они передаются в команду или скрипт.
Если в функции выполняется встроенная команда return, функция завершается и выполнение возобновляется со следующей команды после вызова функции. Когда функция завершается, значения позиционных параметров и специального параметра # будут восстановлены и в них будут находиться значения, которые в них были до выполнения функции. Если в команде return указан числовой аргумент, то он возвращается как код возврата. Простой пример:
Отображение функций
Все функции, известные в текущей командной оболочке, можно отобразить с помощью встроенной команды set, указываемой без параметров. Если к функциям не применялась команда unset, функции будут существовать после того, как они были использованы. Функции можно также отображать с помощью команды which
Это вариант функции, настройка которой обычно выполняется по конфигурационным файлам, определяющим ресурсы командной оболочки, доступные пользователю. Функции более гибки, чем алиасы, и позволяют гораздо проще адаптировать пользовательскую среду.
Bash-скрипты, часть 6: функции и разработка библиотек
Занимаясь разработкой bash-скриптов, вы рано или поздно столкнётесь с тем, что вам периодически приходится использовать одни и те же фрагменты кода. Постоянно набирать их вручную скучно, а копирование и вставка — не наш метод. Как быть? Хорошо бы найти средство, которое позволяет один раз написать блок кода и, когда он понадобится снова, просто сослаться на него в скрипте.
Оболочка bash предоставляет такую возможность, позволяя создавать функции. Функции bash — это именованные блоки кода, которые можно повторно использовать в скриптах.
Объявление функций
Функцию можно объявить так:
Функцию можно вызвать без аргументов и с аргументами.
Использование функций
Напишем скрипт, содержащий объявление функции и использующий её:
Результаты вызова функции
Функцию можно вызывать столько раз, сколько нужно. Обратите внимание на то, что попытавшись использовать функцию до её объявления, вы столкнётесь с ошибкой. Напишем демонстрирующий это скрипт:
Как и ожидается, ничего хорошего после его запуска не произошло.
Попытка воспользоваться функцией до её объявления
Придумывая имена для функций, учитывайте то, что они должны быть уникальными, иначе проблем не избежать. Если вы переопределите ранее объявленную функцию, новая функция будет вызываться вместо старой без каких-либо уведомлений или сообщений об ошибках. Продемонстрируем это на примере:
Как видно, новая функция преспокойно затёрла старую.
Использование команды return
Команда return позволяет задавать возвращаемый функцией целочисленный код завершения. Есть два способа работы с тем, что является результатом вызова функции. Вот первый:
Команда echo вывела сумму введённого числа и числа 10.
Вывод значения, возвращаемого функцией
Учтите, что максимальное число, которое может вернуть команда return — 255. Если функция должна возвращать большее число или строку, понадобится другой подход.
Запись вывода функции в переменную
Ещё один способ возврата результатов работы функции заключается в записи данных, выводимых функцией, в переменную. Такой подход позволяет обойти ограничения команды return и возвращать из функции любые данные. Рассмотрим пример:
Вот что получится после вызова данного скрипта.
Запись результатов работы функции в переменную
Аргументы функций
Функции bash можно воспринимать как небольшие фрагменты кода, которые позволяют экономить время и место, избавляя нас от необходимости постоянно вводить с клавиатуры или копировать одни и те же наборы команд. Однако, возможности функций гораздо шире. В частности, речь идёт о передаче им аргументов.
Аргументы передают функции, записывая их после её имени:
Вот пример, в котором функция вызывается с аргументами и занимается их обработкой:
Вызов функции с аргументами
Обратите внимание на то, что функция не может напрямую работать с параметрами, которые переданы скрипту при его запуске из командной строки. Например, напишем такой сценарий:
При его запуске, а точнее, при вызове объявленной в нём функции, будет выведено сообщение об ошибке.
Функция не может напрямую использовать параметры, переданные сценарию
Вместо этого, если в функции планируется использовать параметры, переданные скрипту при вызове из командной строки, надо передать их ей при вызове:
Теперь всё работает правильно.
Передача функции параметров, с которыми запущен скрипт
Работа с переменными в функциях
Переменные, которыми мы пользуемся в сценариях, характеризуются областью видимости. Это — те места кода, из которых можно работать с этими переменными. Переменные, объявленные внутри функций, ведут себя не так, как те переменные, с которыми мы уже сталкивались. Они могут быть скрыты от других частей скриптов.
Существуют два вида переменных:
▍Глобальные переменные
Глобальные переменные — это переменные, которые видны из любого места bash-скрипта. Если вы объявили глобальную переменную в основном коде скрипта, к такой переменной можно обратиться из функции.
Почти то же самое справедливо и для глобальных переменных, объявленных в функциях. Обращаться к ним можно и в основном коде скрипта после вызова функций.
По умолчанию все объявленные в скриптах переменные глобальны. Так, к переменным, объявленным за пределами функций, можно без проблем обращаться из функций:
Вот что выведет этот сценарий.
Обращение к глобальной переменной из функции
Когда переменной присваивается новое значение в функции, это новое значение не теряется когда скрипт обращается к ней после завершения работы функции. Именно это можно видеть в предыдущем примере.
Что если такое поведение нас не устраивает? Ответ прост — надо использовать локальные переменные.
▍Локальные переменные
Переменные, которые объявляют и используют внутри функции, могут быть объявлены локальными. Для того, чтобы это сделать, используется ключевое слово local перед именем переменной:
Если за пределами функции есть переменная с таким же именем, это на неё не повлияет. Ключевое слово local позволяет отделить переменные, используемые внутри функции, от остальных переменных. Рассмотрим пример:
Локальная переменная в функции
Передача функциям массивов в качестве аргументов
Попробуем передать функции в качестве аргумента массив. Сразу хочется сказать, что работать такая конструкция будет неправильно:
Неправильный подход к передаче функциям массивов
Как видно из примера, при передаче функции массива, она получит доступ лишь к его первому элементу.
Для того, чтобы эту проблему решить, из массива надо извлечь имеющиеся в нём данные и передать их функции как самостоятельные аргументы. Если надо, внутри функции полученные ей аргументы можно снова собрать в массив:
Сборка массива внутри функции
Как видно из примера, функция собрала массив из переданных ей аргументов.
Рекурсивные функции
Рекурсия — это когда функция сама себя вызывает. Классический пример рекурсии — функция для вычисления факториала. Факториал числа — это произведение всех натуральных чисел от 1 до этого числа. Например, факториал 5 можно найти так:
Если формулу вычисления факториала написать в рекурсивном виде, получится следующее:
Этой формулой можно воспользоваться для того, чтобы написать рекурсивную функцию:
Проверим, верно ли работает этот скрипт.
Как видите, всё работает как надо.
Создание и использование библиотек
Итак, теперь вы знаете, как писать функции и как вызывать их в том же скрипте, где они объявлены. Что если надо использовать функцию, тот блок кода, который она собой представляет, в другом скрипте, не используя копирование и вставку?
Оболочка bash позволяет создавать так называемые библиотеки — файлы, содержащие функции, а затем использовать эти библиотеки в любых скриптах, где они нужны.
У команды source есть псевдоним — оператор «точка». Для того, чтобы подключить файл в скрипте, в скрипт надо добавить конструкцию такого вида:
Это — библиотека. Воспользуемся ей в сценарии:
Только что мы использовали библиотечную функцию внутри скрипта. Всё это замечательно, но что если мы хотим вызвать функцию, объявленную в библиотеке, из командной строки?
Вызов bash-функций из командной строки
Теперь функцию можно вызывать прямо из командной строки:
Вызов функции из командной строки
Ещё приятнее то, что такая вот библиотека оказывается доступной всем дочерним процессам оболочки, то есть — ей можно пользоваться в bash-скриптах, не заботясь о подключении к ним этой библиотеки.
Тут стоит отметить, что для того, чтобы вышеприведённый пример заработал, может понадобиться выйти из системы, а потом войти снова. Кроме того, обратите внимание на то, что если имя функции из библиотеки совпадёт с именем какой-нибудь стандартной команды, вместо этой команды будет вызываться функция. Поэтому внимательно относитесь к именам функций.
Итоги
На сегодня это всё. В следующий раз поговорим об утилите sed — мощном средстве обработки строк.
Уважаемые читатели! А вы пользуетесь функциями собственной разработки для решения повседневных задач?
Давайте разберемся как передавать и обрабатывать аргументы скрипта и ознакомимся с основными управляющими конструкциями bash.
В простом скрипте из предыдущей статьи мы использовали переменную «$1«, которая содержит первый аргумент командной строки при вызове скрипта. Аналогично можно использовать «$2», «$3» и так далее для доступа ко второму, третьему… аргументам командной строки. Вот пример:
Иногда необходимо сослаться сразу на все аргументы командной строки. Для этого в bash есть специальная переменная «$@«, которая содержит все аргументы переданные скрипту разделенные пробелами. Мы будем использовать эту переменную чуть позже при рассказе о циклах со счетчиком (конструкция «for»).
Управляющие конструкции bash
Если вы раньше программировали на процедурных языках, таких как Си, Паскаль, Перл и тому подобных, вам должны быть знакомы управляющие конструкции вроде «if», «for» и другие. В bash тоже есть все эти конструкции. В следующих разделах пособия я познакомлю вас с ними и покажу чем они отличаются от подобных конструкций из других языков программирования. Если вы раньше не программировали — не волнуйтесь. Материал будет изложен подробно и дополнен примерами, так что даже новичок в программировании сможет разобраться.
Оператор условного выбора «if»
Если вы раньше программировали на языке Си, то должны знать сколько требуется усилий чтобы определить какой из двух файлов был создан первым, например. А все из-за того, что в Си нет встроенных средств для такого рода сравнения. Вместо этого приходится использовать системный вызов stat() для каждого файла и затем сравнивать результат вручную. Но в bash есть встроенный механизм сравнения файлов, Поэтому узнать «доступен ли для чтения файл /tmp/myfile» настолько же просто как и узнать «превосходит ли значение переменной ‘myvar’ 4».
Привожу список наиболее часто употребляемых в bash операторов сравнения
В следующих примерах показано как использовать оператор сравнения в конструкции «if»:
Квадратные скобки вычисляют условное выражение стоящее в них (это синоним встроенной функции bash — test). Возвращаемый результат — 1 или 0 в зависимости от того выполняется условие или нет. в скобках может стоять несколько выражений, связанных логическими операторами «и» или «или«. Подробнее на странице справки help test.
В некоторых случаях одна и та же операция сравнения может быть сделана несколькими разными способами. Обе конструкции из следующего примера функционально идентичны:
В первой конструкции из предыдущего примера использована операция арифметического сравнения, а во втором — операция сравнения строк.
Тонкости при сравнении строк
В большинстве случаев, когда вы не заключаете строки и строковые переменные в двойные кавычки, это может привести к ошибке. Почему? Да потому что в строке может встретится пробел или символ табуляции, которые bash не сможет правильно обработать. Вот пример некорректного сравнения строк:
В этом примере, если значение переменной «$myvar» будет равно «foo», код будет работать как и ожидается и не печатать ничего. Но если значение переменной «$myvar» будет равно «foo bar oni», скрипт вызовет следующую ошибку:
[: too many arguments
После подстановки значения переменной, bash пытается произвести следующую операцию сравнения:
[ foo bar oni = «foo bar oni» ]
В этом случае bash не может правильно обработать сравнение строк содержащих пробелы, одна из которых не заключена в двойные кавычки. Интерпретатор думает, что в квадратных скобках слишком много аргументов. После заключения переменной в двойные кавычки, ошибка не возникает и код работает так как мы задумали. Запомните, если вы возьмете в привычку заключать в двойные кавычки все строковые аргументы и переменные, то избежите множества ошибок подобных описанной выше. Вот исправленный кусок кода:
if [ «$myvar» = «foo bar oni» ] then echo «yes» fi
Этот код будет работать корректно и не преподнесет нам больше никаких неприятных сюрпризов.
Конструкция создания циклов «for»
Хорошо, с условными переходами разобрались, пора перейти к циклическим конструкциям. Начнем с управляющей конструкции «for«. Вот стандартный пример:
Что же именно произошло? Часть «for x» цикла «for» определяет переменную (называемую итератором) «$x», которая последовательно принимает значения «one», «two», «three», и «four» (по одному за один такт цикла). После присвоения каждого нового значения переменной «$x», выполняется тело цикла (код между словами «do» и «done»). В теле цикла мы выводим на печать значение переменной «$x». Заметим, что после слова «in» в конструкции «for» всегда стоит некий список. В данном примере мы указали четыре слова, но этот список может содержать имена файлов или даже шаблон (wildcard). В следующем примере показано как использовать шаблоны при инициализации итератора цикла:
Код этого цикла исполнится для каждого файла из /etc/ имя которого начинается с «r». Сначала bash найдет все такие файлы и заменит шаблон строкой /etc/rc0.d /etc/rc1.d /etc/rc2.d /etc/rc3.d /etc/rc4.d … /etc/rsyslog.d перед тем как приступить к выполнению цикла. В теле цикла для каждого файла из списка проверяется является ли этот файл директорией при помощи оператора «-d«. Если файл оказался директорией, рядом с его называнием печатается «(dir)».
В списке инициализации итератора можно использовать несколько шаблонов одновременно и даже переменные окружения:
Bash в этом примере подставляет значение переменной и раскрывает шаблоны. А затем копирует все файлы в заданную директорию.
До этого все примеры содержали шаблоны основанные на абсолютных путях, но можно использовать и относительные:
В этом примере bash раскрывает шаблон относительно текущей рабочей директории (не той в которой находится скрипт, а той которую показывает команда «pwd»). Поиграйтесь с этим скриптом, позапускайте его из разных директорий и посмотрите на результат.
Иногда может потребоваться запустить цикл по списку аргументов из командной строки. Вот как это делается:
В этом примере мы использовали переменную «$@» о которой говорили выше.
Арифметика в shell
Перед тем как приступить к разбору следующего вида циклической конструкции, научимся при помощи интерпретатора производить простые арифметические операции. Просто заключите арифметическое выражение в конструкцию «$(( ))» и bash посчитает ее значение. Вот несколько примеров:
Теперь, когда вы познакомились с вычислением арифметических выражений в shell, пришло время рассказать о циклических конструкциях «while» и «until».
Циклические конструкции с условиями («while» и «until»)
«while»–цикл исполняется пока выражение в квадратных скобках истинно. Он имеет следующий формат:
while [ условие ] do код done
В следующем примере тело цикла исполняется ровно 10 раз:
После каждого выполнения кода тела цикла переменная «myvar» увеличивается на 1. Когда значение переменной становится равным 10, условие в квадратных скобках не выполняется и цикл прерывается.
«Until»–цикл очень похож на «while»–цикл: он повторяется пока выражение в квадратных скобках ложно. Вот пример «until»–цикла по функциональности идентичного «while»–циклу из предыдущего примера:
Экстренный выход из цикла
Для экстренного выхода из «for», «while» или «until» цикла используется команда break. Для выхода из нескольких вложенных циклов — break N, где N — количество вложенных циклов.
В последнем примере: «while :» — бесконечный цикл. Двоеточие — это команда bash которая не делает ничего но всегда завершается успехом. Переменная $? содержит статус с которым завершилась последняя команда (подробнее о специальных переменных смотри man bash). В нашем случае код отличный от 0 обозначает что при скачивании файла произошла ошибка. Как только условие в квадратных скобках выполнено, интерпретатор переходит к исполнению команды стоящей после логического и (&&). Break прерывает выполнение цикла.
Предпоследнюю строку предыдущего примера можно заменить на знакомую нам условную конструкцию «if» (помним, что в bash одно действие можно сделать несколькими разными способами):
то же самое но через условную конструкцию:
Да, конструкции можно записывать в одну строку, только нужно поставить несколько разделяющих знаков «точка с запятой». Но не стоит привыкать к такой форме записи — это усложняет читаемость кода.
Команда–переключатель «case»
Конструкция условного перехода «case» может оказаться очень полезной. Вот пример ее использования:
В этом примере сначала происходит обработка строки в переменной «$x» — «$». Как мы помним из первой статьи, после этой операции в переменной «$x» остается только расширение файла. Затем bash сравнивает это расширение с вариантами стоящими слева от одинарных скобок «)«. Если совпадение найдено, выполняется соответствующее действие. Если совпадения не найдено, никаких действий не выполняется, но в данном конкретном коде совпадение будет всегда, потому что в последней строке стоит шаблон «*«, совпадающий с любой последовательностью символов.
Функции и пространство имен
В bash вы можете определять свои функции, как и в других языках программирования (C, Pascal…). Эти функции могут принимать аргументы, используя механизм очень похожий на механизм работы с аргументами командной строки. Вот пример определения простой функции:
Выше мы определили функцию с именем «tarview», которая принимает один аргумент — имя тарбола. Эта функция определяет вид тарбола (без сжатия, сжатый gzip-ом или bzip2) по расширению, затем печатает этот тип и показывает содержимое архива. Если формат определить не удалось, выводится соответствующее сообщение. Вот пример вызова функции:
Как вы видите, обращение к аргументам внутри функции происходит по тем же именам как и к аргументам командной строки внутри скрипта. Переменная «$#» содержит количество переданных функции аргументов. Единственное что остается по-прежнему — переменная «$0«. Она содержит название скрипта при вызове функции из скрипта или строку «bash» при вызове функции напрямую из командной строки.
Вызвать функцию из командной строки можно следующим образом: сохраняем код функции в файл (например с названием «/myfunc.txt») а затем даем следующую команду:
или что тоже самое
Эти команды строка за строкой исполняют инструкции написанные в файле в текущей командной оболочке. (Можно так же напечатать код функции строка за строкой в командной строке, но первый способ намного удобнее). После этого можем вызывать нашу функцию прямо из командной строки:
/.bash_profile, тогда вы сможете вызывать ее из командной строки в любое время при следующем логине.
Пространство имен
Часто возникает потребность создать переменную окружения внутри функции. В большинстве компилируемых языков (например Си), когда вы создаете переменную внутри функции, она попадает в отдельное пространство имен этой функции. Например, если вы напишите функцию «my function» на C и внутри этой функции создадите переменную «x», то она никак не повлияет на переменную с тем же именем «x», созданную вне функции «myfunction».
Но в bash все по-другому. В bash, когда вы создаете переменную внутри функции, она попадает в общее пространство имен. Это значит, что она может перезаписать значение глобальной переменной с таким же именем и продолжит свое существование даже после завершения исполнения функции:
Результатом исполнения этого кода будет строка «ne two three three«, показывающая что переменная «myvar», созданная внутри функции перезаписала значение глобальной переменной «myvar» и что последнее значение итератора «x» равное «three» продолжило существование даже после завершения функции.
В этом простом примере ошибку легко заметить и устранить, переименовав переменные внутри функции. Но есть гораздо более правильное решение этой проблемы: при создании переменной можно явно указать что она является локальной при помощи инструкции «local«. Созданная таким способом внутри функции переменная будет отнесена к локальному пространству имен этой функции и не сможет никак повлиять на глобальную переменную с таким же именем. Следующий пример демонстрирует возможность создания локальной переменной:
Результатом выполнения этого кода будет строка «hello» — значение глобальной переменной «myvar» (на которую никак не повлияла локальная переменная «myvar», созданная внутри функции), а локальная переменная «x» перестает существовать после завершения функции.
Единственное условие при котором вы не должны использовать локальные переменные внутри функций — если хотите изменить значение глобальной переменной.
Подведение итогов
Вот и все. Теперь вы имеете представление о программировании в bash и можете писать свои скрипты. За более подробной информацией обращайтесь к справке man bash или к руководству Advanced Bash-Scripting Guide