«…лишь недалекие люди боятся конкуренции, а люди подлинного творчества ценят общение с каждым талантом…» А. Бек, Талант.

Спец курс (Автоматизация процесса проектирования)/Лекция 2 (BASH)

Материал из Wiki
Перейти к: навигация, поиск
Лекции SCRIPT

Лекции

Практические задания
Тесты

Табель успеваемости

Экзамен

Доп. материалы

Прямая ссылка на Слайды лекции 2 (Bash)

Содержание

Ключевые слова

  • Стоит избегать использования ключевых слов при именовании переменных
    • break выход из цикла for, while или until
    • continue выполнение следующей итерации цикла for, while или until
    • echo вывод аргументов, разделенных пробелами, на стандартное устройство вывода
    • exit выход из оболочки
    • export отмечает аргументы как переменные для передачи в дочерние процессы в среде
    • hash запоминает полные имена путей команд, указанных в качестве аргументов, чтобы не искать их при следующем обращении
    • kill посылает сигнал завершения процессу
    • pwd выводит текущий рабочий каталог
    • read читает строку из ввода оболочки и использует ее для присвоения значений указанным переменным
    • return заставляет функцию оболочки выйти с указанным значением
    • shift перемещает позиционные параметры налево
    • test вычисляет условное выражение
    • times выводит имя пользователя и системное время, использованное оболочкой и ее потомками
    • trap указывает команды, которые должны выполняться при получении оболочкой сигнала
    • unset вызывает уничтожение переменных оболочки
    • wait ждет выхода из дочернего процесса и сообщает выходное состояние.

Объявление переменных: declare и typeset

  • Инструкции declare и typeset являются встроенными инструкциями (они абсолютно идентичны друг другу и являются синонимами) и предназначена для наложения ограничений на переменные. Это очень слабая попытка контроля над типами, которая имеется во многих языках программирования. Инструкция declare появилась в Bash, начиная с версии 2. Кроме того, инструкция typeset может использоваться и в ksh-сценариях.

Локальные переменные

  • Переменные, объявленные как локальные, имеют ограниченную область видимости, и доступны только в пределах блока, в котором они были объявлены. Для функций это означает, что локальная переменная "видна" только в теле самой функции.
  • local l_var=12
#!/bin/bash
function func ()
{
  local loc_var=23       # Объявление локальной переменной.
  echo
  echo "\"loc_var\" в функции = $loc_var"
  global_var=999         # Эта переменная не была объявлена локальной.
  echo "\"global_var\" в функции = $global_var"
}
func
# Проверим, "видна" ли локальная переменная за пределами функции.
echo
echo "\"loc_var\" за пределами функции = $loc_var"
                                      # "loc_var" за пределами функции =
                                      # Итак, $loc_var не видна в глобальном контексте.
echo "\"global_var\" за пределами функции = $global_var"
                                      # "global_var" за пределами функции = 999
                                      # $global_var имеет глобальную область видимости.
echo                                  
exit 0
  • Глобальные переменные, объявляемые в теле функции, считаются не объявленными до тех пор, пока функция не будет вызвана. Это касается всех переменных.
#!/bin/bash
function func ()
{
global_var=37    #  Эта переменная будет считаться необъявленной
                 #+ до тех пор, пока функция не будет вызвана.
}                # КОНЕЦ ФУНКЦИИ
echo "global_var = $global_var"  # global_var =
                                 #  Функция "func" еще не была вызвана,
                                 #+ поэтому $global_var пока еще не "видна" здесь.
 
func
echo "global_var = $global_var"  # global_var = 37
                                 # Переменная была инициализирована в функции.

Ключи инструкций declare/typeset

  • -r readonly (только для чтения)

declare -r var1 (declare -r var1 аналогично объявлению readonly var1)

  1. Это грубый эквивалент констант (const) в языке C. Попытка изменения таких переменных завершается сообщением об ошибке.
  • -i integer
#!/bin/bash
declare -i number
# Сценарий интерпретирует переменную "number" как целое число.
number=3
echo "number = $number"     # number = 3
number=three
echo "number = $number"     # number = 0
# Строка "three" интерпретируется как целое число.
  • Примечательно, что допускается выполнение некоторых арифметических операций над переменными, объявленными как integer, не прибегая к инструкциям expr или let.
  • -a array
#!/bin/bash
declare -a indices
#Переменная indices объявляется массивом.
  • -f functions
#!/bin/bash
#declare -f
#Инструкция declare -f, без аргументов, приводит к выводу списка ранее объявленных функций в сценарии.
func2 () {
echo "Hi All!!!"
} 
declare -f
  • Инструкция declare -f function_name выводит имя функции function_name, если она была объявлена ранее.
-x export
declare -x var3
# Эта инструкция объявляет переменную, как доступную для экспорта.
 
var=$value
declare -x var3=373
  • Инструкция declare допускает совмещение объявления и присваивания значения переменной одновременно.
#!/bin/bash
 
func1 ()
{
echo Это функция.
}
 
declare -f        # Список функций, объявленных выше.
 
echo
 
declare -i var1   # var1 -- целочисленная переменная.
var1=2367
echo "переменная var1 объявлена как $var1"
var1=var1+1       # Допустимая арифметическая операция над целочисленными переменными.
echo "переменная var1 увеличена на 1 = $var1."
# Допустимая операция для целочисленных переменных
echo "Возможно ли записать дробное число 2367.1 в var1?"
var1=2367.1       # Сообщение об ошибке, переменная не изменяется.
echo "значение переменной var1 осталось прежним = $var1"
 
