учебники, программирование, основы, введение в,

 

Язык shell

Основные понятия языка shell
В дальнейшем изложении слово shell будет употребляться в двух смыслах - как имя языка программирования и как название командного интерпретатора.
Для нас shell важен прежде всего как мощный язык мобильного программирования. Предваряя дальнейшее изложение, отметим имеющиеся в нем возможности комбинирования команд с помощью конвейеров, подстановки значений переменных и результатов выполнения команд, генерации имен файлов по шаблонам.
Свойства shell как интерактивного командного интерпретатора, непосредственно взаимодействующего с пользователем, хранение и использование истории сеанса, возможности редактирования командной строки и т.п., на ваш взгляд, менее актуальны, поскольку современный пользовательский интерфейс строится на иной основе.
Выделим основные понятия языка shell на лексическом уровне.
Под пробелом в дальнейшем понимается не только собственно пробел, но также и символ табуляции.
Слово - это лексема, отличная от знака операции.
Имя - последовательность букв, цифр, символов подчеркивания, начинающаяся с буквы или подчеркивания.
Параметр - имя, цифра или любой из символов *, @, #, ?, -, $, !.
Комментарий - лексема, начинающаяся с символа #, а также вся последующая часть строки.
На синтаксическом уровне различаются несколько видов команд.
Простая команда - последовательность полей с разделителями (обычно пробелами) между ними. Первое поле определяет имя команды, которая будет выполняться; оставшиеся поля, за исключением присваиваемых параметрам и перенаправления ввода/вывода (см. далее), передаются команде в качестве аргументов. Имя команды передается как аргумент 0.
Значение простой команды - ее код завершения.
Команда - это либо простая команда, либо одна из управляющих конструкций (см. далее). Кодом завершения команды является код завершения последней выполненной простой команды.
Конвейер - последовательность команд, разделенных знаком |. Стандартный вывод всех команд, кроме последней, направляется на стандартный ввод следующей команды конвейера. Каждая команда выполняется как самостоятельный процесс; shell ожидает завершения последней команды, код завершения которой становится кодом завершения конвейера. Формально будем считать простую команду частным случаем конвейера.
Список - последовательность из одного или нескольких разделенных символами ;, &, && или || конвейеров, она может заканчиваться символами ; или &. Из четырех указанных операций ; и & имеют равные приоритеты, меньшие, чем у && и ||. Приоритеты последних также равны между собой. Символ ; означает, что конвейеры будут выполняться последовательно, а & - параллельно (т. е. shell не ожидает завершения конвейера). Операция && (||) означает, что список, следующий за ней, будет выполняться лишь в том случае, если код завершения предыдущего конвейера нулевой (ненулевой). В списке в качестве разделителя конвейеров вместо символа ; можно использовать символ перевода строки.
Командная строка - строка текста на языке shell.
Shell-процедура - файл, содержащий программу на языке shell.
Для выполнения (почти) каждой простой команды shell порождает отдельный процесс, в рамках которого выполняется программа, хранящаяся в файле, заданном именем команды. Программа может быть выполнимой, т. е. содержать машинные инструкции, или представлять собой shell-процедуру - содержать текст на языке shell.
Далее при описании синтаксиса конструкций языка shell и способа вызова служебных программ будут использоваться следующие соглашения:

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

Конвейеры и примеры их использования
Конвейер - одна из самых красивых конструкций ОС Unix, вошедшая, разумеется, и в стандарт POSIX. Идея его проста, но на редкость продуктивна. С помощью конвейеров удается комбинировать возможности, предоставляемые разными командами, получая по существу новое качество.
Например, утилита ls не подсчитывает число файлов в каталоге, а лишь выдает информацию о них. С другой стороны, служебная программа wc способна подсчитать число строк в файле, но не имеет отношения к распечатке содержимого каталогов. Если же построить конвейер из двух упомянутых команд, количество файлов в каталоге легко вычисляется. Например, результатом работы конвейера на нашей установке ОС Linux будет число 92 (утилита wc, вызванная без аргументов, обрабатывает файл стандартного ввода, который в данном случае является результатом работы команды ls). Значит, в каталоге /bin 91 файл, если считать и элементы, соответствующие текущему и вышележащему каталогам (первая строка выдачи ls содержит суммарное число блоков, занятых файлами каталога).
ls -al /bin | wc -l

