Лабораторная работа №7

Элементы программирования оболочки

В предыдущих лекциях мы рассматривали язык оболочки с точки зрения в основном непосредственного исполнения вводимых команд. Теперь взглянем на него под другим углом: как на универсальный язык программирования, а на оболочку — как на интерпретирующую реализацию этого языка.

От универсального языка программирования ожидаются: средства описания структур данных (переменные), средства вычисления выражений и присвоения их значений переменным, средства организации последовательного, условного и циклического исполнения, средства декомпозиции программы на подпрограммы.

Все эти средства присутствуют в стандартном языке оболочки (Поскольку открытые системы являются многозадачными, декомпозируя задачу, решаемую сценарием, на несколько сценариев и используя системную многозадачность, можно реализовать определенный параллелизм в исполнении сценариев. Описание соответствующих приемов и команды позволяющей организовать синхронизацию (“рандеву”) потоков исполнения, выходит за рамки настоящего курса). Его особенностью является возможность использовать команды (стандартные и нестандартные) ОС в качестве своего рода "вызовов функций" (хотя и определение, и вызов функции также присутствуют в языке как отдельный механизм).

При попытках реализовать "простые программы из учебников" на языке оболочки результат часто оказывается не самым изящным. Однако этот язык очень хорошо приспособлен для решения административных и системных задач. В большинстве открытых ОС значительная часть самой системы написана на этом языке.

Пределы одной лекции позволяют лишь бегло представить механизмы языка оболочки, проиллюстрировав их несколькими примерами.

 

Комментарии и указание оболочки

Часть любой строки, начинающаяся со знака "#" вплоть до символа новой строки, является комментарием и не исполняется оболочкой. Как и в других языках программирования, комментарии предназначены для передачи какой-либо неочевидной из текста самой программы информации ее читателю.

Во многих системах (включая GNU/Linux) специальная нестандартная форма комментария может использоваться также для передачи ядру системы информации о том, какую именно оболочку использовать для интерпретации сценария. Такой комментарий имеет вид символов #!, за которыми слитно следует имя исполняемого файла (обычно /bin/sh, /bin/bash или /usr/bin/bash), и должен начинать файл сценария, то есть находиться в первой строке.

Информация из специального комментария востребуется только если файлу сценария придан атрибут исполняемого, а его выполнение инициировано указанием имени файла в качестве команды ОС. Если сценарий запускается на выполнение явным вызовом дополнительного экземпляра оболочки (например, /usr/bin/bash <сценарий>), специальный комментарий игнорируется. Эта строка почти всегда присутствует при публикации сценариев, чтобы было понятно, используется ли язык стандартной оболочки (sh), ее расширения (bash, zsh, ksh) или не вполне совместимые со стандартом диалекты (такие, как tcsh) (На самом деле в специальном комментарии может быть указан любой исполняемый файл, способный интерпретировать текстовые файлы. Возможно вы сталкивались с ним в начале программ, например, на языке Perl).

 

Переменные и присваивание значений

Как мы помним из пятой лекции, конструкция, состоящая из имени переменной и ее значения, разделенных знаком равенства ("=") без промежутков, за которой не следует никакой команды, является определением переменной оболочки. Переменная, определенная таким способом, не оказывает влияния на поведение последующих команд.

Чтобы значение переменной передавалось всем вызываемым командам, ее следует сделать передаваемой (экспортировать ее) командой export с именем переменной в качестве аргумента.

Для того чтобы присвоить переменной новое значение, ее просто переопределяют; определение, таким образом, выступает и в качестве оператора присваивания.

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

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

Тем не менее в языке присутствуют арифметические и логические операции. Арифметические операции определены на множестве строк, представляющих собой запись чисел.

 

“Арифметические” и “логические” выражения

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

Рис. 1

Выражение интерпретируется как если бы оно было заключено в двойные кавычки “”и “”, т.е. раскрываются имена переменных, предваренные знаком доллара “$”, но специальное значение прочих символов (например, звездочки) отменяется.

Выражение состоит из переменных, констант и знаков операций. Стандартом определены операции, перечисленные на рис.2.