echo
 
declare -r var2=13.36         # инструкция 'declare' допускает установку свойств переменной
                              #+ и одновременно присваивать значение.
echo "var2 declared as $var2" # Допускается ли изменять значение readonly переменных?
var2=13.37                    # Сообщение об ошибке и завершение работы сценария.
 
echo "значение переменной var2 осталось прежним $var2" # Эта строка никогда не будет выполнена.
 
exit 0                        # Сценарий завершит работу выше.

Создание массивов

  • Массив - это переменная, в которой хранится несколько значений.
  • Любая переменная может использоваться как массив.
  • На максимальный размер массива ограничений нет и нет никаких других требований к элементам массива, за исключением лишь того, что элементы массива имеют индексы и значения индексов идут подряд.
  • Массивы начинаются с нулевого элемента: индекс первого элемента равен 0.
Косвенное объявление, задающее переменную типа массив, выполняется следующим образом:
ARRAY[INDEXNR]=value 
#INDEXNR рассматривается как арифметическое выражение, результат вычисления которого должен быть положительным числом.
  • Явное объявление массива выполняется с помощью встроенной команды declare:
declare -a ARRAYNAME
  • Также допускается объявление с указанием индекса, но индекс будет игнорироваться.
  • С помощью встроенных команд declare и readonly для массива можно определять атрибуты.
  • Атрибуты действуют для всех переменных в массиве, у вас не может быть смешанных массивов.
# Массив переменных можно также объявить с помощью инструкции присваивания следующего формата:
ARRAY=(value1 value2 ... valueN)
  • Каждое значение value в этом формате имеет вид [номериндекса =]строка.
  • Индекс не является обязательным.
  • Если он указан, то он используется в инструкции присваивания;
  • В противном случае в качестве индекса используется значение индекса, которое уже было назначено, плюс один.
  • Этот формат можно также использовать в команде declare.
  • Если индексы не указываются, индексация начинается с нуля.
  • Добавление отсутствующих или дополнительных элементов массива осуществляется следующим образом:
ARRAYNAME[indexnumber]=value
  • Помните, что во встроенной команде read есть параметр -a, который позволяет читать и назначать значения элементам массива.

Получение доступа к содержимому массива

  • Чтобы получить доступ к содержимому элемента в массиве, используйте фигурные скобки.

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

  • Если в качестве номера индекса указывается "@" или "*", то выдаются все элементы массива.
#!/bin/bash
ARRAY=(one two three)
echo ${ARRAY[*]}
# one two three
 
echo $ARRAY[*]
one[*]
 
echo ${ARRAY[2]}
#three
 
ARRAY[3]=four
 
echo ${ARRAY[*]}
#one two three four
  • Если переменная массива указывается без указания номера индекса, то в результате будет получено содержимое первого элемента массива, т. е. элемента с нулевым индексом.
  • Bash позволяет оперировать переменными, как массивами, даже если они не были явно объявлены таковыми.
string=abcABC123ABCabc
echo ${string[@]}               # abcABC123ABCabc
echo ${string[*]}               # abcABC123ABCabc
echo ${string[0]}               # abcABC123ABCabc
echo ${string[1]}               # Ничего не выводится!
                                # Почему?
