Описание: Большинство задач системного администрирования могут быть автоматизарованы с использованием shell-скриптов. Кандидат BSDA должен знать о преимуществах и недостатках использования скриптов Bourne shell более, нежели csh(1) или bash(1). Кандидат должен различать «магическую строку» (shebang), комментарии, позиционные параметры и специальные параметры, маски в шаблонах, знать как правильно использовать кавычки и обратные слеши, операторы for, while, if, case и esec. Кандидат должен знать как сделать скрипт исполнимым и как его отлаживать.
Практика: sh(1), chmod(1)
sh(1) используется не столько как интерактивный shell (надо признать, что sh(1) в работе не так удобен, как его более современные аналоги), сколько как язык программирования используемый для автоматизации рутинных процедур. На sh(1) написано множество скриптов используемых при старте системы, а так же скриптов обслуживающих её функционирование.
В первом приближнии sh(1) позволяет попросту перечислить внешние команды, которые необходимо выполнить. Оданко sh(1) так же обладает возможностью делать проверки, выполнять циклы, обрабатывать исключительные ситуации (перехватывать сигналы) и многими другими возможностями. Не стоит забывать, однако, что sh(1), это всего лишь удобное средство автоматизации. Несмотря на циклы и логику, несмотря на наличие собственной целочисленной арифметики, главное, для чего используется sh(1) — это вызов внешних программ, которые, собственно, и делают основную работу.
С чего начинается скрипт? С некоторой магической строки — shebang. Вообще, последовательность действий операционной системы с файлом при попытке пользователя запустить его на исполнение, выглядит примерно так:
#!
то перед нами «магическая
строка» (shebang). Вся строка от третьего символа до
конца строки является указателем на интерпретатор. Надо
выполнить указанную строку, подав интерпретатору на вход
скрипт. Такая конструкция позволяет запускать интерпретатор с
дополнительными опциями: #!/usr/local/bin/perl -w
или
делать сложные вызовы, указывая специальные переменные
окружения: #!/usr/bin/env -i /usr/local/bin/python
.
Даже несмотря на наличие такого умолчания, разумно указывать
интерпретатор в явном виде. Сценарии sh должны начинаться со
строки #!/bin/sh
.
На вход интерпретатору подаётся весь скрипт целиком, включая
первую строку с shebang. Поэтому скриптовый язык обязан
воспринимать знак #
как символ комментария, как
минимум в первой строке. В этом легко убедится. Пусть у нас есть
скрипт test.cat
состоящий из двух строк:
#!/bin/cat Hello
При попытке выполнить скрипт мы получим:
$
chmod 755 test.cat$
./test.cat #!/bin/cat Hello
Ещё одной особенностью скрипта является то, что он обязан иметь пермиссии не только на исполнение, но и на чтение. В противном случае скрипт не сможет быть направлен на стандартный вход указанному интерпретатору.
В разделе посвящённом переменным окружения обсуждается вопрос использования утилиты env(1) в shebang и вопросы связанные с безопасностью при использовании различных интерпретаторов в «магической строке».
Надо честно признать, повседневно работать в sh(1) неудобно. Да, sh(1) неудобный интерпретатор, ему есть множество альтернатив, которые, к тому же, порой полностью совместимы по синтаксису. Почему же в операционных системах BSD так упорно цепляются за него? Для начала приведу выдержку из FreeBSD FAQ.
Вопрос 7.8: Почему возможности
/bin/sh
так малы? Почему бы во FreeBSD не использовать bash или какой-либо другой командный процессор?Ответ: Потому что в стандарте POSIX сказано, что все командные процессоры должны вести себя так же, как shell.
Более подробный ответ заключается в следующем: многим требуется, чтобы разрабатываемые скрипты для командного процессора были переносимы между многими системами. Именно поэтому в POSIX очень подробно описан командный процессор и набор утилит. Большинство скриптов пишутся на языке процессора Bourne shell, к тому же некоторые важные программные вызовы (make(1), system(3), popen(3) и их аналоги на языках скриптов высокого уровня, таких как Perl или Tcl) предполагают для интерпретации команд использование именно Bourne shell. Так как Bourne shell используется столь широко и часто, то очень важно, чтобы он стартовал очень быстро, его поведение было строго регламентировано и при этом потребности в оперативной памяти были малы.
В имеющейся реализации мы приложили максимум усилий для воплощения в жизнь всех этих требований одновременно. Для того, чтобы сохранить
/bin/sh
небольшим по размеру, мы не включили многие из обычных возможностей, которые имеются в других командных процессорах. Однако в Коллекцию Портов включены командные процессоры, обладающие гораздо большими возможностями, такие, как bash, scsh, tcsh и zsh. (Вы можете сами сравнить использование памяти всеми этими оболочками, посмотрев в колонки «VSZ» и «RSS» вывода команды ps -u).
Думается, что слова эти нуждаются в некотором дополнительном
пояснении. Когда UNIX запускает новый
процесс, ядро осуществляет следующую сложную цепочку действий:
сперва оно выполняет системный вызов fork(2)
и копирует область памяти соответствующую родительскому
процессу. Появляется два совершенно одинаковых родительских
процесса, которые различаются только кодом возврата функции
fork(2). Родитель получает
PID
потомка, а потомок — 0.
По этому нулю потомок догадывается, что он потомок и
осуществляет системный вызов exec(2), в
результате чего он полностью замещается новым процессом.
Из сказанного должно быть ясно, что во-первых вызов новой
программы, это очень дорогая операция (мы видели это, когда
сравнивали работу аргумента -exec
у программы
find(1) и использование команды
xargs(1) в Раздел 7.6, «Поиск файла по заданным атрибутам»), а
во-вторых, чем сложнее программа вызывающая новый процесс, тем
дороже эта операция. Между тем, программирование на Bourne Shell
сводится именно к написанию большого количества вызовов внешних
программ.
Сравним работу двух одинаковых с виду программ на sh:
#!/bin/sh i=0 while [ $i -lt 1000 ] do i=`echo $i+1|/usr/bin/bc` done echo $i
и на bash:
#!/usr/local/bin/bash i=0 while [ $i -lt 1000 ] do i=`echo $i+1|/usr/bin/bc` done echo $i
Как видите, эти программы отличаются только первой строчкой. В этих программах осуществляется в цикле тысячекратный вызов программы bc(1), крохотного калькулятора, для увеличения счётчика. И столько же раз вызвана команда test(1) (см. ниже).
$
time ./test.sh 1000 real 0m7.447s user 0m1.428s sys 0m5.812s$
time ./test.bash 1000 real 0m14.223s user 0m2.273s sys 0m11.559s
Легко видеть, что тяжеловесный bash(1) ворочал этот скрипт почти вдвое дольше. На разных опробованных мною системах соотношение времени выполнения этого скрипта bash/sh колебалось от 1.3 до 1.9.
Таблица 7.7. Синтаксическая таблица Bourne Shell
Элемент | Описание |
---|---|
# | Комментарий |
; | Конец команды. Тождественен концу строки. Команда
будет выполнена в foreground, т.е.
sh(1) будет ждать, пока она
выполнится (ср. с &
ниже). |
& | Конец команды. Команда будет выполнена в фоновом
режиме, т.е. sh(1) не будет ждать,
пока она выполнится (ср. с ; выше). Не путать с && |
&& | Логическое И. если до && стоит команда,
которая выдала нулевой код возврата (истина), то
выполняется команда следующая за && , суммарным кодом
возврата будет код возврата второй команды. Если нет,
то вторая команда не выполняется, а код возврата
берётся от первой операции (т.е. ложь). |
|| | Логическое ИЛИ. если до || стоит команда,
которая выдала ненулевой код возврата (ложь), то
выполняется команда следующая за || , суммарным кодом
возврата будет код возврата второй команды. Если нет,
то вторая команда не выполняется, а код возврата
берётся от первой операции (т.е. истина). |
| | Символ pipe (труба). STDOUT
команды перед |
перенапрвляется на STDIN следующей
команде. Подробно о перенаправлении говорится в Раздел 7.1, «Перенаправление вывода и использование
tee(1)». Команды объединённые через | вместе называются конвейером
(pipeline). Код возврата конвейера равен коду возврата
последней команды в конвейере. |
[n]>&m | Перенаправление STDOUT или
файлового дескриптора n в файловый дескриптор m.
Подробно о перенаправлении говорится в Раздел 7.1, «Перенаправление вывода и использование
tee(1)». |
[n]> file | Перенаправление STDOUT или
файлового дескриптора n в файл. Подробно о
перенаправлении говорится в Раздел 7.1, «Перенаправление вывода и использование
tee(1)». |
[n]< file | Перенаправление STDIN , либо
файлового дескриптора n из файла.
Подробно о перенаправлении говорится в Раздел 7.1, «Перенаправление вывода и использование
tee(1)». |
[n]<&- | Закрыть STDIN , либо файловый
дескриптор n |
[n]>&- | Закрыть STDOUT , либо файловый
дескриптор n |
var=... | Присваивание переменной var |
$var | Вызов значения переменной var |
\ | Экранирование следующего символа. Например, если
find(1) ожидает получить в качестве
аргумента знак ; , то, для того, чтобы
этот знак не был интерпретирован
sh(1), а был-бы благополучно
доставлен в find(1), в командной
строке следует написать \; |
' | Внутри одинарных кавычек решительно все символы не имеют никакого специального значения. Полное экранирование. |
` | Команда внутри обратных кавычек будет выполнена,
а её STDOUT будет подставлен в
командную строку вместо кавычек. При этом концы строк
будут заменены на пробелы. |
" | Неполное экранирование. Внутри двойных кавычек не экранируются обратные кавычки и знак $ (ссылка на переменную). |
! | Инвертирует код возврата последующей команды (или конвейера). |
$(...) | То же, что и обратные кавычки, но может быть вложенным. |
$((...)) | Ожидается, что внутри будет арифметическое
выражение. Оно будет вычислено и подставлено в
командную строку вместо скобок. Внимание! Аналогичные
скобки $[...] , это
расширение bash(1) и
sh(1) его не поддерживает. |
(...) | команды в круглых скобках будут выполнены в отдельном подпроцессе. Область видимости переменных, определённых в скобках, не выйдет за их пределы. |
name () {...} | Определение функции (см. ниже). |
. filename | Выполнение набора команд из файла
filename . (См. ниже про
«модули») |
: | Встроенная команда ничего не делающая, но возвращающая истину. |
Присваивание переменной осуществляется при помощи оператора
=
. Вызов определённой ранее
переменной, при помощи префикса $
. Например:
$
PI=3.1415926535897931$
echo $PI 3.1415926535897931
Следует подчеркнуть: переменные и переменные окружения это не одно и то же. Любая переменная окружения видна как переменная, но не любая переменная видна как переменная окружения. Чтобы переменная стала переменной окружения её надо экспортировать:
$
PI=3.1415926535897931$
echo $PI 3.1415926535897931$
printenv PI$
echo $? 1$
export PI$
printenv PI 3.1415926535897931
Т.е. до вызова команды export
,
Переменная PI
была, а переменной
окружения PI
не было. (Подробно о команде
printenv(1) можно узнать в разделе Раздел 7.2.1.1, «env(1),
printenv(1)») Здесь использована так же
переменная $?
в которой хранится
код возврата последней операции. Подробно специальные
переменные перечислены в таблице ниже.
Таблица 7.8. Специальные переменные в Bourne Shell
Переменная | Описание |
---|---|
$* | Список аргументов, с которыми был вызван скрипт. |
$@ | Список аргументов, с которыми был вызван скрипт. |
$# | Число аргументов, с которыми был вызван скрипт. |
$? | Код возврата последней команды (последнего конвейера). |
$- | Список аргументов, с которыми вызван sh(1) |
$$ | PID родительской оболочки |
$! | PID последнего отправленного в фон процесса. |
В качестве условия в sh(1) выполняется некоторая команда, и изучается её код возврата. Если код возврата равен нулю, и, следовательно, программа завершилась успешно, sh(1) трактует это как истину, если программа вернула код возврата больше нуля, sh(1) трактует это как ложь.
Проверки можно комбинировать при помощи знаков
&&
(логическое «и») и
||
(логическое «или»).
В простейшем случае можно вообще обойтись без явного условного оператора, используя лишь комбинацию этих знаков:
uname | grep -q BSD && echo "Это какая-то BSD!" || echo "Не знаю что это."
Такой синтаксис краток, но не следует им злоупотреблять. В
конечном счёте это ведёт к трудно читаемым программам. Ту же
проверку лучше осуществить с использованием явного оператора
if
:
if uname | grep -q BSD then echo "Это какая-то BSD!" else echo "Не знаю, что это." fi
Если эти операторы понадобится написать в одну строку, то надо
понимать, что ключевые слова if
,
then
, else
и fi
, это самостоятельные команды и
они в строке они должны предваряться знаком ;
.
Ветвь else
, разумеется,
необязательна. Если нам надо проверить несколько альтернатив,
то, чтобы не вкладывать много условных операторов друг в друга,
мы можем использовать оператор elif
.
if uname | grep -q FreeBSD then echo "Это FreeBSD, на ней может получиться удобная рабочая станция" elif uname | grep -q OpenBSD then echo "Это OpenBSD, знаменитая своей безопасностью." echo "Прекрасный выбор для сервера" elif uname | grep -q NetBSD then echo "Это NetBSD, она поддерживает самые немыслимые архитектуры." echo "Хороший выбор для тостера или холодильника." else echo "Не знаю, что это." fi
Недостатком данной конструкции является то, что здесь шесть раз вызываются программы uname(1) и grep(1). Существует более очевидная конструкция для проверки на соответвие строки списку значений.
case "`uname`" in FreeBSD) echo "Это FreeBSD, на ней может получиться удобная рабочая станция";; OpenBSD) echo "Это OpenBSD, знаменитая своей безопасностью." echo "Прекрасный выбор для сервера";; NetBSD) echo "Это NetBSD, она поддерживает самые немыслимые архитектуры." echo "Хороший выбор для тостера или холодильника.";; BSD|[Dd][Aa][Rr][Vv][Ii][Nn]) echo "Есть основания полагать, что это тоже BSD";; *) echo "Не знаю, что это.";; esac
Специально для условного оператора sh(1) существует программа осуществляющая математические проверки, проверки на существование файловых объектов и равенство строк. В зависимости от результата сравнения эта программа возвращает либо ноль, либо единицу. Речь идёт о программе test(1).
Таблица 7.9. Опции команды test(1)
Опция | Описание |
---|---|
Проверки файловых объектов | |
-e <file> | Истина, если <file> существует независимо от того, чем он является |
-r <file> | Истина, если <file> существует и из него можно читать |
-w <file> | Истина, если <file> существует и в него можно писать |
-x <file> | Истина, если <file> существует и его можно выполнить |
-s <file> | Истина, если <file> существует и не пуст |
-b <file> | Истина, если <file> существует и является блочным устройством |
-c <file> | Истина, если <file> существует и является символьным устройством устройством |
-d <file> | Истина, если <file> существует и является каталогом |
-f <file> | Истина, если <file> существует и является обычным файлом |
-h <file> -L <file> | Истина, если <file> существует и является
символьной ссылкой. Опция -h
оставлена для совместимости и не рекомендуется к
использованию. |
-p <file> | Истина, если <file> существует и является именованным каналом (FIFO) |
-S <file> | Истина, если <file> существует и является сокетом |
-k <file> | Истина, если <file> существует и на нём установлен stiсky-бит |
-t <num> | Истина, если файловый дескриптор <num> существует и направлен на терминал. С помощью этой проверки можно убедиться направлен ли вывод скрипта на терминал или перенаправлен в файл |
-O <file> | Истина, если <file> существует и его владелец тот же, что и EUID данного процесса |
-G <file> | Истина, если <file> существует и его группа та же, что и EGID данного процесса |
<file1> -nt <file2> | Истина, если файл <file1> новее (newer then) чем файл <file2> |
<file1> -ot <file2> | Истина, если файл <file1> старше (older then) чем файл <file2> |
<file1> -et <file2> | Истина, если файл <file1> и файл <file2> указывают на один и тот же файл |
Проверки строк | |
-n <string> | Истина, если строка <string> не пуста |
<string> | Истина, если строка <string> не пуста |
-z <string> | Истина, если строка <string> пуста |
<s1> = <s2> | Истина, если строки <s1> и <s2> одинаковы |
<s1> != <s2> | Истина, если строки <s1> и <s2> отличаются |
<s1> < <s2> | Истина, если строка <s1> должна идти перед <s2> по кодам ASCII. Например "abc" < "abd" |
<s1> > <s2> | Истина, если строка <s1> должна идти после <s2> по кодам ASCII |
Проверки чисел | |
<n1> -eq <n2> | Истина, если числа <n1> и <n2> равны (equal) |
<n1> -ne <n2> | Истина, если числа <n1> и <n2> не равны (not equal) |
<n1> -ge <n2> | Истина, если число <n1> больше либо равно <n2> (grater or equal) |
<n1> -gt <n2> | Истина, если число <n1> строго больше <n2> (grater then) |
<n1> -le <n2> | Истина, если число <n1> меньше либо равно <n2> (less or equal) |
<n1> -lt <n2> | Истина, если число <n1> строго меньше <n2> (less then) |
Объединение условий | |
-a | И (and) |
-o | Или (or) |
! | инвертирование проверки |
(...) | группирование для операторов «и» или «или» |
Для команды test(1) существует альтернативное имя [. Если она вызывается по имени [, то она разбирает командную строку вплоть до того, пока не встретит закрывающую квадратную скобку. Таким образом, следующие четыре конструкции эквивалентны:
$
test -d /usr/ports && echo "найдено дерево портов" || echo "Дерево портов не найдено"$
[ -d /usr/ports ] && echo "найдено дерево портов" || echo "Дерево портов не найдено"$
if test -d /usr/ports>
then echo "найдено дерево портов">
else echo "Дерево портов не найдено">
fi$
if [ test -d /usr/ports ]>
then echo "найдено дерево портов">
else echo "Дерево портов не найдено">
fi
Обратите внимание: вокруг квадратных скобок обязательно должны быть пробелы, потому что [ это не синтаксическая конструкция sh(1), а обычная команда на подобии test(1).
В sh(1) имеется два вида циклов: цикл с условием и цикл с перебором. Первый действует до тех пор, пока верно некоторое условие. Второй перебирает значения некоторого списка, приравнивая переменную (итератор) каждый раз к новому значению из этого списка. Кроме того, имеются обычные команды для прерывания цикла.
Следующая программа выводит список квадратов натуральных
чисел от 1 до 100, используя цикл с условием while
:
i=1 while [ $i -le 100 ] do echo $(($i*$i)) i=$(($i+1)) done
Ключевые слова do
и done
ограничивают тело цикла, при
написании в одной строке они должны предваряться ;
.
i=1; while [ $i -le 100 ]; do echo $(($i*$i)); i=$(($i+1)); done
Цикл может быть прерван встроенной командой break
:
i=1 while : do echo $(($i*$i)) i=$(($i+1)) if [ $i -gt 100 ]; then break; fi done
Встроенная команда :
всегда
возвращает истину (см. Таблица 7.7, «Синтаксическая таблица Bourne Shell»).
Вместо неё можно было бы употребить команду
/usr/bin/true
.
Встроенная команда continue
предназначена для прерывания текущей итерации. Т.е.
выполнение цикла будет продолжено, но текущая итерация
будет прервана. Если после команд break
или continue
указано число, то оно
означает глубину цикла, который будет ими прерван.
Следующая программа конвертирует все картинки в каталоге
image
из формата
GIF в формат PNG
for filename in image/*.jpg do # Действительно ли это JPEG? if ! file $filename | grep -q "JPEG image data" then continue fi # Конвертируем JPEG в PNG if convert $filename ${filename%jpg}png then echo "$filename -> ${filename%jpg}png" else echo "$filename don't converted" fi done
Утилита convert(1) — сторонняя утилита. Не входит ни в одну операционную систему BSD по-умолчанию и доставляется отдельно из портов или пакетов.
Конструкция image/*.jpg
превратится в список файлов в каталоге
image
имя которых оканчивается на
.jpg
.
Первая проверка нужна для того, чтобы убедиться, что мы
имеем дело с файлом в формате JPEG.
Вдруг файл имеющий это расширение на самом деле никакой не
JPEG, а, скажем MP3?
В случае, если это не JPEG мы
используем прерывание текущей итерации (но не всего цикла
целиком) при помощи команды continue
.
Обратите внимание на использование кавычек: внутри двойных кавычек переменные раскрываются в свои значения. Кавычки мы использовали для того, чтобы в одном случае строка трактовалась бы утилитой grep(1) как один аргумент, несмотря на наличие в ней пробелов, в другом случае, чтобы защитить символ > и в третьем случае защитить одинарную кавычку.
Конструкция ${filename%jpg}
указывает sh(1), что надо взять
значение переменной $filename
и
отрезать с конца фрагмент jpg
.
sh(1) позволяет определять функции и
вызывать их. Переменные $1
, $2
и т.д. внутри функций ссылаются не
на аргументы скрипта, а на аргументы с которыми функция
вызывалась. Ниже определена функция для вычисления квадрата
натурального числа и переписан цикл выводящий список
квадратов натуральных чисел:
sqrt () { echo $(($1*$1)) } i=1 while [ $i -le 100 ] do sqrt $i i=$(($i+1)) done
При помощи директивы return функция может вернуть свой собственный код возврата.
В sh(1) существует конструкция,
позволяющая подгружать внешние файлы с определёнными в них
функциями и переменными. Допустим у нас есть файл
math
в котором имеются следующие
определения:
# Описываем константы PI=3.1415926535897931 # Функция для возведения в квадрат sqrt () { echo $(($1*$1)) } # Длина окружности circlen () { echo "$PI*$1*2" | /usr/bin/bc } # Площадь круга circarea () { sq=`sqrt $1` echo "$PI*$sq" | /usr/bin/bc }
Теперь, если мы захотим вычислить длину окружности, или её
площадь, нам достаточно внутри скрипта подгрузить данный
«модуль». Это делается при помощи оператора .
(точка). После того, как модуль
подгружен, мы можем использовать все функции и константы,
которые в нём определены.
#!/bin/sh . math echo "Длина окружности радиуса 3 см равна `circlen 3` см" echo "А площадь круга того же радиуса равна `circarea 3` см^2" echo "Причина этого явления в том, что число пи, по прежнему" echo "равно $PI, и со времён древних греков" echo "существенно не изменилось..."
В этом скрипте мы подгружаем «модуль» math и
вызываем функцию подсчёта длины окружности и площади круга,
которые в нём определены, а так же ссылаемся на определённую
в нём переменную $PI
. Этот приём
часто используется при написании системных скриптов. В
частности, именно так реализованы файлы
/etc/defaults/rc.conf
и
/etc/rc.conf
в
FreeBSD. Оба являются модулями, в первом
определяются константы, вроде:
................................. inetd_enable="NO" # Run the network daemon dispatcher (YES/NO). inetd_program="/usr/sbin/inetd" # path to inetd, if you want a different one. inetd_flags="-wW -C 60" # Optional flags to inetd .................................
Во втором они могут частично переопределяться, например:
................................. inetd_enable="YES" .................................
При этом оба файла последовательно подгружаются в файле
/etc/rc.subr
в функции
load_rc_config()
.
Команда getopts входит в стандарт
POSIX и является встроенной командой
sh(1). У команды
getopts имеется два аргумента:
1) — строка с перечнем возможных опций. После
опций у которых возможно значение, ставится двоеточие.
2) — имя переменной, в которую будет
сохраняться имя опции. Значение переменной будет
сохраняться в переменной $OPTARG
. Пример:
#!/bin/sh while getopts e:h option do case $option in h) echo "Usage: `/usr/bin/basename $0` [-h|-e text]";; e) echo Hello, $OPTARG;; \?) $0 -h;; esac done
Теперь вызовем этот скрипт (назовём его
getopts.sh
).
$
./getopts.sh -h Usage: getopts.sh [-h|-e text]$
./getopts.sh -a Illegal option -a Usage: getopts.sh [-h|-e text]$
./getopts.sh -e BSD Hello, BSD$
./getopts.sh -h -e BSD Usage: getopts.sh [-h|-e text] Hello, BSD
Команда eval просто выполняет свой аргумент. Это позволяет «сконструировать скрипт» складывая команды и аргументы в некоторую переменную, а потом подставить её на выполнение команде eval.
Ниже приведено другое остроумное применение данной
команды. Пример взят из файла
/etc/rc.subr
, являющегося
«модулем» для некоторых системных скриптов
FreeBSD. Рассмотрим функцию проверяющую
значение переменной. Этой функции передаётся имя
переменной. Функция проверяет значение этой переменной и,
если там стоит слово «no» возвращает единицу,
если «yes» — 0, в противном случае
предупреждает об ошибке. Для того, чтобы манипулировать и
с именем переменной и с её значением применяется команда
eval. С её помощью значение переменной
помещается в отдельную переменную
_value
, а имя переменной доступно как
аргумент функции, т.е. $1
:
# # checkyesno var # Test $1 variable, and warn if not set to YES or NO. # Return 0 if it's "yes" (et al), nonzero otherwise. # checkyesno() { eval _value=\$${1} debug "checkyesno: $1 is set to $_value." case $_value in # "yes", "true", "on", or "1" [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) return 0 ;; # "no", "false", "off", or "0" [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) return 1 ;; *) warn "\$${1} is not set properly - see rc.conf(5)." return 1 ;; esac }
Встроенная в sh(1) команда trap позволяет зарегистрировать обработчик сигнала. Если в процессе выполнения скрипт получит указанный в команде trap сигнал, то вместо обычного поведения, он вызовет указанный обработчик:
terminator () { echo "Не умру ни за что" >&2 } trap terminator 15
Теперь, если скрипт получит сигнал
SIGTERM
(номер 15), то вместо того,
чтобы нормально завершиться, он напечатает на стандартный
вывод ошибок сообщение и продолжит работу.
Более разумным применением этого механизма было бы стирание временных файлов и корректное завершение работы.
В заключение следует сказать, что после того, как вы написали могучий сценарий sh(1), ему не мешает дать пермиссии на выполнение, командой chmod(1).