Глава 2. Для начала о Sha-Bang

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

Пример 2-1. cleanup: Сценарий очистки лог-файлов в /var/log

# cleanup
# Для работы сценария требуются права root.

cd /var/log
cat /dev/null > messages
cat /dev/null > wtmp
echo "Лог-файлы очищены."

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

Пример 2-2. cleanup: Расширенная версия предыдущего сценария.

#!/bin/bash
# cleanup, version 2
# Для работы сценария требуются права root.

LOG_DIR=/var/log
ROOT_UID=0     # Только пользователь с $UID 0 имеет привилегии root.
LINES=50       # Количество сохраняемых строк по-умолчанию.
E_XCD=66       # Невозможно сменить каталог?
E_NOTROOT=67   # Признак отсутствия root-привилегий.


if [ "$UID" -ne "$ROOT_UID" ]
then
  echo "Для работы сценария требуются права root."
  exit $E_NOTROOT
fi

if [ -n "$1" ]
# Проверка наличия аргумента командной строки.
then
  lines=$1
else
  lines=$LINES # Значение по-умолчанию, если число не задано в командной строке
fi


#  Stephane Chazelas предложил следующее,
#+ для проверки корректности аргумента, переданного из командной строки,
#+ правда это достаточно сложно для данного руководства.
#
#    E_WRONGARGS=65  # Не числовой аргумент
#
#    case "$1" in
#    ""      ) lines=50;;
#    *[!0-9]*) echo "Usage: `basename $0` file-to-cleanup"; exit $E_WRONGARGS;;
#    *       ) lines=$1;;
#    esac
#
#* Конец проверки корректности аргумента


cd $LOG_DIR

if [ `pwd` != "$LOG_DIR" ]  # или   if [ "$PWD" != "$LOG_DIR" ]
                            # Не в /var/log?
then
  echo "Невозможно перейти в каталог $LOG_DIR."
  exit $E_XCD
fi  # Проверка каталога перед очисткой лог-файлов.

# более эффективный вариант:
#
# cd /var/log || {
#   echo "Невозможно перейти в требуемый каталог." >&2
#   exit $E_XCD;
# }




tail -$lines messages > mesg.temp # Сохранить последние строки в лог-файле.
mv mesg.temp messages


# cat /dev/null > messages
#* Необходимость этой команды отпала, поскольку очистка выполняется выше.

cat /dev/null > wtmp  #  команды ': > wtmp' и '> wtmp'  имеют тот же эффект.
echo "Лог-файлы очищены."

exit 0
#  Возвращаемое значение 0
#+ указывает на успешное завершение работы сценария.

Если вы не желаете полностью вычищать системные логи, то выше представлена улучшенная версия предыдущего сценария. Здесь сохраняются последние несколько строк (по-умолчанию -- 50).

Если файл сценария начинается с последовательности #!, которая в мире Unix называется sha-bang, то это указывает системе какой интерпретатор следует использовать для исполнения сценария. Это двухбайтовая последовательность, или [1] -- специальный маркер, определяющий тип сценария, в данном случае -- сценарий командной оболочки (см. man magic). Более точно, sha-bang определяет интерпретатор, который вызывается для исполнения сценария, это может быть командная оболочка (shell), иной интерпретатор или утилита. [2]

#!/bin/sh
#!/bin/bash
#!/usr/bin/perl
#!/usr/bin/tcl
#!/bin/sed -f
#!/usr/awk -f


Каждая, из приведенных выше сигнатур, приводит к вызову различных интерпретаторов, будь то /bin/sh -- командный интерпретатор по-умолчанию (bash для Linux-систем), либо иной. [3] При переносе сценариев с сигнатурой #!/bin/sh на другие Unix системы, где в качестве командного интерпретатора задан другой shell, вы можете лишиться некоторых особенностей, присущих bash. Поэтому такие сценарии должны быть POSIX совместимыми. [4].