Рис. 2

Рис. 3

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

Поскольку руководства и справочники по Си общедоступны, разбирать операции подробно мы не будем. Они в основном соответствуют общепринятой математической и программистской нотации для выражений, за исключением представления символа равенства сочетанием “= =” а не символом “=”.

Чаще всего арифметическое раскрытие применяется в команде присваивания, но его можно использовать в любом месте (например, для задания числового операнда команды или числового параметра ключа) (рис. 3).

Помимо конструкции арифметического раскрытия, существует стандартная команда ехрr, также вычисляющая значение выражения (с несколько иным синтаксисом, в частности, использующим для проверки на равенство знака “=“), переданного ей в качестве аргумента, и выводящая его результат. Выражение при этом следует экранировать двойными кавычками “” и “”.

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

При настоятельной необходимости применить в сценарии численные методы, включающие работу с вещественными числами в представлении с плавающим десятичным знаком, можно воспользоваться стандартной командой вычисления выражения с произвольной точностью bc, которая обладает также внутренними возможностями сценирования. Ее описание выходит за рамки этого курса.

 

Генерация кодов возврата

Обычно директивные языки для определения условий в операторах условного и циклического исполнения применяют механизм выражений. Язык оболочки в этом плане достаточно эксцентричен и использует с этой целью механизм кодов возврата (переменной $?) команды ОС. Дополнительную путаницу вводит то, что в открытых системах успешный код возврата — ноль, который, таким образом, соответствует логическому значению "истинно", в то время как в "логических" выражениях, обсуждавшихся выше, используется соглашение Си (ноль, наоборот, соответствует значению "ложно", а "истинно" — любому ненулевому значению).

(Отсутствие простого механизма согласования между раскрытием арифметических выражений и условиями условного и циклического исполнения, различие в синтаксисе а) раскрываемых выражений, б) команды expr и в) обсуждаемой ниже команды test являются серьезными недостатками языка оболочки, заметно усложняющими его освоение даже опытными программистами.)

Хотя условие условного или циклического исполнения может задаваться самыми разными командами (поскольку любая команда завершается с каким-либо кодом возврата), чаще всего в соответствующих операторах используется команда test. Эта команда вычисляет переданное ей в виде набора аргументов выражение и завершается с кодом возврата "0" (ноль), если оно истинно, "1", если оно ложно, и "2", если выражение содержит синтаксическую ошибку. Она настолько важна для программирования оболочки, что для нее введено особое сокращение: вместо подачи команды test с аргументами можно просто заключить аргументы в квадратные скобки "[" и "]", отделив их от первого и последнего аргументов промежутками (рис. 4).

Рис. 4

В качестве аргументов команды test могут выступать константы, переменные и символические обозначения операций, а также круглые скобки, позволяющие менять приоритет исполнения операций. Обратите внимание, что выражение передается команде в виде совокупности аргументов, а не в виде одного аргумента, поэтому аргументы должны разделяться промежутками, заключать выражение целиком в кавычки нельзя, а любые специальные символы должны экранироваться.

Командой test поддерживаются операции, перечисленные на рис. 5. Они различаются по типу (точнее, по интерпретации) операндов, но все возвращают "логические" (в указанном выше смысле) значения.

 

 

Операции над строками

 

-n строка

Длина строки ненулевая

-z строка

Длина строки нулевая

cтрока1 = строка2

Строки идентичны

строка1 ! = строка2

Строки различны

Операции над числами

 

число1 -eq число2

Целые числа равны

число1 -ne число2

Целые числа не равны

число1 -gt число2

Первое целое больше второго

число1 -ge число2

Первое целое больше или равно второму

число1 -lt число2

Первое целое меньше второго

число1 -le число2

Первое целое меньше или равно второму

Операции над выражениями

 

выражение1     выражение2

Логическое "И"

выражение1    выражение2

Логическое "ИЛИ"

!    выражение

Логическое "НЕ"

Операции над именами и дескрипторами файлов

-b файл

Файл существует и является блочным устройством

файл

Файл существует и является символьным устройством

-d файл