Листинг 2.1. Пример конвейера.
Еще один пример. Пусть необходима информация о файлах текущего каталога, модифицированных в октябре. К цели ведет конвейер, показанный в:
ls -al | grep "Oct "

Листинг 2.2. Еще один пример конвейера.
Служебная программа grep играет здесь роль фильтра, который пропускает для вывода только часть строк, выдаваемых ls.
Можно выстроить и трехступенчатый конвейер, если требуется подсчитать число файлов, модифицированных в октябре (см.):
ls -al | grep "Oct " | wc -l

Листинг 2.3. Пример трехступенчатого конвейера.
Здесь утилиту grep с еще большим правом можно назвать фильтром.
Приведем еще один пример конвейера, полезного, когда нужна подробная информация о большом каталоге :
ls -Rl /dev | more

Листинг 2.4. Конвейер для поэкранного просмотра результатов.
Связующее звено между последовательными компонентами конвейера называется каналом. Иными словами, для интерпретации конвейера shell создает временный файл типа "канал", с одного конца в него заносят данные, а с другого - читают. С помощью служебной программы tee можно организовать ответвление канала, т. е. помещать информацию не только на стандартный вывод, но и в указанные файлы:
tee файл ...

Например, если нужно не только подсчитать число файлов из текущего каталога, модифицированных в октябре, но и поместить информацию о них в файл для последующего анализа, следует построить четырехступенчатый конвейер (см.
ls -al | grep "Oct" | tee /tmp/tmpinf | wc -l

Листинг 2.5. Четырехступенчатый конвейер.
В результате его выполнения на экране появится сообщение о количестве нужных файлов, а в файле /tmp/tmpinf - информация о них.

http://localhost:3232/img/empty.gifПеременные и аргументы shell-процедур

Переменные обозначаются именами. Значения могут присваиваться им привычным способом, то есть посредством команд вида:
имя=значение [имя=значение] ...
Все значения в языке shell трактуются как текстовые. Подчеркнем, что, в соответствии с этими требованиями, конструкция
имя=значение
должна представлять собой одно слово - в ней не может быть пробелов.
Обычно в языках программирования ясно из контекста, где подразумевается имя переменной, а где значение. Так, в левой части оператора присваивания обычно используется имя, в правой - значение. В shell все не так. Переход от имени переменной к значению помечается посредством явной операции $. Если в команде встречается конструкция
$имя
то вместо нее интерпретатор shell подставляет значение переменной с указанным именем. Допускается и запись
${имя}
с тем же смыслом, если нужно отделить имя от последующего текста.
Рассмотрим пример. После выполнения команд - утилита echo (эхо) выдает на стандартный вывод свои аргументы - на экране появится результат вывода значений переменных .
a=value_of_variable
b=1+2
echo a = $a
echo b = $b
Листинг 2.6. Присваивание и извлечение значение переменных.
a = value_of_variable
b = 1+2
Листинг 2.7. Результат вывода значений переменных.
Значения формальных аргументов shell-процедур обозначаются как
$цифра
$0 - это имя интерпретируемой shell-процедуры. Если заданных при вызове команды фактических аргументов меньше, чем 9, "лишние" формальные аргументы получают пустые значения. О том, как добраться до фактических аргументов с номерами большими, чем 9, будет сказано ниже (см. управляющую конструкцию for и команду shift).
В качестве примера рассмотрим shell-процедуру, которая выдает на стандартный вывод свое имя и значения трех первых аргументов .
echo Имя команды: $0
echo Значение первого аргумента: $1
echo Значение второго аргумента: $2
echo Значение третьего аргумента: $3
Листинг 2.8. Пример shell-процедуры.
Пусть приведенный текст помещен в файл с именем three_args. Тогда после выполнения команды на экране появится ее результат .
three_args arg1 . - arg4
Листинг 2.9. Пример вызова shell-процедуры с аргументами.
Имя команды: three_args
Значение первого аргумента: arg1
Значение второго аргумента: .
Значение третьего аргумента: -
Листинг 2.10. Результат выполнения shell-процедуры.
Поскольку в тексте shell-процедуры упомянуты только первые 3 аргумента, значения аргументов с большими номерами (даже если они заданы, как в приведенном примере) не влияют на ее работу.
Команда
three_args arg1 --
выдаст другой результат :
Имя команды: three_args
Значение первого аргумента: arg1
Значение второго аргумента: --
Значение третьего аргумента:
Листинг 2.11. Еще один результат выполнения shell-процедуры.
Прежде чем начнется выполнение командной строки, т. е. будет вызвана заданная в ней команда, строку обрабатывает shell, выполняющий в ней некоторые подстановки. С одной из них - значения переменной вместо конструкции $имя - мы уже познакомились. Язык shell содержит ряд аналогичных конструкций. Рассмотрим их.
Там, где в командной строке встречается запись вида
${имя:-слово}
вместо нее подставляется значение переменной с указанным именем, если это значение непусто, в противном случае подставляется слово. Например, при наличии в shell-процедуре присваивания
initdir=${1:-/}
переменная initdir получит значение первого аргумента, если оно непусто. Если же процедуру вызвали без аргументов, значением initdir станет символ /. Подобные конструкции - удобный способ использования подразумеваемых значений.
Запись вида
${имя:=слово}
влечет присваивание нового значения (слово) переменной с заданным именем, только если ее старое значение не установлено или пусто (удобный способ присваивания переменным подразумеваемых значений). Вместо всей конструкции в командную строку подставляется итоговое (непустое старое или присвоенное новое) значение переменной.
Конструкция
${имя:?слово}
предназначена для выдачи в стандартный протокол сообщения об ошибке, если значение переменной с заданным именем не установлено или пусто, после чего неинтерактивный shell завершает работу с ненулевым кодом возврата. Непустое значение подставляется в командную строку, и интерпретация командного файла продолжается обычным образом.
Вместо конструкции
${имя:+слово}
подставляется слово, если значение переменной с заданным именем непусто; в противном случае не подставляется ничего. Такой способ удобен для использования альтернативных значений.
Если в приведенных выше конструкциях опустить двоеточие, будет отменена проверка непустоты значения (останется лишь проверка того, установлено ли значение переменной с заданным именем. В остальном смысл конструкций остается прежним.
Shell содержит базовые средства для обработки цепочек символов. Так, вместо конструкции
${#имя}
подставляется число символов в значении переменной с заданным именем.
Предоставляется четыре конструкции для сопоставления с образцом:
${имя%слово}
${имя%%слово}
${имя#слово}
${имя##слово}
Во всех случаях слово рассматривается как образец (шаблон, см. далее); после сопоставления с ним подставляется значение переменной с заданным именем, из которого удален минимальный (максимальный) сопоставленный суффикс (префикс).
Приведем несколько примеров. Для их понимания достаточно знать, что шаблон * сопоставляется с произвольной цепочкой символов, в том числе с пустой.
После присваивания переменной
HOME=/home/galat
команда
echo ${#HOME}
выдаст 11.
Заменить в имени файла суффикс .f на .for можно с помощью команд, показанных в:
f=file.f
mv $f ${f%.f}.for
Листинг 2.12. Пример сопоставления с образцом.
Выдать первый элемент маршрутного имени файла (см. далее) можно с помощью команд, показанных в:
f=маршрутное_имя
echo ${f%%/*}
Листинг 2.13. Второй пример сопоставления с образцом.
Последний элемент выдается командой, приведенной в:
echo ${f##*/}
Листинг 2.14. Третий пример сопоставления с образцом.
Служебные переменные языка shell
Значения некоторых переменных устанавливаются самим языком shell. Перечислим эти переменные и опишем их предназначение.
#
Количество фактических аргументов (десятичное).
-
Флаги (однобуквенные опции), указанные при запуске shell или установленные посредством команды set (см. далее).
?
Десятичное значение, возвращенное предыдущей синхронно выполненной командой.
$
Идентификатор процесса, в рамках которого выполняется shell.
!
Идентификатор последнего асинхронно запущенного процесса.
*, @
Совокупность всех фактических аргументов (начиная с $1), разделенных пробелами.
Напомним: чтобы получить значения этих переменных, перед ними нужно поставить знак $.
Значения $@ и $* имеют некоторые тонкие различия, на которых мы, однако, останавливаться не будем.
Несколько усложним процедуру three_args, чтобы продемонстрировать только что описанные возможности .
echo Идентификатор текущего процесса: $$
echo Имя команды: $0
echo Число фактических аргументов: $#
echo Совокупность всех аргументов: $@
echo Значение первого аргумента: $1
echo Значение второго аргумента: $2
echo Значение третьего аргумента: $3
Листинг 2.15. Усовершенствованная shell-процедура three_args.
Теперь, вызывая усовершенствованную процедуру three_args, выполним командную строку
three_args arg1 . - arg4
Тогда на экране появится примерно следующее :
Идентификатор текущего процесса: 3882
Имя команды: three_args
Число фактических аргументов: 4
Совокупность всех аргументов: arg1 . - arg4
Значение первого аргумента: arg1
Значение второго аргумента: .
Значение третьего аргумента: -
Листинг 2.16. Результат вызова усовершенствованной процедуры three_args.
Окружение процессов
Окружение - это набор пар (имя, значение), который передается выполняемой программе так же, как и обычный список аргументов. Иными словами, порождаемые процессы наследуют окружение процесса-предка. Компонентами окружения являются, помимо прочих, следующие переменные и их значения:
HOME
Подразумеваемый аргумент утилиты смены текущего каталога cd - домашний каталог пользователя.
IFS
Цепочка символов, где перечислены разделители полей; обычно включает пробел, табуляцию и перевод строки.
PATH
Список имен каталогов для поиска команд. В дальнейшем подобные списки называются списками поиска. Элементы списка разделяются двоеточием. Пустой элемент означает текущий каталог.
PS1
Основное приглашение интерактивного языка shell (по умолчанию "$").
TERM
Тип пользовательского терминала.
TZ
Информация о часовом поясе.
Для отсылки информации об окружении на стандартный вывод следует воспользоваться командой
env
Поясним подробнее смысл некоторых переменных окружения.
Прежде чем выполнить команду, shell ищет файл с соответствующим именем в последовательности каталогов, являющейся значением переменной PATH. Назовем подобную последовательность списком поиска. Если, например, значение $PATH суть
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin
то нужный файл будет сначала разыскиваться в каталоге /usr/local/bin, затем в /bin и т.д. Как только файл отыщется, поиск прекратится. Это важно, если в разных каталогах есть одноименные выполнимые файлы.
Значение $IFS влияет не только на разбор команд, но и на чтение строк данных с помощью команды read (см. далее). Кроме того, первый символ из значения $IFS вставляется между фактическими аргументами при выполнении подстановки $*.
Переменная TERM хранит тип терминала пользователя. Интерактивные утилиты (редакторы или другие программы с экранным интерфейсом, например talk) с помощью значения $TERM настраиваются на конкретный тип терминала.
Переменная TZ задает локальный часовой пояс. Эта информация необходима всегда, когда требуется показать текущее время. Особенно полезна она при почтовом взаимодействии с территориально удаленными (в частности, зарубежными) пользователями.
Для изменения окружения мало присвоить новое значение соответствующей переменной. Дело в том, что по умолчанию переменные считаются локальными по отношению к shell-процедуре, т. е. присваивание изменит локальную переменную, но не затронет одноименную переменную окружения. Таким образом, в окружение новых процессов (порожденных, например, для выполнения последующих команд данной shell-процедуры) войдет переменная со старым значением.
С помощью конструкции
export  имя[=слово]
переменная с указанным именем и значением (последнее может быть опущено) помещается в окружение, т. е. становится глобальной.
Следующая строка иллюстрирует типичное применение команды export:
export PATH=/local/bin:$PATH
Переменная PATH изменилась, и новое значение экспортировано в окружение.
Команда
export -p
выдает на стандартный вывод имена и значения всех экспортированных переменных. Эту выдачу можно использовать для сохранения и последующего восстановления (быть может, с некоторыми модификациями) окружения. Ниже приведен фрагмент возможного результата работы команды export -p .
export HISTSIZE="1000"
export HOME="/home/galat"
export LANG="C"
export LESSCHARSET="koi8-r"
export LOGNAME="galat"
export MAIL="/var/spool/mail/galat"
export TTY="/dev/ttyS4"
export USER="galat"
Листинг 2.17. Возможные результаты выполнения команды export -p.
Мы видим, что выдача устроена так, чтобы вновь быть поданной на вход командного интерпретатора.

http://localhost:3232/img/empty.gifhttp://localhost:3232/img/empty.gifПодстановка результатов выполнения команд

Если в командной строке встретилась цепочка символов, заключенная в обратные кавычки (`), она интерпретируется как команда, стандартный вывод которой подставляется вместо упомянутой конструкции. Говорят, что в этом случае производится подстановка результатов выполнения команды, а сами обратные кавычки называют символами подстановки.
Эквивалентной синтаксической формой для подстановки результата выполнения команды является конструкция вида
$(команда)
Для выполнения заданной команды порождается процесс, в рамках которого работает еще один экземпляр командного интерпретатора.
Опишем несколько употребительных способов использования подстановки результатов. Пусть файл filelist содержит список имен других файлов и над совокупностью последних требуется проделать некоторое действие. Если в командную строку поместить конструкцию
... `cat filelist` ...
то это аналогично явному указанию в командной строке на все файлы из списка. (Утилита cat выдает на стандартный вывод содержимое файлов, заданных в качестве аргументов.) Пример подстановки результатов выполнения команды (выдача информации о файлах):
ls -l `cat filelist`
Нет нужды в том, чтобы каждая служебная программа умела читать свои аргументы из файла: имеются универсальный механизм подстановки результатов и команда cat.
Подстановка результатов выполнения команд может сочетаться с другими видами подстановок, например, значения вместо имени переменной. Так, после обработки конструкции
${x:-$(ls)}
команда ls выполнится только тогда, когда значение x не установлено или пусто; в противном случае выполнится команда $x, и ее результат займет место в командной строке.
В языке shell все значения считаются текстовыми. Значит, для выполнения операций с числами нужны особые средства. Вместо конструкции вида
$((выражение))
shell подставит результат вычисления этого выражения, что (с некоторой натяжкой) можно рассматривать как частный случай подстановки результатов выполнения команд. Например, после обработки строки
i=$(($i+1))
значение i (если оно было числом) увеличится на единицу.
Стандарт POSIX обязывает поддерживать арифметику длинных целых со знаком; константы (десятичные, восьмеричные, шестнадцатеричные) должны удовлетворять требованиям языка C.
Управляющие конструкции
Среди прочих язык shell содержит следующие управляющие конструкции.
Оператор цикла for:
for имя [in слово ...]
do список
done
При каждой итерации переменная имя принимает следующее значение из набора
in слово  ...
Если конструкция in слово ... опущена, то список выполняется для каждого формального аргумента.
Условный оператор:
if список_1
then список_2
[elif список_3
then список_4]
...
[else список_5]
fi
Выполняется список_1. Если код его завершения 0, то выполняется список_2, в противном случае - список_3, и если код его завершения 0, то выполняется список_4 и т.д. Если же коды завершения всех списков, использованных в качестве условий, оказались ненулевыми, выполняется else-часть (список_5). Если else-часть отсутствует и ни одна then-часть не выполнялась, возвращается нулевой код завершения.
Оператор цикла while (until):
while список_1
do список_2
done
Пока код завершения последней команды списка_1 есть 0, выполняются команды списка_2. При замене служебного слова while на until условие продолжения цикла меняется на противоположное. Если команды из списка_2 не выполнялись вообще, код завершения устанавливается равным нулю.
Оператор выбора:
case слово in
[шаблон [| шаблон] ...) список ;;]
...
esac
Выполняется список, соответствующий первому из шаблонов, успешно сопоставленных со словом. Формат шаблона аналогичен используемому для генерации имен файлов (см. далее).
Определение функции:
имя () открывающая_скобка
список
закрывающая_скобка
Определяется функция с заданным именем. Список является ее телом, которое окружают круглые или фигурные скобки. Для вызова функции используется обычный синтаксис команды:
имя  [аргумент ...]
Если тело функции заключено в фигурные скобки, она выполняется в рамках текущего процесса; в случае круглых скобок порождается отдельный процесс. На время выполнения функции аргументы $1, $2, ..., а также служебная переменная # получают новые значения, определенные аргументами команды вызова. Затем восстанавливаются старые значения.
Приведем примеры использования управляющих конструкций. Сначала усовершенствуем процедуру three_args, чтобы она выдавала значения всех, а не только первых трех аргументов :
echo Идентификатор текущего процесса: $$
echo Имя команды: $0
echo Число фактических аргументов: $#
echo Совокупность всех аргументов: $@
i=1
for arg
do
echo Значение аргумента номер ${i}: $arg
i=$(($i+1))
done
Листинг 2.18. Еще одно усовершенствование shell-процедуры three_args.
В управляющих конструкциях if и while часто используется команда test, она проверяет некоторое условие и вырабатывает нулевой или ненулевой код завершения в зависимости от того, оказалось ли условие, соответственно, истинным или ложным. Команда записывается любым из двух способов:
test  условие
или употребив пару квадратных скобок:
[ условие ]
Условие может включать, помимо прочих, следующие примитивы:
-r файл
Истина, если файл существует и доступен для чтения.
-w файл
Истина, если файл существует и доступен для записи.
-x файл
Истина, если файл существует и является выполнимым.
-f файл
Истина, если файл существует и является обычным файлом.
-d файл
Истина, если файл существует и является каталогом.
-c файл
Истина, если файл существует и является специальным символьным файлом.
-b файл
Истина, если файл существует и является специальным блочным файлом.
-p файл
Истина, если файл существует и является именованным каналом.
-s файл
Истина, если файл существует и имеет ненулевой размер.
-n s1
Истина, если цепочка символов s1 имеет ненулевую длину.
s1 = s2
Истина, если цепочки символов s1 и s2 равны.
s1 != s2
Истина, если цепочки символов s1 и s2 не равны.
n1 -eq n2
Истина, если целые числа n1 и n2 алгебраически равны. На месте -eq могут быть также операции сравнения -ne (не равно), -gt (больше), -ge (больше или равно), -lt (меньше), -le (меньше или равно).
Примитивы комбинируются с помощью круглых скобок, а также следующих операций (в порядке уменьшения приоритета):


!

Унарная операция отрицания

-a

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

-o

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

Обратим внимание на то, что квадратные скобки, обрамляющие условие, и каждый компонент условия должны быть выделены пробелами.
Приведем пример использования управляющей конструкции if. В процессе загрузки практически любой разновидности ОС Unix выполняются строки следующего или близкого вида :
if [ -s ${f} ]
then
/bin/sh ${f} start
fi
Листинг 2.19. Пример условного оператора.
Если файл, имя которого является значением переменной f, существует и имеет ненулевой размер, он выполняется с аргументом start.
В качестве примера употребления конструкции case приведем еще один фрагмент, типичный для процесса загрузки системы:
case "$1" in
start)
start
;;
stop)
stop
;;
reload | restart)
restart
;;
condrestart)
if [ -f /var/lock/subsys/atd ]
then
restart
fi
;;
*)
echo $"Usage: $0 {start | stop | restart | condrestart}"
exit 1
esac
Листинг 2.20. Пример оператора выбора.
Известные значения первого аргумента распознаются, в ответ на все прочие (шаблон *) сообщается, как пользоваться данной shell-процедурой.
Следующий пример иллюстрирует определение и вызов функции .
echo $# $1
f ( ) {
echo $# $1
}
f a b
f b
echo $# $1
Листинг 2.21. Пример определения и вызова функции.
Если данный текст хранится в файле g, то по окончании выполнения команды (вызов shell-процедуры, содержащей функцию)
g c d e
будет выдан следующий результат:
3 c
2 a
1 b
3 c

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика