Некогда Мефодий выучил несколько языков программирования, и уже собрался было написать кое-какие нужные программы на Си или 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
Пример 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
Пример 15. Оператор test
Команда test -f
проверяет, на является ли её аргумент файлом; поскольку examples
— это каталог, результат — неуспешный. Команда [ -d
— то же самое, что и test -d
(не каталог ли первый параметр), только последним параметром этой команды — исключительно для красоты — должен быть символ «]
». Результат — успешный, и выполняется команда ls -ld
. Команда test параметр1 -lt параметр3
проверяет, является ли параметр1
числом, меньшим, чем (less then) параметр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
Пример 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 итого
Пример 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><MSK><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
Пример 18. Использование for
и операции «.
»
Во втором for
Мефодий воспользовался подстановкой вывода команды date
, каждое слово которой вывел с помощью echo -n
в одну строку, а в конце команды пришлось вывести один перевод строки вручную.
Вторая половина примера иллюстрирует ситуацию, с которой Мефодий столкнулся во время своих экспериментов: все переменные, определяемые в сценарии, куда-то пропадают после окончания его работы. Оно и понятно: для обработки сценария всякий раз запускается новый интерпретатор (дочерний процесс!), и все его переменные принадлежат именно ему и с завершением процесса уничтожаются. Таким образом достигается отсутствие побочных эффектов: запуская программу, пользователь может быть уверен, что та не изменит окружения командной оболочки. Однако в некоторых случаях требуется обратное: запустить сценарий, который нужным образом настроит окружение. Единственный выход — отдавать такой сценарий на обработку текущему, а не новому, интерпретатору (т. е. тому, что разбирает команды пользователя). Это и делается с помощью специальной команды «.
». Если вдруг в передаваемом сценарии обнаружится команда exit
, exec
или какая-нибудь другая, приводящая к завершению работы интерпретатора, завершится именно текущая командная оболочка, чем сеанс работы пользователя в системе может и закончиться.