10. Написание сценариев командного интерпретатора


Что такое сценарий?

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

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

Доступные командные интерпретаторы

В операционной системе QNX Neutrino чаще всего используется командный интерпретатор ksh, который является общедоступной реализацией командного интерпретатора Korn. Команда sh обычно представляет собой символьную ссылку на командный интерпретатор ksh. Более подробные сведения об этом интерпретаторе см. в:
Операционная система QNX Neutrino также предоставляет или использует несколько других сред исполнения сценариев.
В целом сценарии командного интерпретатора наиболее полезны и мощны при запуске программ и изменении файлов в контексте файловой системы, а утилиты sed, gawk и perl предназначены в первую очередь для работы с содержимым файлов. Дополнительные сведения см. в следующих источниках:

Запуск сценария командного интерпретатора

Сценарий командного интерпретатора можно запустить следующими способами:

sh myscript

. myscript

chmod 744 myscript

./myscript


В этом примере текущий командный интерпретатор вызывает новый командный интерпретатор для исполнения сценария.


Первая строка


Первая строка многих — если не большинства — сценариев имеет следующую форму:

#! interpreter [arg]

Например, сценарии Korn shell начинаются с такой строки:

#! /bin/sh

Эта строка начинается с символа #, который указывает на то, что она является комментарием и должна игнорироваться командным интерпретатором, который обрабатывает сценарий. Первая пара символов, #!, не имеет значения для командного интерпретатора, но код загрузчика в модуле procnto распознает их как команду загрузить следующий за ними исполняемый файл, /bin/sh, и передать ему:
  1. Путь к данному интерпретатору;

  2. Опциональные аргументы, заданные в первой строке сценария;

  3. Путь к сценарию;

  4. Аргументы этого сценария, заданные в качестве командно-строковых параметров.

Например, сценарий называется my_script и пользователь вызывает его следующей командой:

./my_script my_arg1 my_arg2 ...


В этом случае procnto загружает:

interpreter [arg] ./my_script my_arg1 my_arg2 ...

Примечание.

Интерпретатор не может быть иным, нежели заданный #!.

Ядро игнорирует атрибуты setuid и getuid у сценария; дочерний процесс будет иметь такие же идентификаторы пользователя и группы, что и родительский процесс. (Для получения дополнительной информации см. раздел “Setuid и setgid” в разделе “Работа с файлами”.)

Некоторые интерпретаторы корректируют список получаемых аргументов:

ksh удаляет себя из списка аргументов;

gawk заменяет свое путевое имя просто на имя gawk“;

perl удаляет из аргументов себя и имя сценария, и добавляет имя сценария в переменную $0.


Для примера рассмотрим простые сценарии, печатающие полученные ими аргументы.


Аргументы сценария ksh

Пусть имеется следующий сценарий ksh_script:

#! /bin/sh

echo $0

for arg in "$@" ; do

echo $arg

done

Если вызвать его с помощью команды «./ksh_script one two three», то загрузчик вызовет его как «/bin/sh ./ksh_script one two three», а затем ksh удалит себя из списка аргументов. Вывод будет иметь следующий вид:


./ksh_script

one

two

three


Аргументы сценария gawk

Рассмотрим версию предыдущего сценария для интерпретатора gawk с именем gawk_script, который будет выглядеть следующим образом:


#!/usr/bin/gawk -f

BEGIN {

for (i = 0; i < ARGC; i++)

print ARGV[i]

}


Аргумент -f важен, т.к. он указывает, что бы утилита gawk прочитала сценарий из заданного файла. Без опции -f данный сценарий не будет работать ожидаемым образом.

Если этот сценарий запустить командой «./gawk_script one two three», то загрузчик вызовет его как «/usr/bin/gawk -f ./gawk_script one two three», а затем gawk заменит полное путевое имя на gawk. Вывод будет иметь следующий вид:

gawk

one

two

three


Аргументы сценария perl

Версия рассматриваемого сценария на языке perl будет выглядеть следующим образом:

#! /usr/bin/perl

for ($i = 0; $i <= $#ARGV; $i++) {

print "$ARGV[$i]\n";

}


Если этот сценарий запустить командой «./perl_script one two three» то загрузчик вызовет его как «/usr/bin/perl ./perl_script one two three», а затем perl удаляет себя и имя сценария из списка аргументов. Вывод будет иметь следующий вид:

one

two

three


Пример сценария командного интерпретатора Korn

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

#!/bin/sh

#

# tfind:

# сценарий, который ищет строки в различных файлах и выводит

# эти строки с помощью утилиты less


case $# in

1)

find . -name '*.[ch]' | xargs grep $1 | less

exit 0 # успешное завершение

esac

echo " Введите команду tfind строка_для_поиска, "

echo " где строка_для_поиска — искомая строка "

echo " "

echo " Например, команда tfind console state просматривает все файлы"

echo " в текущем каталоге и его подкаталогах и отображает все "

echo " экземпляры фразы console state. "

exit 1 # неудачное завершение


Как было описано выше, первая строка указывает программу, /bin/sh, для интерпретации сценария.

Несколько следующих строк являются комментариями, которые описывают действия, выполняемые сценарием. Далее следует конструкция:

case $# in

1)

...

esac

Команда case...in является встроенной командой интерпретатора Korn и представляет собой одну из структур ветвления, эквивалентную оператору switch языка C.

Последовательность символов $# является переменной командного интерпретатора. Чтобы обратиться к переменной в командном интерпретаторе, ее имя следует предварить символом $, тогда командный интерпретатор отличит имя переменной от символьной строки. $# — особая переменная командного интерпретатора, которая содержит количество командно-строковых аргументов, переданных сценарию.

Константа 1) является возможным значением переменной ветвления, которое эквивалентно оператору case в языке C. Этот код проверяет, был ли передан командному интерпретатору ровно один параметр.

Строка esac завершает оператор case. В командах case и if для указания конца структуры ветвления используется имя команды, записанное справа налево.

Внутри оператора case имеется конструкция:

find . -name '*.[ch]' | xargs grep $1 | less


Эта строка выполняет несколько действий и включает в себя следующие компоненты:
Эти команды объединены при помощи символа конвейера |. Конвейер — одна из самых мощных возможностей командного интерпретатора; он принимает выходные данные программы, которая указана слева, и передает их в качестве входных данных программе, которая указана справа. Конвейер позволяет строить сложные действия из более простых компонентов. Более подробные сведения см. в разд. "Перенаправление ввода и вывода" раздела 4.

Первый компонент конвейера, find . -name '*.[ch]', использует еще одну мощную и распространенную команду. Большинство файловых систем структурировано в виде рекурсивной иерархии каталогов, а утилита find выполняет рекурсивный поиск в этой иерархии. В этом примере утилита find ищет файлы, которые заканчиваются на .c или на .h (т. е. исходные и заголовочные файлы языка C), и распечатывает их имена.

Групповые символы имен файлов заключаются в одиночные кавычки, поскольку командный интерпретатор интерпретирует их особым образом. В отсутствие кавычек командный интерпретатор раскрывает групповые символы в текущий каталог, однако в этом примере необходимо, чтобы интерпретацию групповых символов выполнила утилита find, поэтому групповые символы скрыты от командного интерпретатора с помощью кавычек. Более подробные сведения см. в разд. "Применение кавычек со специальными символами" раздела 4.

Следующий компонент конвейера, xargs grep $1, выполняет два действия:

find . -name '*.[ch]' -exec grep $i {} | less

Эта команда загружает и запускает программу grep для каждого найденного файла. Команда, которая была использована в примере:

find . -name '*.[ch]' | xargs grep $1 | less

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

Последний компонент конвейера less является утилитой страничного представления вывода. Команда способна сгенерировать большой объем выходных данных, которые выходят за пределы экрана, а утилита less представляет вывод постранично и позволяет перелистывать данные вперед и назад.

Конструкция case также включает в себя команду:

exit 0 # успешное завершение


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

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

echo " Введите команду tfind строка_для_поиска, "

echo " где строка_для_поиска — искомая строка "

echo " "

echo " Например, команда tfind console state просматривает все файлы"

echo " в текущем каталоге и его подкаталогах и отображает все "

echo " экземпляры фразы console state. "

exit 1 # неудачное завершение


Эффективность

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

Рекомендации разработчикам сценариев

Далее приведены некоторые рекомендации, которые следует учитывать при написании сценариев.

chmod a+x имя_сценария

Сценарий не обязан быть исполняемым, если планируется вызывать его путем передачи в качестве аргумента командному интерпретатору:

ksh имя_сценария

или запускать его с помощью команды "точка":

. имя_сценария

˜/bin/my_script