Обратите внимание на то, что сигнатура должна указывать правильный путь к интерпретатору, в противном случае вы получите сообщение об ошибке -- как правило это "Command not found".

Сигнатура #! может быть опущена, если вы не используете специфичных команд. Во втором примере (см. выше) использование сигнатуры #! обязательно, поскольку сценарий использует специфичную конструкцию присваивания значения переменной lines=50. Еще раз замечу, что сигнатура #!/bin/sh вызывает командный интерпретатор по-умолчанию -- /bin/bash в Linux-системах.

Important

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

if [ $# -ne Number_of_expected_args ]
then
  echo "Usage: `basename $0` whatever"
  exit $WRONG_ARGS
fi


2.1. Запуск сценария

Запустить сценарий можно командой sh scriptname [5] или bash scriptname. (Не рекомендуется запуск сценария командой sh <scriptname, поскольку это запрещает использование устройства стандартного ввода stdin в скрипте). Более удобный вариант -- сделать файл скрипта исполняемым, командой chmod.

Это:

chmod 555 scriptname (выдача прав на чтение/исполнение любому пользователю в системе) [6]

или

chmod +rx scriptname (выдача прав на чтение/исполнение любому пользователю в системе)

chmod u+rx scriptname (выдача прав на чтение/исполнение только "владельцу" скрипта)



После того, как вы сделаете файл сценария исполняемым, вы можете запустить его примерно такой командой ./scriptname. [7] Если, при этом, текст сценария начинается с корректной сигнатуры ("sha-bang"), то для его исполнения будет вызван соответствующий интерпретатор.

И наконец, завершив отладку сценария, вы можете поместить его в каталог /usr/local/bin (естественно, что для этого вы должны обладать правами root), чтобы сделать его доступным для себя и других пользователей системы. После этого сценарий можно вызвать, просто напечатав название файла в командной строке и нажав клавишу [ENTER].

Notes

[1]

Некоторые разновидности Unix (основанные на 4.2BSD) требуют, чтобы эта последовательность состояла из 4-х байт, за счет добавления пробела после ! -- #! /bin/sh.

[2]

В shell-скриптах последовательность #! должна стоять самой первой и задает интерпретатор (sh или bash). Интерпретатор, в свою очередь, воспринимает эту строку как комментарий, поскольку она начинается с символа #.

Если в сценарии имеются еще такие же строки, то они воспринимаются как обычный комментарий.

#!/bin/bash

echo "Первая часть сценария."
a=1

#!/bin/bash
# Это *НЕ* означает запуск нового сценария.

echo "Вторая часть сценария."
echo $a  # Значение переменной $a осталось равно 1.


[3]

Эта особенность позволяет использовать различные хитрости.

#!/bin/rm
# Самоуничтожающийся сценарий.

# Этот скрипт ничего не делает -- только уничтожает себя.

WHATEVER=65

echo "Эта строка никогда не будет напечатана."

exit $WHATEVER  # Не имеет смысла, поскольку работа сценария завершается не здесь.


Попробуйте запустить файл README с сигнатурой #!/bin/more (предварительно не забудьте сделать его исполняемым).

[4]

Portable Operating System Interface, попытка стандартизации UNIX-подобных операционных систем.

[5]

Внимание: вызов Bash-скрипта с помощью команды sh scriptname отключает специфичные для Bash расширения, что может привести к появлению ошибки и аварийному завершению работы сценария.

[6]

Сценарий должен иметь как право на исполнение, так и право на чтение, поскольку shell должен иметь возможность прочитать скрипт.

[7]

Почему бы не запустить сценарий просто набрав название файла scriptname, если сценарий находится в текущем каталоге? Дело в том, что из соображений безопасности, путь к текущему каталогу "." не включен в переменную окружения $PATH. Поэтому необходимо явно указывать путь к текущему каталогу, в котором находится сценарий, т.е. ./scriptname.