echo ${#string[@]}              # 1
                                # Количество элементов в массиве.

Удаление переменных массивов

  • Для уничтожения массивов или элементов массива используется встроенная команда unset:
#!/bin/bash
unset ARRAY[1]
echo ${ARRAY[*]}
#one three four
 
unset ARRAY
 
echo ${ARRAY[*]}
#<--no output-->

Примеры массивов

Практические примеры использования массивов найти трудно. Вы легко найдете множество скриптов, которые в вашей системе, в действительности, ничего не делают, но в них используются массивы для вычисления математических рядов. И это будут одни из наиболее интересных примеров ... В большинстве скриптов всего лишь в упрощенном виде показывается, что вы, теоретически, можете делать с массивами. Причина этого состоит в том, что массивы являются сравнительно сложными структурами. Вы обнаружите, что большинство практических примеров, в которых используются массивы, уже реализованы в вашей системе, но на более низком уровне — на языке программирования C, на котором написаны большинство команд UNIX.
#!/bin/bash
# сможет ли пользователь, имеющий надлежащие права доступа, запускать скрипт с правильными аргументами.
if [ $(whoami) != 'root' ]; then
        echo "Must be root to run $0"
        exit 1;
fi
if [ -z $1 ]; then
        echo "Usage: $0 <path/to/httpd.conf>"
        exit 1
fi
 
httpd_conf_new=$1
httpd_conf_path="/usr/local/apache/conf"
login=htuser
 
#Имена хостов, которые необходимо сконфигурировать, перечислены в массиве farm_hosts
farm_hosts=(web03 web04 web05 web06 web07)
 
for i in ${farm_hosts[@]}; do
        su $login -c "scp $httpd_conf_new ${i}:${httpd_conf_path}"
        su $login -c "ssh $i sudo /usr/local/apache/bin/apachectl graceful"
 
done
exit 0
  • Следующий пример представил Дэн Рихтер (Dan Richter). Он столкнулся со следующей проблемой:
"... На сайте моей компании есть демонстрационные примеры, и каждую неделю кто-нибудь должен все их проверять. Поэтому у меня есть задание cron, которое 
заполняет массив возможными кандидатами; в задании cron для определения недели в году используется команда date +%W и для того, чтобы получить правильный 
индекс, выполняется операция по модулю. Счастливчик получает уведомление по электронной почте".
И ниже приводится способ ее решения:
#!/bin/bash
# This is get-tester-address.sh 
#
# First, we test whether bash supports arrays.
# (Support for arrays was only added recently.)
#
whotest[0]='test' || (echo 'Failure: arrays not supported in this version of
bash.' && exit 2)
 
#
# Our list of candidates. (Feel free to add or
# remove candidates.)
#
wholist=(
     'Bob Smith <bob@example.com>'
     'Jane L. Williams <jane@example.com>'
     'Eric S. Raymond <esr@example.com>'
     'Larry Wall <wall@example.com>'
     'Linus Torvalds <linus@example.com>'
   )
#
# Count the number of possible testers.
# (Loop until we find an empty string.)
#
count=0
while [ "x${wholist[count]}" != "x" ]
do
   count=$(( $count + 1 ))
done
 
#
# Now we calculate whose turn it is.
#
week=`date '+%W'`       # The week of the year (0..53).
week=${week#0}          # Remove possible leading zero.
 
let "index = $week % $count"   # week modulo count = the lucky person
 
email=${wholist[index]}     # Get the lucky person's e-mail address.
 
echo $email             # Output the person's e-mail address.
Затем этот скрипт будет использован в других скриптах, таких как следующий, в котором используется встраиваемый документ (here document):
 
email=`get-tester-address.sh`   # Find who to e-mail.
hostname=`hostname`             # This machine's name.
 
#
# Send e-mail to the right person.
#
mail $email -s '[Demo Testing]' <EOF
The lucky tester this week is: $email
 
Reminder: the list of demos is here:
    http://web.example.com:8080/DemoSites
 
(This e-mail was generated by $0 on ${hostname}.)
EOF

Подмножества и части строк

  • Обычно расширение имеет такую форму: ${PARAMETER:OFFSET:LENGTH}, где аргумент LENGTH необязателен. Итак, если вы хотите выбрать только определенное подмножество аргументов скрипта, вы можете использовать полную версию, чтобы показать, сколько аргументов следует выбрать. Например, ${@:4:3} обращается к трем аргументам, начиная с аргумента 4, а именно, к аргументам 4, 5 и 6. Вы можете использовать это расширение для выбора конкретных параметров помимо тех, которые доступны сразу, используя от $1 до $9 включительно. ${@:15:1} — способ вызова сразу 15 параметра.
  • Вы можете использовать расширение с конкретными параметрами, а также весь набор параметров, представленный при помощи $* или $@. В этом случае параметр обрабатывается как строка и число, представляющее собой сдвиг или длину. Например, если переменная x имеет значение «some value», то
${x:3:5}

будет иметь значение «e val».

Подстроки значений параметров в shell

#!/bin/bash
x="some value"
echo "${x:3:5}"
#e val

Размеры переменных

  • Вы уже видели, что $# указывает число параметров и что расширение ${PARAMETER:OFFSET:LENGTH} применяется и к конкретным параметрам, и к $* и $@, поэтому вас не должно удивить, что аналогичная конструкция, ${#PARAMETER}, может использоваться для определения размера конкретного параметра. Простая функция testlength, показанная в Листинге 10, иллюстрирует это. Попробуйте сделать это сами.

#!/bin/bash
testlength () { for p in «$@»; do echo ${#p};done }
testlength 1 abc «def ghi»
#1
#3
#7

Работа с шаблонами

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

Расширение шаблонов в shell Расширение Назначение

  • ${ПАРАМЕТР#СЛОВО} Shell расширяет СЛОВО как расширение имени файла и удаляет самое короткое соответствие шаблону, если оно имеется, с начала расширенного значения ПАРАМЕТРА. Использование ‘@’ или ‘$’ приводит к удалению по образцу для каждого параметра в списке.
  • ${ПАРАМЕТР##СЛОВО} Приводит к удалению самого длинного соответствия шаблону с начала вместо самого короткого.
  • ${ПАРАМЕТР%СЛОВО} Shell расширяет СЛОВО как расширение имени файла и удаляет самое короткое соответствие шаблону, если оно имеется, с конца расширенного значения ПАРАМЕТРА. Использование ‘@’ или ‘$’ приводит к удалению по образцу для каждого параметра в списке.
  • ${ПАРАМЕТР%%СЛОВО} Приводит к удалению самого длинного соответствия шаблону с конца вместо самого короткого.
  • ${ПАРАМЕТР/ОБРАЗЕЦ/ПОСЛЕДОВАТЕЛЬНОСТЬ} Shell расширяет ОБРАЗЕЦ как расширение имени файла и заменяет самое длинное соответствие шаблону, если оно имеется, расширенным значением ПАРАМЕТРА. Для соответствия образцам в начале расширенного значения ПАРАМЕТРА поставьте в начале ОБРАЗЦА # или %, если соответствие должно проверяться до конца. Если ПОСЛЕДОВАТЕЛЬНОСТЬ пуста, перемещение / может быть опущено и соответствия удаляются. Использование ‘@’ или ‘$’ приводит к замене образца для каждого параметра в списке.
  • ${ПАРАМЕТР//ОБРАЗЕЦ/ПОСЛЕДОВАТЕЛЬНОСТЬ} Выполняет замену для всех подходящих, а не только для первого.
#!/bin/bash
x=»a1 b1 c2 d2″
echo ${x#*1}
#b1 c2 d2
echo ${x##*1}
#c2 d2
echo ${x%1*}
#a1 b
echo ${x%%1*}
#a
echo ${x/1/3}
#a3 b1 c2 d2
echo ${x//1/3}
#a3 b3 c2 d2
echo ${x//?1/z3}
#z3 z3 c2 d2

Перенаправление потока вывода в файл

Bombilla amarilla - yellow Edison lamp.pngДескриптор файла -- это просто число, по которому система идентифицирует открытые файлы. Рассматривайте его как упрощенную версию указателя на файл.

  • COMMAND_OUTPUT > # Перенаправление stdout (вывода) в файл. Если файл отсутствовал, то он создается, иначе -- перезаписывается.
ls -lR > dir-tree.list
# Создает файл, содержащий список дерева каталогов.
  •  : > filename # Операция > усекает файл "filename" до нулевой длины. Если до выполнения операции файла не существовало, то создается новый файл с нулевой длиной (тот же эффект дает команда 'touch'). Символ : выступает здесь в роли местозаполнителя, не выводя ничего.
  • COMMAND_OUTPUT >> Перенаправление stdout (вывода) в файл. Создает новый файл, если он отсутствовал, иначе -- дописывает в конец файла.
  • 1>filename # Перенаправление вывода (stdout) в файл "filename".
  • 1>>filename # Перенаправление вывода (stdout) в файл "filename", файл открывается в режиме добавления.
  • 2>filename # Перенаправление stderr в файл "filename".
  • 2>>filename # Перенаправление stderr в файл "filename", файл открывается в режиме добавления.
  • &>filename # Перенаправление stdout и stderr в файл "filename".

Поток ввода

  • < FILENAME # Ввод из файла. Парная команде ">", часто встречается в комбинации с ней.

Перенаправление потоков

  • 2>&1 # Перенаправляется stderr на stdout. Сообщения об ошибках передаются туда же, куда и стандартный вывод.
  • i>&j # Перенаправляется файл с дескриптором i в j. Вывод в файл с дескриптором i передается в файл с дескриптором j.
  • >&j # Перенаправляется файл с дескриптором 1 (stdout) в файл с дескриптором j. Вывод на stdout передается в файл с дескриптором j.
  • [j]<>filename # Файл "filename" открывается на чтение и запись, и связывается с дескриптором "j". Если "filename" отсутствует, то он создается. Если дескриптор "j" не указан, то, по-умолчанию, берется дескриптор 0, stdin. Как одно из применений этого -- запись в конкретную позицию в файле.
echo 1234567890 > File    # Записать строку в файл "File".
      exec 3<> File             # Открыть "File" и связать с дескриптором 3.
      read -n 4 <&3             # Прочитать 4 символа.
      echo -n . >&3             # Записать символ точки.
      exec 3>&-                 # Закрыть дескриптор 3.
      cat File                  # ==> 1234.67890
      # Произвольный доступ, да и только!

Подстановка процессов

Подстановка процессов -- это аналог подстановки команд. Операция подстановки команд записывает в переменную результат выполнения некоторой команды, например, dir_contents=`ls -al` или xref=$(grep word datafile). Операция подстановки процессов передает вывод одного процесса на ввод другого (другими словами, передает результат выполнения одной команды -- другой).

>(command)
<(command)
  • Таким образом инициируется подстановка процессов. Между круглой скобкой и символом "<" или ">", не должно быть пробелов, в противном случае это вызовет сообщение об ошибке.
cat <(ls -l)
# То же самое, что и     ls -l | cat
sort -k 9 <(ls -l /bin) <(ls -l /usr/bin) <(ls -l /usr/X11R6/bin)
# Список файлов в трех основных каталогах 'bin', отсортированный по именам файлов.
# Обратите внимание: на вход 'sort' поданы три самостоятельные команды.
diff <(command1) <(command2)    # Выдаст различия в выводе команд.
tar cf >(bzip2 -c > file.tar.bz2) $directory_name
# Вызовет "tar cf /dev/fd/?? $directory_name" и затем "bzip2 -c > file.tar.bz2".
# Из-за особенностей, присущих некоторым системам, связанным с /dev/fd/<n>,
# канал между командами не обязательно должен быть именованным.
# Это можно сделать и так.
bzip2 -c < pipe > file.tar.bz2&
tar cf pipe $directory_name
rm pipe
#        или
exec 3>&1
tar cf /dev/fd/4 $directory_name 4>&1 >&3 3>&- | bzip2 -c > file.tar.bz2 3>&-
exec 3>&-

Конвееры

  • | # Конвейер (канал). Универсальное средство для объединения команд в одну цепочку. Похоже на ">", но на самом деле -- более обширная. Используется для объединения команд, сценариев, файлов и программ в одну цепочку (конвейер).
cat *.txt | sort | uniq > result-file
  • Допускается перенаправление нескольких потоков в один файл.
ls -yz >> command.log 2>&1
# Сообщение о неверной опции "yz" в команде "ls" будет записано в файл "command.log".
# Поскольку stderr перенаправлен в файл.
  • Операции перенаправления и/или конвейеры могут комбинироваться в одной командной строке.
command < input-file > output-file
 
command1 | command2 | command3 > output-file

Закрытие дескрипторов файлов

  • n<&- Закрыть дескриптор входного файла n.
  • 0<&-, <&- Закрыть stdin.
  • n>&- Закрыть дескриптор выходного файла n.
  • 1>&-, >&- Закрыть stdout.

Дочерние процессы наследуют дескрипторы открытых файлов. По этой причине и работают конвейеры. Чтобы предотвратить наследование дескрипторов -- закройте их перед запуском дочернего процесса.

# В конвейер передается только stderr.
exec 3>&1                              # Сохранить текущее "состояние" stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Закрыть дескр. 3 для 'grep' (но не для 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Теперь закрыть его для оставшейся части сценария.

Каталоги специального назначения: /dev и /proc

  • /dev содержит файлы физических устройств, которые могут входить в состав аппаратного обеспечения компьютера. [1] Каждому из разделов не жестком диске соответствует свой файл-устройство в каталоге /dev, информация о которых может быть получена простой командой df.
    • Каталог /dev содержит специальные файлы -- точки монтирования физических и виртуальных устройств. Они занимают незначительное пространство на диске.
    • Некоторые из устройств, такие как /dev/null, /dev/zero или /dev/urandom -- являются виртуальными. Они не являются файлами физических устройств, система эмулирует эти устройства программным способом.
    • /dev/null -- это, своего рода, "черная дыра" в системе. Это, пожалуй, самый близкий смысловой эквивалент. Все, что записывается в этот файл, "исчезает" навсегда. Попытки записи или чтения из этого файла не дают, ровным счетом, никакого результата. Тем не менее, псевдоустройство /dev/null вполне может пригодиться.
1. Подавление вывода на stdout.
cat $filename >/dev/null
# Содержимое файла $filename не появится на stdout.
2.Подавление вывода на stderr (from Пример 12-2).
rm $badname 2>/dev/null
#           Сообщение об ошибке "уйдет в никуда".
3. Подавление вывода, как на stdout, так и на stderr.
cat $filename 2>/dev/null >/dev/null
# Если "$filename" не будет найден, то вы не увидите сообщения об ошибке.
# Если "$filename" существует, то вы не увидите его содержимое.
# Таким образом, вышеприведенная команда ничего не выводит на экран.
#
#  Такая методика бывает полезной, когда необходимо лишь проверить код завершения команды
#+ и нежелательно выводить результат работы команды на экран.
#
# cat $filename &>/dev/null
#     дает тот же результат, автор примечания Baris Cicek.
    • dev/zero так же является псевдоустройством, с той лишь разницей, что содержит нули. Информация, выводимая в этот файл, так же бесследно исчезает. Чтение нулей из этого файла может вызвать некоторые затруднения, однако это можно сделать, к примеру, с помощью команды od или шестнадцатиричного редактора. В основном, /dev/zero используется для создания заготовки файла с заданной длиной.
#!/bin/bash
# Создание файла подкачки.
# Этот сценарий должен запускаться с правами root.
ROOT_UID=0         # Для root -- $UID 0.
E_WRONG_USER=65    # Не root?
 
FILE=/swap
BLOCKSIZE=1024
MINBLOCKS=40
SUCCESS=0
if [ "$UID" -ne "$ROOT_UID" ]
then
  echo; echo "Этот сценарий должен запускаться с правами root."; echo
  exit $E_WRONG_USER
fi
blocks=${1:-$MINBLOCKS}          #  По-умолчанию -- 40 блоков,
                                 #+ если размер не задан из командной строки.
if [ "$blocks" -lt $MINBLOCKS ]
then
  blocks=$MINBLOCKS              # Должно быть как минимум 40 блоков.
fi
echo "Создание файла подкачки размером $blocks блоков (KB)."
dd if=/dev/zero of=$FILE bs=$BLOCKSIZE count=$blocks  # "Забить" нулями.
mkswap $FILE $blocks             # Назначить как файл подкачки.
swapon $FILE                     # Активировать.
echo "Файл подкачки создан и активирован."
exit $SUCCESS
  • /proc -- это виртуальная файловая система. Файлы, в каталоге /proc, содержат информацию о процессах, о состоянии и конфигурации ядра и системы.
#!/bin/bash
 cat /proc/devices
 cat /proc/interrupts
 cat /proc/partitions
 cat /proc/loadavg
 cat /proc/filesystems | grep iso9660

Списки команд

  • И-список
  • Каждая последующая команда, в таком списке, выполняется только тогда, когда предыдущая команда вернула код завершения true (ноль). Если какая-либо из команд возвращает false (не ноль), то исполнение списка команд в этом месте завершается, т.е. следующие далее команды не выполняются.
command-1 && command-2 && command-3 && ... command-n
  • ИЛИ-список
  • Каждая последующая команда, в таком списке, выполняется только тогда, когда предыдущая команда вернула код завершения false (не ноль). Если какая-либо из команд возвращает true (ноль), то исполнение списка команд в этом месте завершается, т.е. следующие далее команды не выполняются. Очевидно, что "ИЛИ-списки" имеют смысл обратный, по отношению к "И-спискам"
command-1 || command-2 || command-3 || ... command-n


Bombilla amarilla - yellow Edison lamp.png Списки возвращают код завершения последней выполненной команды. Комбинируя "И" и "ИЛИ" списки, легко "перемудрить" с логическими условиями, поэтому, в таких случаях может потребоваться детальная отладка.

#!/bin/bash
false && true || echo false         # false
# Тот же результат дает
( false && true ) || echo false     # false
# Но не эта комбинация
false && ( true || echo false )     # (нет вывода на экран)
#  Обратите внимание на группировку и порядок вычисления условий -- слева-направо,
#+ поскольку логические операции "&&" и "||" имеют равный приоритет.
#  Если вы не уверены в своих действиях, то лучше избегать таких сложных конструкций.

Генерация псевдослучайных чисел

  • $RANDOM -- внутренняя функция Bash (не константа), которая возвращает псевдослучайные целые числа в диапазоне 0 - 32767. Функция $RANDOM не должна использоваться для генераци ключей шифрования.
  • Если вам нужны случайные числа не превышающие определенного числа, воспользуйтесь оператором деления по модулю (остаток от деления).
  • Если вы желаете ограничить диапазон "снизу", то просто производите генерацию псевдослучайных чисел в цикле до тех пор, пока не получите число большее нижней границы.
#!/bin/bash
# $RANDOM возвращает различные случайные числа при каждом обращении к ней.
# Диапазон изменения: 0 - 32767 (16-битовое целое со знаком).
MAXCOUNT=10
count=1
 
echo
echo "$MAXCOUNT случайных чисел:"
echo "-----------------"
while [ "$count" -le $MAXCOUNT ]      # Генерация 10 ($MAXCOUNT) случайных чисел.
do
  number=$RANDOM
  echo $number
  let "count += 1"  # Нарастить счетчик.
done
echo "-----------------"

Завершение и код завершения

  • ...эта часть Bourne shell покрыта мраком, тем не менее все пользуются ею. Chet Ramey
  • Команда exit может использоваться для завершения работы сценария, точно так же как и в программах на языке C. Кроме того, она может возвращать некоторое значение, которое может быть проанализировано вызывающим процессом.
  • Каждая команда возвращает код завершения (иногда код завершения называют возвращаемым значением ). В случае успеха команда должна возвращать 0, а в случае ошибки -- ненулевое значение, которое, как правило, интерпретируется как код ошибки. Практически все команды и утилиты UNIX возвращают 0 в случае успешного завершения, но имеются и исключения из правил.
  • Аналогичным образом ведут себя функции, расположенные внутри сценария, и сам сценарий, возвращая код завершения. Код, возвращаемый функцией или сценарием, определяется кодом возврата последней команды. Команде exit можно явно указать код возврата, в виде: exit nnn, где nnn -- это код возврата (число в диапазоне 0 - 255).
  • Bombilla amarilla - yellow Edison lamp.pngNote : Когда работа сценария завершается командой exit без параметров, то код возврата сценария определяется кодом возврата последней исполненной командой.


Код возврата последней команды хранится в специальной переменной $?. После исполнения кода функции, переменная $? хранит код завершения последней команды, исполненной в функции. Таким способом в Bash передается "значение, возвращаемое" функцией. После завершения работы сценария, код возврата можно получить, обратившись из командной строки к переменной $?, т.е. это будет код возврата последней команды, исполненной в сценарии.

Пример. Завершение / код завершения
#!/bin/bash
 
echo hello
echo $?    # код возврата = 0, поскольку команда выполнилась успешно.
 
lskdf      # Несуществующая команда.
echo $?    # Ненулевой код возврата, поскольку команду выполнить не удалось.
 
echo
 
exit 113   # Явное указание кода возврата 113.
           # Проверить можно, если набрать в командной строке "echo $?"
           # после выполнения этого примера.
 
#  В соответствии с соглашениями, 'exit 0' указывает на успешное завершение,
#+ в то время как ненулевое значение означает ошибку.
Переменная $? особенно полезна, когда необходимо проверить результат исполнения команды (см. Пример 12-27 и Пример 12-13).

Note Символ !, может выступать как логическое "НЕ" для инверсии кода возврата.

Пример 6-2. Использование символа ! для логической инверсии кода возврата

true  # встроенная команда "true".
echo "код возврата команды \"true\" = $?"     # 0
 
! true
echo "код возврата команды \"! true\" = $?"   # 1
# Обратите внимание: символ "!" от команды необходимо отделять пробелом.
#    !true   вызовет сообщение об ошибке "command not found"

Отладка сразу всего скрипта

Когда дела идут не так, как планировалось, необходимо определить, из-за чего в скрипте возникли проблемы. В Bash для отладки предоставляются широкие возможности. Наиболее распространенным способом является запуск подоболочки с параметром -x, благодаря которому весь скрипт будет запущен в отладочном режиме. После того, как для каждой команды будут выполнены все необходимые подстановки и замены, но перед тем, как команда будет выполнена, в стандартный выходной поток будет выдана трассировка команды и все ее аргументы.

  • К примеру у нас есть скрипт
#!/bin/bash
# Данный скрипт очищает экран терминала, выдает в терминал приглашение и показывает информацию о пользователях,
# подключенных в текущий момент.  Устанавливаются значения для двух переменных, которые выдаются в терминал.
 
clear                           # очищает окно терминала
 
echo "The script starts now."
 
echo "Hi, $USER!"               # символ доллара используется для получения значения переменной
echo
 
echo "I will now fetch you a list of connected users:"
echo                                                    
w                               # показывается, кто зарегистрирован в системе
echo                            # и что каждый из них делает
 
echo "I'm setting two variables now."
COLOUR="black"                                  # устанавливает значение для переменной в локальной оболочке
VALUE="9"                                       # устанавливает значение для переменной в локальной оболочке
echo "This is a string: $COLOUR"                # показывается содержимое переменной 
echo "And this is a number: $VALUE"             # показывается содержимое переменной 
echo
 
echo "I'm giving you back your prompt now."
echo
  • Запустим его с аргументами отладки
bash -x script1.sh
+ clear
 
+ echo 'The script starts now.'
The script starts now.
+ echo 'Hi, willy!'
Hi, willy!
+ echo
 
+ echo 'I will now fetch you a list of connected users:'
I will now fetch you a list of connected users:
+ echo
 
+ w
  4:50pm  up 18 days,  6:49,  4 users,  load average: 0.58, 0.62, 0.40
 
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU  WHAT
root     tty2     -                Sat 2pm  5:36m  0.24s  0.05s  -bash
willy    :0       -                Sat 2pm   ?     0.00s   ?     -
willy    pts/3    -                Sat 2pm 43:13  36.82s 36.82s  BitchX willy ir
willy    pts/2    -                Sat 2pm 43:13   0.13s  0.06s  /usr/bin/screen
+ echo
 
+ echo 'I'\''m setting two variables now.'
I'm setting two variables now.
+ COLOUR=black
+ VALUE=9
+ echo 'This is a string: '
This is a string:
+ echo 'And this is a number: '
And this is a number:
+ echo
 
+ echo 'I'\''m giving you back your prompt now.'
I'm giving you back your prompt now.
+ echo


Отладка скрипта по частям

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

#!/bin/bash -xv 
set -x                  # activate debugging from here
w
set +x                  # stop debugging from here

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

The script starts now.
Hi, willy!
 
I will now fetch you a list of connected users:
 
+ w
  5:00pm  up 18 days,  7:00,  4 users,  load average: 0.79, 0.39, 0.33
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU  WHAT
root     tty2     -                Sat 2pm  5:47m  0.24s  0.05s  -bash
willy    :0       -                Sat 2pm   ?     0.00s   ?     -
willy    pts/3    -                Sat 2pm 54:02  36.88s 36.88s  BitchX willyke
willy    pts/2    -                Sat 2pm 54:02   0.13s  0.06s  /usr/bin/screen
+ set +x
 
I'm setting two variables now.
This is a string:
And this is a number:
 
I'm giving you back your prompt now.
  • В одном и том же скрипте вы можете включать и выключать отладочный режим столько раз, сколько это необходимо.
set -f  (set -o noglob)  Отключается генерация имени файла с помощью метасимволов (подстановка).
set -v  (set -o verbose) Командная оболочка печатает входные строки сразу, как они считываются.
set -x  (set -o xtrace)  Перед исполнением команды выдаются трассировочные данные.
  • Bombilla amarilla - yellow Edison lamp.pngСимвол "тире" используется для активации параметра командной оболочки, а символ "плюс" - для его деактивации. Не перепутайте это!
  • Кроме того, эти режимы можно указать в самом скрипте, для этого добавьте соответствующие параметры в первую строку, в которой указывается командная оболочка. Параметры можно объединить, что является обычным приемом при использовании команд UNIX:
#!/bin/bash -xv
  • Как только вы обнаружили ошибочный фрагмент в вашем скрипте, вы можете добавить инструкции echo перед каждой командой, в работе которой вы не уверены, и можете увидеть, где именно и почему что-то не работает.
echo "debug message: now attempting to start w command"; w
  • В более сложных скриптах инструкцию echo можно добавлять для отображения значений переменных на различных этапах работы скрипта, что может помочь в обнаружении проблемы:
echo "Variable VARNAME is now set to $VARNAME."

Перехват сигналов вашей командной оболочке Bash

  • Сигнал (signal) -- это просто сообщение, передается процессу либо ядром, либо другим процессом, чтобы побудить процесс выполнить какие либо действия (обычно -- завершить работу). Например, нажатие на Control-C, вызывает передачу сигнала SIGINT, исполняющейся программе.
SIGQUIT
SIGHUP   (1)
SIGINT   (2) 
SIGKILL  (9)
SIGTERM  (15)
SIGTSTOP (12,19,23)
SIGTTOU 
  • Когда отсутствуют какие-либо команды trap, интерактивная оболочка Bash игнорирует сигналы SIGTERM и SIGQUIT.
  • Сигнал SIGINT перехватывается и обрабатывается, и если осуществляется управление заданиями, то также игнорируются сигналы SIGTTIN, SIGTTOU и SIGTSTP.
  • Если эти сигналы поступают от клавиатуры, то команды, участвующие в подстановке команд, также игнорируют эти сигналы.
  • По сигналу SIGHUP по умолчанию происходит выход из командной оболочки. Интерактивная оболочка отправит сигнал SIGHUP всем заданиям, работающим или остановленным; * Если вы хотите отменить такое действие для конкретного процесса, которое задается по умолчанию, смотрите документацию по встроенной команде disown. Во встроенной команде shopt укажите параметр huponexit для уничтожения всех заданий, принимающих сигнал SIGHUP.

Посылаем сигнал в командной оболочке

  • Ctrl+C Сигнал прерывания, отправляет сигнал SIGINT заданию, работающему в приоритетном режиме.
  • Ctrl+Y Сигнал задержанной приостановки. Вызывает остановку работающего процесса, когда он попытается прочитать из терминала входные данные. Управление возвращается в командную оболочку, причем пользовательский процесс может оставаться приоритетным, фоновым или может быть уничтожен. Задержанную приостановку можно использовать только в тех операционных системах, где эта функция поддерживается.
  • Ctrl+Z Сигнал приостановки, посылается SIGTSTP в работающую программу, что ведет к остановке программы и возвращению управления в командную оболочку.

fg и bg

  • Чтобы процесс «растормошить» и запустить обратно, мы можем вывести его на передний план используя команду fg (от англ. foreground — прим. пер.):
#!/bin/bash
 fg
 #(test it out, then stop the process again)
 Control-Z
 #[1]+  Stopped                 xeyes -center red
  • А теперь продолжим его в фоне с помощью команды bg (от англ. backgroud — прим. пер.):
bg
#[1]+ xeyes -center red &
#Прекрасно! Процесс xeyes сейчас запущен в фоновом режиме, а мы снова имеем приглашение bash.

Использование "&"

  • Если нам нужно сразу запустить скрипт или процесс в фоновом режиме (вместо использования Control-Z и bg), мы можем просто добавить "&" (амперсанд) в конец команды
#!/bin/bash
xeyes -center blue &
#[2] 16224

Использование сигналов с функцией kill

  • Чтобы убить, остановить, или продолжить процесс, Linux использует специальную форму взаимодействия, называемую сигналы. Отправляя сигнал некоторому процессу, вы можете его завершить, остановить, или сделать что-нибудь еще. Это то, что происходит на самом деле, когда вы нажимаете Control-C, Control-Z, или используете bg и fg — вы указываете bash отправить процессу определенный сигнал. Сигналы также можно отправить с помощью команды kill указав ей как параметр id процесса (pid):
  • В большинстве современных командных оболочек, к которым относится Bash, есть встроенная функция kill.
  • В Bash в качестве параметров можно указывать :
    • имена и номера сигналов
    • аргументами могут быть задания или идентификаторы процессов.
    • Состояние кода возврата можно получить с помощью параметра -l: ноль, если успешно отправлен хотя бы один сигнал, и не ноль, если произошла ошибка.
  • Когда используется команда kill из /usr/bin в вашей системе, то в команде могут быть дополнительные возможности, позволяющие уничтожать процессы с идентификатором не вашего, а другого пользователя, и указывать процессы по именам, точно также, как в pgrep и pkill.
  • Если ничего не задано, то оба варианта команды kill посылают сигнал TERM.

Bombilla amarilla - yellow Edison lamp.png Сигналы SIGKILL и SIGSTOP нельзя перехватывать, блокировать или игнорировать.

  • Когда уничтожается процесс или серия процессов, разумно начать с попытки использовать менее опасный сигнал SIGTERM. Таким образом, программам, которым важно правильное их завершение, предоставляется шанс следовать процедурам, которые создаются для случаев, когда программы получают сигнал SIGTERM, например, процедурам уборки мусора и закрытия открытых файлов. Если вы посылаете в процесс сигнал SIGKILL, все шансы сделать в этом процессе аккуратную уборку мусора и остановку могут быть потеряны и это может привести к плачевным последствиям.
  • Но если чистое завершение не работает, единственным способом остаются сигналы INT или KILL. Например, когда процесс не уничтожается с помощью нажатия клавиш Ctrl+C, то лучше использовать kill -9 и указать идентификатор процесса:
#!/bin/bash
ps -ef | grep stuck_process
#maud    5607   2214  0 20:05 pts/5    00:00:02 stuck_process
 
kill -9 5607
 
ps -ef | grep stuck_process
#maud    5614    2214 0 20:15 pts/5    00:00:00 grep stuck_process
#[1]+ Killed             stuck_process

Bombilla amarilla - yellow Edison lamp.pngКогда процесс запускает несколько экземпляров, проще воспользоваться командой killall. В ней используются те же самые параметры, как и в команде kill, но она применяется ко всем экземплярам данного процесса. Испытайте эту команду прежде, чем ей воспользоваться на практике, поскольку в некоторых коммерческих версиях UNIX она может работать не так, как ожидается.

Команды Trap

  • Могут быть ситуации, когда вы не захотите, чтобы пользователи ваших скриптов с помощью ввода на клавиатуре специальной последовательности клавиш несвоевременно выходили из скрипта, например, поскольку нужно освободить входной поток или стереть ненужные данные. Инструкция trap перехватывает эти последовательности и ее можно так запрограммировать, что при обнаружении этих сигналов будет выполнен список команд.
  • Синтаксис инструкции trap сравнительно простой:
trap [COMMANDS] [SIGNALS]
  • Команде trap указывается перехватить перечисленные сигналы SIGNALS, которые могут быть именами сигналов с префиксом SIG или без этого префикса, либо номерами сигналов.
  • Если сигнал равен 0 или EXIT, команды COMMANDS выполняются тогда, когда происходит выход в командную оболочку.
  • Если одним из сигналов является сигнал DEBUG, список команд COMMANDS выполняется после выполнения каждой простой команды.
  • Сигнал может быть также определен как ERR, в этом случае команды COMMANDS выполняются каждый раз, когда выход из простой команды происходит с ненулевым кодом возврата.
  • Обратите внимание, что эти команды не будут выполняться, если ненулевой код возврат будет возвращен из части инструкции if или из цикла while или until.
  • Они не будут исполняться даже в случае, когда с помощью логических команд AND (&&) или OR (||) будет возвращен ненулевой код выхода, или когда код возврата команды инвертируется с помощью оператора !.
  • Если спецификации сигнала были указаны правильно, то код возврата самой команды trap равен нулю. В команде trap есть несколько параметров, которые описаны в документации по Bash.
  • Очень простой пример перехвата Ctrl+C, вводимого пользователя, при котором печатается сообщение. При попытке уничтожить эту программу без указания сигнала KILL, ничего происходить не будет.
#!/bin/bash
# traptest.sh
 
trap "echo Booh!" SIGINT SIGTERM
echo "pid is $$"
 
while :                 # This is the same as "while true".
do
        sleep 60        # This script is not really doing anything.
done

Как Bash интерпретирует команду trap

  • Когда во время ожидания завершения команды Bash принимает сигнал, для которого была установлена команда trap, команда trap не будет выполняться до завершения исполняемой команды.
  • Когда Bash с помощью встроенной команды wait ожидает выполнение асинхронной команды, прием сигнала, для которого была задана команда trap, вызовет немедленный выход из встроенной команды wait с кодом возврата, большим 128, а затем сразу будет выполнена команда trap.