Файл существует и является каталогом

-е файл

Файл существует

-f файл

Файл существует и является обычным файлом

-g файл

Файл существует и обладает установленным битом SGID

-h файл

 

 

Файл существует и является символической ссылкой

-L файл

Аналогично предыдущему

-p файл

Файл существует и является файлом-очередью

-r файл

Файл существует и может быть прочитан текущим процессом

-S файл

Файл существует и является гнездом (сокетом)

-s файл

Файл существует и обладает ненулевой длиной

-u файл

Файл существует и обладает установленным битом SUID

-w файл

Файл существует и может быть записан текущим процессом

-x файл

Файл существует и может исполняться текущим процессом

-t дескриптор

Файл, связанный с дескриптором, открыт и ассоциирован с терминалом

 

Рис. 5. Операции, поддерживаемые командой test.

Операции над числами допускают в качестве операндов только константы и переменные, однако можно использовать и раскрываемые арифметические выражения. И символическое обозначение операций, и операнды выражений, передаваемых команде test, в терминах командной строки являются операндами команды (хотя форма операций и похожа на ключи).

 

Условное исполнение

Оболочка реализует команду условного исполнения, доступную в трех модификациях: if - then - fi, if -then - else -  fi и if - then - elif   ...   -  fi. Простейшей является форма if - then -  fi (рис. 6).

Рис. 6

Рис. 7

Выполняется оператор условного исполнения так: выполняется список команд1 затем, если код завершения истинен (равен нулю), выполняется список_команд2. Команды в каждом списке могут соединяться переводом строки или точкой с запятой ";" (рис. 7).

Обратите внимание, что если оператор условного исполнения (или любая другая сложная конструкция) вводится в интерактивном режиме и строка завершилась раньше, чем оператор, оболочка выведет строку приглашения продолжения (значение переменной $PS2; по умолчанию ">"), будет ожидать продолжения ввода и повторять это, пока оператор не будет завершен (в данном случае — комбинацией символов fi).

Если в первом списке более одной команды, кодом завершения списка будет код завершения последней в списке команды. Однако существуют два других символа завершения команды: "&&" и "||". Кодом завершения списка, соединенного "&&", является результат выполнения операции "И" над значениями истинности кодов завершения входящих в список команд, а кодом завершения "||"-списка — результат выполнения операции "ИЛИ".

Использование в первом списке команды if более одной команды является экзотическим приемом, которого по возможности следует избегать.

                          Рис. 8

                                                                    Рис. 9

Вторая форма (рис. 8) более сложна: выполняется список_команд1, затем, если код завершения истинен, выполняется список_команд2, а если ложен — список_команд3 (рис. 9).

                          Рис. 10

И наконец, третья форма (рис. 10) позволяет задавать множественные условия: если код завершения первого списка истинен, выполняется второй список команд, иначе выполняется третий список команд и, если его код завершения истинен, выполняется четвертый список команд. Конструкция elif - then может быть множественной, но в любом случае выполнен будет лишь один then-список.

В if - then - elif ... fi форме оператора if также может присутствовать конструкция else; следующий за ней список команд будет выполнен, если коды завершения if-списка и всех elif-списков оказались ложными.

 

Циклическое исполнение с предусловием

Оболочка поддерживает два оператора цикла с предусловием: while - do - done (рис. 11) и until - do done (рис. 12).

                          Рис. 11

 

                          Рис. 12

Выполнение любого из них заключается в том, что выполняется список_команд1 и в зависимости от кода завершения либо выполняется список_команд2 и выполнение цикла повторяется, либо выполнение цикла завершается. while-цикл выполняется, пока код завершения первого списка истинен, а until-цикл — пока он ложен.

Обратите внимание, что until-цикл, в отличие от использования этого ключевого слова в большинстве языков программирования, является также циклом с предусловием, а не с постусловием. Если же действительно необходимо организовать циклическое исполнение с постусловием, реальное тело цикла можно включить в список команд1, завершив его командой проверки условия, а номинальное тело (заключенное между ключевыми словами do и done) сделать пустым.

 

Циклическое исполнение со списком значений

