Опубликован: 22.06.2005 | Уровень: для всех | Доступ: платный | ВУЗ: Компания IBM
Лекция 8:

Возможности командной оболочки

< Лекция 7 || Лекция 8: 123456 || Лекция 9 >

Язык программирования sh

Некогда Мефодий выучил несколько языков программирования, и уже собрался было написать кое-какие нужные программы на Си или Python, однако Гуревич его остановил. Большая часть того, что нужно начинающему пользователю Linux, делается с помощью одной правильной команды, или вызовом нескольких команд в конвейере. От пользователя только требуется оформить решение задачи в виде сценария на shell. На самом же деле уже самый первый из командных интерпретаторов, sh, был настоящим высокоуровневым языком программирования - если, конечно, считать все утилиты системы его операторами. При таком подходе от sh требуется совсем немного: возможность вызывать утилиты, возможность свободно манипулировать результатом их работы и несколько алгоритмических конструкций (условия и циклы).

К сожалению, программирование на shell, а также других, более мощных интерпретируемых языках в Linux, остается за рамками нашего курса. Так что, пока Мефодий читает документацию по bash и упражняется в написании сценариев, нам остается только поверхностно рассмотреть свойства shell как языка программирования и интегратора команд. Заметим попутно, что писать сценарии для bash - непрактично, так как исполняться они смогут лишь при помощи bash. Если же ограничить себя рамками sh, совместимость с которым объявлена и в bash, и в zsh, и в ash (наиболее близком по возможностям к sh ), и в других командных интерпретаторах, выполняться эти сценарии смогут любым из sh- подобных интерпретаторов, и не только в Linux.

Интеграция процессов

Каждый процесс Linux при завершении передает родительскому код возврата (exit status), который равен нулю, если процесс считает, что его работа была успешной, или номеру ошибки - в противном случае. Командный интерпретатор хранит код возврата последней команды в специальной переменной " ?". Что более важно, код возврата используется в условных операторах: если он равен нулю, условие считается выполненным, а если нет - невыполненным:

[methody@localhost methody]$ grep Methody bin/script 
echo 'Hello, Methody!'
[methody@localhost methody]$ grep -q Methody bin/script ; echo $?
0
[methody@localhost methody]$ grep -q Shogun bin/script ; echo $?
1
[methody@localhost methody]$ if grep -q Shogun bin/script ; then echo "Yes"; fi
[methody@localhost methody]$ if grep -q Methody bin/script ; then echo "Yes"; fi    
Yes
Пример 8.14. Оператор if использует код возврата программы grep

Условный оператор if запускает команду- условие, grep -q, которая ничего не выводит на экран, зато возвращает 0, если шаблон найден, и 1, если нет. В зависимости от кода возврата этой команды, if выполняет или не выполняет тело: список команд, заключенный между then и fi. Точка с запятой разделяет операторы в sh ; либо она, либо перевод строки необходимы перед then и fi, иначе все, что идет после grep, интерпретатор посчитает параметрами этой утилиты.

Множеством функций обладает команда test: она умеет сравнивать числа и строки, проверять ярлык объекта файловой системы и наличие самого этого объекта. У " test " есть второе имя: " [ " (как правило, /usr/bin/[ - символьная или даже жесткая ссылка на /usr/bin/test), позволяющее оформлять оператор if более привычным образом:

[methody@localhost methody]$ if test -f examples ; then ls -ld examples ; fi
[methody@localhost methody]$ if [ -d examples ] ; then ls -ld examples ; fi
drwxr-xr-x 2 methody methody 4096 Окт 31 15:26 examples
[methody@localhost methody]$ A=8; B=6; if [ $A -lt $B ] ; then echo "$A<$B" ; fi
[methody@localhost methody]$ A=5; B=6; if [ $A -lt $B ] ; then echo "$A<$B" ; fi
5<6
Пример 8.15. Оператор test

Команда test -f проверяет, не является ли ее аргумент файлом; поскольку examples - это каталог, результат будет неудачным. Команда [ -d - то же самое, что и test -d (не каталог ли первый параметр), только последним параметром этой команды - исключительно для красоты - должен быть символ " ] ". Результат - успешный, и выполняется команда ls -ld. Команда test параметр1 -lt параметр3 проверяет, является ли параметр1 числом, меньшим, чем (less than) параметр3. В более сложных случаях оператор if удобнее записывать "лесенкой", выделяя переводами строки окончание условия и команды внутри тела (их может быть много).

Второй тип подстановки, которую shell делает внутри двойных кавычек - это подстановки вывода команды. Подстановка вывода имеет вид " `команда` " (другой вариант - " $(команда) "). Как и подстановка значения переменной, она происходит перед тем, как начнется разбор командной строки: выполнив команду и получив от нее какой-то текст, shell примется разбирать его, как если бы этот текст пользователь набрал вручную. Это очень удобное средство, если то, что выводит команда, необходимо передать самому интерпретатору:

[methody@localhost methody]$ A=8; B=6
[methody@localhost methody]$ expr $A + $B
14
[methody@localhost methody]$ echo "$A + $B = `expr $A + $B`"
8 + 6 = 14
[methody@localhost methody]$ A=3.1415; B=2.718
[methody@localhost methody]$ echo "$A + $B = `expr $A + $B`"
expr: нечисловой аргумент
3.1415 + 2.718 = 
[methody@localhost methody]$ echo "$A + $B" | bc
5.8595
[methody@localhost methody]$ C=`echo "$A + $B" | bc`
[methody@localhost methody]$ echo "$A + $B = $C"
3.1415 + 2.718 = 5.8595
Пример 8.16. Подстановка вывода команды

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

Сценарии

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

[methody@localhost methody]$ cat > bin/two
#!/bin/sh
echo "Running $0: $*"
$1 $3
$2 $3
[methody@localhost methody]$ chmod +x bin/two
[methody@localhost methody]$ bin/two file "ls -ld" examples
Running bin/two: file ls -ld examples
examples: directory
drwxr-xr-x 2 methody methody 4096 Окт 31 15:26 examples
[methody@localhost methody]$ two "ls -s" wc "bin/two bin/loop" junk
Running /home/methody/bin/two: ls -s wc bin/two bin/loop junk
4 bin/loop 4 bin/two
 4 9 44 bin/two
 1 5 26 bin/loop
 5 14 70 итого
Пример 8.17. Использование позиционных параметров в сценарии

Как видно из примера, форма " $номер_параметра " позволяет обратиться и к нулевому параметру - команде, а вся строка параметров хранится в переменной " * ". Кроме того, свойство подстановки выполняться до разбора командной строки позволило Мефодию передать в качестве одного параметра " ls -ld " или " bin/two bin/loop ", а интерпретатору - разбить эти параметры на имя команды и ключи и два имени файла соответственно.

В sh есть и оператор while, формат которого аналогичен if, и более удобный именно в сценариях оператор обхода списка for (список делится на слова так же, как и командная строка - с помощью разделителей ):

[methody@localhost methody]$ for Var in Wuff-Wuff 
Miaou-Miaou; do echo $Var; done
Wuff-Wuff
Miaou-Miaou
[methody@localhost methody]$ for F in $(date); do echo -n "<$F>"; done; echo
<Сбт><Ноя><6><12:08:38><2004>
[methody@localhost methody]$ cat > /tmp/setvar
QWERTY="$1"
[methody@localhost methody]$ sh /tmp/setvar 1 2 3; echo $QWERTY
[methody@localhost methody]$ . /tmp/setvar 1 2 3; echo $QWERTY
1
Пример 8.18. Использование for и операции "."

Во втором for Мефодий воспользовался подстановкой вывода команды date, каждое слово которой вывел с помощью echo -n в одну строку, а в конце команды пришлось вывести один перевод строки вручную.

Вторая половина примера иллюстрирует ситуацию, с которой Мефодий столкнулся во время своих экспериментов: все переменные, определяемые в сценарии, после окончания его работы куда-то пропадают. Оно и понятно: для обработки сценария всякий раз запускается новый интерпретатор ( дочерний процесс!), и все его переменные принадлежат именно ему и с завершением процесса уничтожаются. Таким образом достигается отсутствие побочных эффектов: запуская программу, пользователь может быть уверен, что та не изменит окружения командной оболочки. Однако в некоторых случаях требуется обратное: запустить сценарий, который нужным образом настроит окружение. Единственный выход - отдавать такой сценарий на обработку текущему, а не новому, интерпретатору (т. е. тому, что разбирает команды пользователя). Это делается с помощью специальной команды " .". Если вдруг в передаваемом сценарии обнаружится команда exit, exec или какая-нибудь другая, приводящая к завершению работы интерпретатора, завершится именно текущая командная оболочка, чем сеанс работы пользователя в системе может и закончиться.

< Лекция 7 || Лекция 8: 123456 || Лекция 9 >
Тимур Булатов
Тимур Булатов

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

дмитрий шремзер
дмитрий шремзер

В поле PPID (" p arent p rocess id entificator") указан идентификатор родительского процесса, т. е. процессапородившего данный. Для ps это – bash, а для bash, очевидно, login

А что тогда находится в поле CMD?

Гузель Фахретдинова
Гузель Фахретдинова
Россия
Алексей Маряскин
Алексей Маряскин
Россия