Оболочка предоставляет также возможность организации циклического исполнения с переменной, пробегающей список значений (рис. 13).

                                                                   Рис. 13

Рис. 14

Список_команд будет исполнен по одному разу для каждого значения в списке значений (рис. 14). Если ключевое слово in и список значений не указаны, переменная будет пробегать список значений специальных переменных $1 — $9, соответствующих аргументам командной строки (см. ниже) (в порядке их следования), из которой запущен сценарий (программа).

 

Многовариантное условное исполнение

Для многовариантного условного исполнения в зависимости от значения строковой переменной оболочка поддерживает оператор case с на редкость причудливым синтаксисом (рис. 15).

                                                     Рис.15

Указанная строка (обычно — результат раскрытия значения переменной) поочередно сравнивается с шаблонами и при первом совпадении выполняется соответствующий список команд (до конструкции ";;"), после чего выполнение оператора case завершается.

Строки, указанные в качестве шаблонов, подвергаются обычному раскрытию, за исключением того, что специальные символы "*", "?", "[ ... ]" не приводят к поиску файлов, а используются (по тем же правилам, что и при раскрытии шаблонов имен файлов) как метасимволы при сравнении.

Чаще всего многовариантное условное исполнение применяется при разборе списка параметров, с которыми сценарий был вызван для исполнения. Примеры настолько громоздки, что мы их опускаем.

 

Ввод-вывод

Вывод значений переменных и выражений сценарием, как правило, осуществляется известной нам командой echo или командой printf (предназначенной для форматированного вывода), которую мы в этом курсе не рассматриваем.

Ввести данные (т.е. присвоить значения переменным) можно с помощью команды read. Эта команда читает из стандартного ввода строку, разделяет ее на отдельные аргументы (пробелами, табуляцией, знаком переноса или символами, содержащимися в переменной окружения $IF) и присваивает их перечисленным в команде переменным. Если аргументов оказывается больше, чем переменных, остаток строки присваивается последней переменной (рис. 16).

 

                                                     Рис. 16

Однако во многих случаях вывод (и ввод) осуществляется не только командой echo, но и прочими командами, применяемыми в сценарии. Следует понимать, что каждая команда, ввод-вывод которой не переназначен явно и не включен в конвейер, наследует в числе прочих атрибутов дескрипторы стандартных ввода-вывода от подающего ее процесса. Таким образом, переназначив, например, стандартный вывод сценария в файл, мы тем самым переназначаем стандартный вывод каждой вызываемой программы, если только ее вывод не переназначается отдельно или не передается по конвейеру.

 

Передача аргументов сценарию

Как и любая команда, сценарий, написанный на языке оболочки, может вызываться с позиционными параметрами (ключами и операндами).

Сценарию эти параметры доступны посредством ряда специальных  переменных, перечисленных в таблице на рис. 17.

Рис. 17

Стандарт не предоставляет каких-либо средств поддержки грамматического разбора командной строки; обычно для этого используется комбинация операторов for и операторов условного исполнения.

 

Функции и вызов других сценариев

В сценарии на языке оболочки можно определить подпрограммы-функции. В функции определенными являются переменные, определенные сценарием (или его окружением) на момент вызова функции. Однако сама функция должна быть объявлена до ее вызова. Кроме того, функции при вызове можно передавать параметры (как любой команде), которые она может адресовать способом, указанным выше (рис. 17).

Определение функции имеет вид, указанный на рис 18.

                                                            Рис. 18

 

 

                                                               Рис. 19

Вызывается функция простым указанием ее имени в качестве  команды (см. рис. 19).

 

Раскрытие команды

Крайне занимательной является возможность раскрыть целую команду, то есть включить в командную строку ее стандартный вывод. Для этого команда заключается в обратные апострофы "`" и "`", которые не следует путать ни с одиночными прямыми апострофами, ни с кавычками.

 

                                                  Рис. 20

В примере на рис. 20 команда echo подается с тремя операндами, первый и последний из которых являются текстовыми константами, а второй — результатом раскрытия команды ls *~ * .tmp, то есть списком файлов, соответствующих первому и второму шаблонам, указанным в качестве операндов команды ls.

 

Включенный документ

В некотором смысле обратной по отношению к раскрытию команды с помощью обратных апострофов является конструкция включенного документа (или конструкция "документ здесь").

На рис. 21 представлена конструкция вида "<<цепочка", внешне похожая на перенаправление ввода команды из файла. Однако цепочка представляет собой не имя файла, а ограничивающую цепочку символов, а сам ввод осуществляется непосредственно из файла сценария (или в данном случае с терминала) до тех пор, пока очередная строка не совпадет с ограничивающей цепочкой.

                                                                   Рис. 21

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

 

 

 

Примеры

 

WHILE – DO –DONE

Рассмотрим скрипт myscript

 

i=0

while [ $I –lt 3 ]

do

     echo “$i

     i=`expr $i + 1`

done

 

0

1

2

 

 

 

UNTIL – DO – DONE

Нарпимер:

 

i=0

until [ $I –gt 3 ]

do

     echo “$i

     i=`expr $i + 1`

done

 

0

1

2

 

 

БЕСКОНЕЧНЫЕ ЦИКЛЫ С WHILE И UNTIL

 

while true

do

     echoнавсегда

done

 

 

until false

do

     echoи снова навсегда

done

 

CASE

 

echo ‘Что вы хотите делать ?’

read answer

case “$answer” in

     есть)

         echoOK, съешьте бутерброд”

         ;;

     спать)

         echo “Тогда спокойной ночи”

         ;;

     *)

         echo “Я не понимаю, что вы хотите сделать”

         echo “Надеюсь увидимся завтра”

         ;;

esac

 

 

Команда expr

 

$ expr 7 + 3

10

$ expr ‘(‘ 7 + 3 ‘)’ ‘*’ 14

140

$ expr length ABCDEFG

7

$ expr 15 ‘>’ 16

0

 

Исполняемые файлы

            Для того, чтобы создать shell-скрипт, просто поместите команды bash в файл, как будто если бы вы набирали их в командном процессоре. Чтобы запустить скрипт, у вас есть три способа.

            Добавить в начале файла строку #!/bin/bash и сделать файл исполняемым. Это самый распространённый способ запуска скриптов. Добавьте строку:

            #!/bin/bash

в самом начале файла скрипта. Это должна быть самая первая строка файла, выровненная влево. После этого сделайте файл исполняемым.

            $ chmod +x myscript

            Опционально поместите его в вашу директорию поиска. Затем запустите его как любую другую команду.

            $ myscript

            Если скрипт находится в вашей текущей директории, но текущая директория “.” не является директорией поиска, то вам нужно будет приписать вначале “./”, чтобы командный процессор мог найти скрипт.

            $ ./myscript

            Текущая директория поиска, как правило, не является вашей директорией поиска по соображениям безопасности.

            Передать в качестве аргумента команде bash.

            Команда bash интерпретирует свой аргумент как имя скрипта и запускает его.

            $ bash myscript

            Запустить в текущем командном процессоре с командой “.”.

            Предыдущие методы запускают ваш скрипт как независимый процесс, который не влияет на ваш текущий командный процессор. Если вы хотите, чтобы ваш скрипт вносил изменения в ваш командный процессор, его можно запустить в текущем командном процессоре с командой “.”.

            $ .myscript

 

Передача аргументов

            Shell-скрипты могут принимать командные аргументы и опции точно так же, как и команды Linux. В пределах своего shell-скрипта вы можете обращаться к этим аргументам с помощью переменных $1, $2, $3 и т.д.

            $ cat >myscript

     #!/bin/bash

     echo “Меня зовут $1 и я живу в $2”

     ^D

     $ ./myscript Кирилл Москва

     Меня зовут Кирилл и я живу в Москве

 

Ваш скрипт может проверять количество полученных аргументов с помощью переменной $#.

If [ $# -lt 2 ]

Then

     Echo “$0 ошибка: вы должны задать 2 аргумента”

Else echo “меня зовут $1 и я живу в $2”

fi

 

[Курс] [Помощь]