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

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

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

Лекции

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

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

Экзамен

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

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

Содержание

Литература

Введение

Название BASH -- это аббревиатура от "Bourne-Again Shell" и игра слов от, ставшего уже классикой, "Bourne Shell" Стефена Бурна (Stephen Bourne). В последние годы BASH достиг такой популярности, что стал стандартной командной оболочкой de facto для многих разновидностей UNIX. Большинство принципов программирования на BASH одинаково хорошо применимы и в других командных оболочках, таких как Korn Shell (ksh), от которой Bash позаимствовал некоторые особенности, [2] и C Shell и его производных. (Примечательно, что C Shell не рекомендуется к использованию из-за отдельных проблем, отмеченных Томом Кристиансеном (Tom Christiansen) в октябре 1993 года на Usenet post

Первый скрипт

  • Создаем файл first_script.sh
  • Даем файлу права на запуск ( chmod 755 first_script.sh )
  • Содержимое файла
#!/bin/bash
echo "hello world"

Заголовок скрипта и комментарии

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

#!/bin/bash
  • Если в сценарии имеются еще такие же строки, то они воспринимаются как обычный комментарий.
#!/bin/bash
echo "Первая часть сценария."
a=1
#!/bin/bash
# Это *НЕ* означает запуск нового сценария.
echo "Вторая часть сценария."
echo $a  # Значение переменной $a осталось равно 1.
  • Эта особенность позволяет использовать различные хитрости.
#!/bin/rm
# Самоуничтожающийся сценарий.
# Этот скрипт ничего не делает -- только уничтожает себя.
WHATEVER=65
echo "Эта строка никогда не будет напечатана."
exit $WHATEVER  # Не имеет смысла, поскольку работа сценария завершается не здесь.

Оператор echo

Для вывода текстовых сообщений или инофрмации в поток вывода используется оператор echo

#!/bin/bash
 
echo "Имя скрипта — \$0"
echo "Первый аргумент: \$1"
echo "Второй аргумент: \${2}"
echo "Семнадцатый аргумент: \${17}"
echo "Количество аргументов: \$#"

Переменные

  • Переменные -- это одна из основ любого языка программирования. Они учавствуют в арифметических операциях, в синтаксическом анализе строк и совершенно необходимы для абстрагирования каких либо величин с помощью символических имен. Физически переменные представляют собой ни что иное как участки памяти, в которые записана некоторая информация.
  • В отличие от большинства других языков программирования, Bash не производит разделения переменных по "типам". По сути, переменные Bash являются строковыми переменными, но, в зависимости от контекста, Bash допускает целочисленную арифметику с переменными. Определяющим фактором здесь служит содержимое переменных.

Бремя отслеживания типа той или иной переменной полностью лежит на плечах программиста. Bash не будет делать это за вас!

  • Для объявления переменной нужно указать ее имя и задать значение.
#!/bin/bash
# Присваивание значений переменным 
a=375
  • Чтобы использовать значение переменной используют специальный символ $
#!/bin/bash
# Присваивание значений переменным и подстановка значений переменных
a=375
hello=$a
  • Использование пробельных символов с обеих сторон символа "=" присваивания недопустимо.
    • Если записать "VARIABLE =value", то интерпретатор попытается выполнить команду "VARIABLE" с параметром "=value".
    • Если записать "VARIABLE= value", то интерпретатор попытается установить переменную окружения "VARIABLE" в "" и выполнить команду "value".
  • Примечательно, что написание $variable фактически является упрощенной формой написания ${variable}
#!/bin/bash
hello='hello'
echo hello    # Это не ссылка на переменную, выведет строку "hello".
echo $hello
echo ${hello} # Идентично предыдущей строке.
echo "$hello"
echo "${hello}"
  • Если в значениях переменных встречаются пробелы, то использование кавычек обязательно.
#!/bin/bash
numbers="один два три"
other_numbers="1 2 3"
echo "numbers = $numbers"
echo "other_numbers = $other_numbers"   # other_numbers = 1 2 3
  • Неинициализированная переменная хранит "пустое" значение - не ноль!. Объявление неинициализированной переменной (то же, что и присваивание пустого значения, см. выше).
#!/bin/bash
echo "uninitialized_variable = $uninitialized_variable"
uninitialized_variable=   
echo "uninitialized_variable = $uninitialized_variable"
uninitialized_variable=23     
echo "uninitialized_variable = $uninitialized_variable"
  • Использование неинициализированных переменных может приводить к ошибкам разного рода в процессе исполнения.
  • Оператор unset используется для того, чтобы инициализированную переменную сделать не инициализированной
#!/bin/bash
uninitialized_variable=23       # Присваивание.
unset uninitialized_variable    # Сброс.
echo "uninitialized_variable = $uninitialized_variable"
                                # Переменная содержит "пустое" значение.

Присваивание значений переменным

  • '=' оператор присваивания (пробельные символы до и после оператора -- недопустимы)
#!/bin/bash
a=879
echo "Значение переменной \"a\" -- $a."
  • Можно использовать присваивание совместно с оператором let
#!/bin/bash
let a=16+5
echo "Значение переменной \"a\" теперь стало равным: $a."
  • Неявное присваивание
#!/bin/bash
echo -n "Значения переменной \"a\" в цикле: "
for a in 7 8 9 11
do
  echo -n "$a "
done
  • Присваивание с оператором read
#!/bin/bash
echo -n "Введите значение переменной \"a\" "
read a
echo "Значение переменной \"a\" теперь стало равным: $a."
  • Присваивание переменным переменных
#!/bin/bash
a=23              # Простейший случай
echo $a
b=$a
echo $b
  • Маскированное присваивание переменных с использованием обратных ковычек `...`
#!/bin/bash
a=`echo Hello!`   # В переменную 'a' попадает результат работы команды 'echo'
echo $a
a=`ls -l`         # В переменную 'a' записывается результат работы команды 'ls -l'
echo $a           # Кавычки отсутствуют, удаляются лишние пробелы и пустые строки.
echo
echo "$a"         # Переменная в кавычках, все пробелы и пустые строки сохраняются.
  • Маскированное присваивание переменных с использованием $(...) (более современный метод, по сравнению с обратными кавычками)
#!/bin/bash
a=$(echo Hello\!)   # В переменную 'a' попадает результат работы команды 'echo'
echo $a

Переменные Bash не имеют типов

  • Тип переменной определяется из контекса
 
#!/bin/bash
a=2334                   # Целое число.
let "a += 1"
echo "a = $a "           # a = 2335
echo                     # Все еще целое число.
 
b=${a/23/BB}             # замена "23" на "BB".
                         # Происходит трансформация числа в строку.
echo "b = $b"            # b = BB35
declare -i b             # Явное указание типа здесь не поможет.
echo "b = $b"            # b = BB35
 
let "b += 1"             # BB35 + 1 =
echo "b = $b"            # b = 1
echo
 
c=BB34
echo "c = $c"            # c = BB34
d=${c/BB/23}             # замена "BB" на "23".
                         # Переменная $d становится целочисленной.
echo "d = $d"            # d = 2334
let "d += 1"             # 2334 + 1 =
echo "d = $d"            # d = 2335
  • Пустая переменная становиться целочисленной
 
#!/bin/bash
e=""
echo "e = $e"            # e =
let "e += 1"             # Арифметические операции допускают использование "пустых" переменных?
echo "e = $e"            # e = 1
  • Необъявленная переменная трансформируется в целочисленную.
#!/bin/bash
echo "f = $f"            # f =
let "f += 1"             # Арифметические операции допустимы?
echo "f = $f"            # f = 1

Использование ковычек в значения переменных

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

(Символ называется "специальным", если он несет дополнительную смысловую нагрузку, например символ шаблона -- *.)

  • Желательно использовать двойные кавычки (" ") при обращении к переменным. Это предотвратит интерпретацию специальных символов, которые могут содержаться в именах переменных, за исключением $, ` (обратная кавычка) и \ (escape -- обратный слэш).
  • То, что символ $ попал в разряд исключений, позволяет выполнять обращение к переменным внутри строк, ограниченных двойными кавычками ("$variable"), т.е. выполнять подстановку значений переменных.
  • Двойные кавычки могут быть использованы для предотвращения разбиения строки на слова. Заключение строки в кавычки приводит к тому, что она передается как один аргумент, даже если она содержит пробельные символы - разделители.
variable1="a variable containing five words"
COMMAND This is $variable1    # Исполнение COMMAND с 7 входными аргументами:
# "This" "is" "a" "variable" "containing" "five" "words"
 
COMMAND "This is $variable1"  # Исполнение COMMAND с одним входным аргументом:
# "This is a variable containing five words"
 
 
variable2=""    # Пустая переменная.
 
COMMAND $variable2 $variable2 $variable2        # Исполнение COMMAND без аргументов.
COMMAND "$variable2" "$variable2" "$variable2"  # Исполнение COMMAND с 3 "пустыми" аргументами.
COMMAND "$variable2 $variable2 $variable2"      # Исполнение COMMAND с 1 аргументом (и 2 пробелами).

Внутренние переменные

  • $BASH путь к исполняемому файлу Bash
  • $BASH_VERSINFO[n] это массив, состоящий из 6 элементов, и содержащий информацию о версии Bash. Очень похожа на переменную $BASH_VERSION, описываемую ниже.
  • $BASH_VERSION версия Bash, установленного в системе. Проверка переменной $BASH_VERSION -- неплохой метод проверки типа командной оболочки, под которой исполняется скрипт. Переменная $SHELL не всегда дает правильный ответ.
  • $DIRSTACK содержимое вершины стека каталогов (который управляется командами pushd и popd. Эта переменная соответствует команде dirs, за исключением того, что dirs показывает полное содержимое всего стека каталогов.
  • $EDITOR заданный по-умолчанию редактор, вызываемый скриптом, обычно vi или emacs.
  • $EUID "эффективный" идентификационный номер пользователя (Effective User ID). Идентификационный номер пользователя, права которого были получены, возможно с помощью команды su.
  • $FUNCNAME имя текущей функции
xyz23 ()
{
  echo "Исполняется функция $FUNCNAME."  # Исполняется функция xyz23.
}
xyz23
echo "FUNCNAME = $FUNCNAME"        # FUNCNAME =
                                   # Пустое (Null) значение за пределеми функций.
  • $GROUPS группы, к которым принадлежит текущий пользователь. Это список групп (массив) идентификационных номеров групп для текущего пользователя, как эо записано в /etc/passwd.
  • $HOME домашний каталог пользователя, как правило это /home/username (см. Пример 9-13)
  • $HOSTNAME Сетевое имя хоста устанавливается командой hostname во время исполнения инициализирующих сценариев на загрузке системы. Внутренняя переменная $HOSTNAME Bash получает свое значение посредством вызова функции gethostname(). См. так же Пример 9-13.
  • $HOSTTYPE тип машины. Подобно $MACHTYPE, идентифицирует аппаратную архитектуру.
  • $IFS разделитель полей во вводимой строке (IFS -- Input Field Separator). По-умолчанию -- пробельный символ (пробел, табуляция и перевод строки), но может быть изменен, например, для разбора строк, в которых отдельные поля разделены запятыми. Обратите внимание: при составлении содержимого переменной $*, Bash использует первый символ из $IFS для разделения аргументов. При всем при том следует помнить, что при использовании $IFS пробельные символы обрабатываются несколько иначе, чем все остальные.
  • $LC_CTYPE Эта внутренняя переменная определяет кодировку символов. Используется в операциях подстановки и поиске по шаблону.
  • $LINENO Номер строки исполняемого сценария. Эта переменная имеет смысл только внутри исполняемого сценария и чаще всего применяется в отладочных целях.
  • $MACHTYPE Идентификатор аппаратной архитектуры.
  • $OSTYPE тип операционной системы
  • $PATH путь поиска, как правило включает в себя каталоги /usr/bin/, /usr/X11R6/bin/, /usr/local/bin, и т.д. Когда командный интерпретатор получает команду, то он автоматически пытается отыскать соответствующий исполняемый файл в указанном списке каталогов (в переменной $PATH). Каталоги, в указанном списке, должны отделяться друг от друга двоеточиями. Обычно, переменная $PATH инициализируется в /etc/profile и/или в ~/.bashrc. Инструкция PATH=${PATH}:/opt/bin добавляет каталог /opt/bin в конец текущего пути поиска. Иногда может оказаться целесообразным, внутри сценария, временно добавить какой-либо каталог к пути поиска. По завершении работы скрипта, эти изменения будут утеряны (вспомните о том, что невозможно изменить переменные окружения вызывающего процесса).

Note Текущий "рабочий каталог", ./, обычно не включается в $PATH из соображений безопасности.

  • $PPID Переменная $PPID хранит PID (идентификатор) родительского процесса.
  • $PS1 приглашение командной строки.
  • $PS2 Вторичное приглашение командной строки, выводится тогда, когда от пользователя ожидается дополнительный ввод. Отображается как ">".
  • $PS3 Третичное приглашение (prompt), выводится тогда, когда пользователь должен сделать выбор в операторе select (см. Пример 10-29).
  • $PS4 Приглашение (prompt) четвертого уровня, выводится в начале каждой строки вывода тогда, когда сценарий вызывается с ключом -x. Отображается как "+".
  • $PWD рабочий (текущий) каталог, аналог встроенной команды pwd.
  • $REPLY переменная по-умолчанию, куда записывается ввод пользователя, выполненный с помощью команды read если явно не задана другая переменная. Так же может использоваться в операторе select, для построения меню выбора.
#!/bin/bash
 
echo
echo -n "Ваше любимое растение? "
read
 
echo "Ваше любимое растение: $REPLY."
# REPLY хранит последнее значение, прочитанное командой "read" тогда, и только тогда
#+ когда команде "read" не передается имя переменной.
 
echo
echo -n "Ваш любимый фрукт? "
read fruit
echo "Ваш любимый фрукт $fruit."
echo "но..."
echo "Значение переменной \$REPLY осталось равным $REPLY."
# Переменная $REPLY не была перезаписана потому, что
# следующей команде "read", в качестве аргумента была передана переменная $fruit
echo
exit 0
  • $SECONDS Время паботы сценария в секундах.
  • $SHELLOPTS список допустимых опций интерпретатора shell. Переменная доступна только для чтения.
  • $SHLVL Уровень вложенности shell. Если в командной строке дает 1, то в сценарии значение этой переменной будет больше на 1, т.е. 2.
  • $TMOUT Если переменная окружения $TMOUT содержит ненулевое значение, то интерпретатор будет ожидать ввод не более чем заданное число секунд, что, в первичном приглашении (см. описание PS1 выше), может привести к автоматическому завершению сеанса работы.
  • Замечание

К сожалению это возможно только во время ожидания ввода с консоли или в окне терминала. А как было бы здорово, если бы можно было использовать эту внутреннюю переменную, скажем в комбинации с командой read! Но в данном контексте эта переменная абсолютно не применима и потому фактически бесполезна в сценариях. (Есть сведения о том, что в ksh время ожидания ввода командой read можно ограничить.)

  • $UID (идентификатор) текущего пользователя, в соответствии с /etc/passwd, Это реальный UID текущего пользователя, даже если он временно приобрел права другого пользователя с помощью su. Переменная $UID доступна только для чтения.
  • Переменные $ENV, $LOGNAME, $MAIL, $TERM, $USER и $USERNAME, не являются встроенными переменными Bash. Тем не менее, они часто инициализируются как переменные окружения в одном из стартовых файлов Bash. Переменная $SHELL, командная оболочка пользователя, может задаваться в /etc/passwd или в сценарии "init" и она тоже не является встроенной переменной Bash.

Позиционные параметры (аргументы)

  • $0, $1, $2 и т.д. аргументы передаются... из командной строки в сценарий, функциям или команде set
  • $# количество аргументов командной строки [2], или позиционных параметров
  • $* Все аргументы в виде одной строки (слова)
  • $@ То же самое, что и $*, но при этом каждый параметр представлен как отдельная строка (слово), т.е. параметры не подвергаются какой либо интерпретации.
Пример 9-6. arglist: Вывод списка аргументов с помощью переменных $* и $@
#!/bin/bash
# Вызовите сценарий с несколькими аргументами, например: "один два три".
E_BADARGS=65
 
if [ ! -n "$1" ]
then
  echo "Порядок использования: `basename $0` argument1 argument2 и т.д."
  exit $E_BADARGS
fi
 
index=1
 
echo "Список аргументов в переменной \"\$*\":"
for arg in "$*"  # Работает некорректно, если "$*" не ограничена кавычками.
do
  echo "Аргумент #$index = $arg"
  let "index+=1"
done             # $* воспринимает все аргументы как одну строку.
echo "Полный список аргументов выглядит как одна строка."
 
echo
 
index=1
 
echo "Список аргументов в переменной \"\$@\":"
for arg in "$@"
do
  echo "Аргумент #$index = $arg"
  let "index+=1"
done             # $@ воспринимает аргументы как отдельные строки (слова).
echo "Список аргументов выглядит как набор различных строк (слов)."
 
echo
 
exit 0
  • После команды shift (сдвиг), первый аргумент, в переменной $@, теряется, а остальные сдвигаются на одну позицию "вниз" (или "влево", если хотите).
#!/bin/bash
# Вызовите сценарий в таком виде: ./scriptname 1 2 3 4 5
 
echo "$@"    # 1 2 3 4 5
shift
echo "$@"    # 2 3 4 5
shift
echo "$@"    # 3 4 5
 
# Каждая из команд "shift" приводит к потере аргумента $1,
# но остальные аргументы остаются в "$@".
  • Специальная переменная $@ может быть использована для выбора типа ввода в сценария. Команда cat "$@" позволяет выполнять ввод как со стандартного устройства ввода stdin, так и из файла, имя которого передается сценарию из командной строки. Переменные $* и $@, в отдельных случаях, могут содержать противоречивую информацию! Это зависит от содержимого переменной $IFS.

Прочие специальные переменные

  • $- Список флагов, переданных сценарию (командой set). Эта конструкция изначально была введена в ksh, откуда перекочевала в Bash и, похоже, работает в Bash не совсем надежно. Единственное возможное применение -- проверка - запущен ли сценарий в интерактивном режиме.
  • $! PID последнего, запущенного в фоне, процесса
  • $_ Специальная переменная, содержит последний аргумент предыдущей команды.
  • $? Код возврата команды, функции или скрипта (см. Пример 22-3)
  • $$ PID самого процесса-сценария. Переменная $$ часто используется при генерации "уникальных" имен для временных файлов (см. Пример A-14, Пример 29-6, Пример 12-23 и Пример 11-23). Обычно это проще чем вызов mktemp.

Проверка условий

  • В Bash, для проверки условий, имеется команда test, различного вида скобочные операторы и условный оператор if/then.
  • Оператор if/then проверяет -- является ли код завершения списка команд 0 (поскольку 0 означает "успех"), и если это так, то выполняет одну, или более, команд, следующие за словом then.
  • Существует специальная команда -- [ (левая квадратная скобка). Она является синонимом команды test, и является встроенной командой (т.е. более эффективной, в смысле производительности). Эта команда воспринимает свои аргументы как выражение сравнения или как файловую проверку и возвращает код завершения в соответствии с результатами проверки (0 -- истина, 1 -- ложь).
#!/bin/bash
echo "Что есть истина"
 
echo "Проверяется \"1\""
if [ 1 ]      # единица
then
  echo "1 -- это истина."
else
  echo "1 -- это ложь."
fi            # 1 -- это ложь.
 
echo
 
echo "Testing \"-1\""
if [ -1 ]     # минус один
then
  echo "-1 -- это истина."
else
  echo "-1 -- это ложь."
fi            # -1 -- это истина.
 
echo
 
echo "Проверяется \"NULL\""
if [ ]        # NULL (пустое условие)
then
  echo "NULL -- это истина."
else
  echo "NULL -- это ложь."
fi            # NULL -- это ложь.
 
echo
 
echo "Проверяется \"xyz\""
if [ xyz ]    # строка
then
  echo "Случайная строка -- это истина."
else
  echo "Случайная строка -- это ложь."
fi            # Случайная строка -- это истина.
 
echo
 
echo "Проверяется \"\$xyz\""
if [ $xyz ]   # Проверка, если $xyz это null, но...
              # только для неинициализированных переменных.
then
  echo "Неинициализированная переменная -- это истина."
else
  echo "Неинициализированная переменная -- это ложь."
fi            # Неинициализированная переменная -- это ложь.
 
echo
 
echo "Проверяется \"-n \$xyz\""
if [ -n "$xyz" ]            # Более корректный вариант.
then
  echo "Неинициализированная переменная -- это истина."
else
  echo "Неинициализированная переменная -- это ложь."
fi            # Неинициализированная переменная -- это ложь.
 
echo
 
 
xyz=          # Инициализирована пустым значением.
 
echo "Проверяется \"-n \$xyz\""
if [ -n "$xyz" ]
then
  echo "Пустая переменная -- это истина."
else
  echo "Пустая переменная -- это ложь."
fi            # Пустая переменная -- это ложь.
  • Начиная с версии 2.02, Bash предоставляет в распоряжение программиста конструкцию ... расширенный вариант команды test, которая выполняет сравнение способом более знакомым программистам, пишущим на других языках программирования.
  • Обратите внимание: [[ -- это зарезервированное слово, а не команда. Bash исполняет $a -lt $b как один элемент, который имеет код возврата.
  • Круглые скобки (( ... )) и предложение let ... так же возвращают код 0, если результатом арифметического выражения является ненулевое значение. Таким образом, арифметические выражения могут участвовать в операциях сравнения.
  • Условный оператор if проверяет код завершения любой команды, а не только результат выражения, заключенного в квадратные скобки.
#!/bin/bash
if cmp a b &> /dev/null  # Подавление вывода.
then echo "Файлы a и b идентичны."
else echo "Файлы a и b имеют различия."
fi
 
if grep -q Bash file
then echo "Файл содержит, как минимум, одно слово Bash."
fi
 
if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
then echo "Команда выполнена успешно."
else echo "Обнаружена ошибка при выполнении команды."
fi
  • Оператор if/then допускает наличие вложенных проверок.
#!/bin/bash
 
if echo "Следующий *if* находится внутри первого *if*."
  if [[ $comparison = "integer" ]]
    then (( a < b ))
  else
    [[ $a < $b ]]
  fi
then
  echo '$a меньше $b'
fi
  • Когда if и then располагаются в одной строке, то конструкция if должна завершаться точкой с запятой. И if, и then -- это зарезервированные слова. Зарезервированные слова начинают инструкцию, которая должна быть завершена прежде, чем в той же строке появится новая инструкция.
if [ -x "$filename" ]; then
  • elif -- это краткая форма записи конструкции else if. Применяется для построения многоярусных инструкций if/then.
#!/bin/bash
if [ condition1 ]
then
   command1
   command2
   command3
elif [ condition2 ]
# То же самое, что и else if
then
   command4
   command5
else
   default-command
fi
  • Команда test -- это встроенная команда Bash, которая выполняет проверки файлов и производит сравнение строк. Таким образом, в Bash-скриптах, команда test не вызывает внешнюю (/usr/bin/test) утилиту, которая является частью пакета sh-utils. Аналогично, [ не производит вызов утилиты /usr/bin/[, которая является символической ссылкой на /usr/bin/test.
bash$ type test
test is a shell builtin
bash$ type '['
[ is a shell builtin
bash$ type '[['
[[ is a shell keyword
bash$ type ']]'
]] is a shell keyword
bash$ type ']'
bash: type: ]: not found
  • Эквиваленты команды test -- /usr/bin/test, [ ], и /usr/bin/[
#!/bin/bash
if test -z "$1"
then
  echo "Аргументы командной строки отсутствуют."
else
  echo "Первый аргумент командной строки: $1."
fi
  • Конструкция [[ ]] более универсальна, по сравнению с [ ]. Этот расширенный вариант команды test перекочевал в Bash из ksh88. Внутри этой конструкции не производится никакой дополнительной интерпретации имен файлов и не производится разбиение аргументов на отдельные слова, но допускается подстановка параметров и команд. Конструкция ... более предпочтительна, нежели [ ... ], поскольку поможет избежать некоторых логических ошибок. Например, операторы &&, ||, < и > внутри [[ ]] вполне допустимы, в то время как внутри [ ] порождают сообщения об ошибках.
#!/bin/bash
file=/etc/passwd
if [[ -e $file ]]
then
  echo "Файл паролей найден."
fi
  • Строго говоря, после оператора if, ни команда test, ни квадратные скобки ( [ ] или [[ ]] ) не являются обязательными. Инструкция "if COMMAND" возвращает код возврата команды COMMAND. Точно так же, условие, находящееся внутри квадратных скобок может быть проверено без использования оператора if.
dir=/home/bozo
if cd "$dir" 2>/dev/null; then   # "2>/dev/null" подавление вывода сообщений об ошибках.
  echo "Переход в каталог $dir выполнен."
else
  echo "Невозможно перейти в каталог $dir."
fi
 
var1=20
var2=22
[ "$var1" -ne "$var2" ] && echo "$var1 не равно $var2"
home=/home/bozo
[ -d "$home" ] || echo "каталог $home не найден."
  • Внутри (( )) производится вычисление арифметического выражения. Если результатом вычислений является ноль, то возвращается 1, или "ложь". Ненулевой результат дает код возврата 0, или "истина". То есть полная противоположность инструкциям test и [ ], обсуждавшимся выше.
#!/bin/bash
# Проверка арифметических выражений.
 
# Инструкция (( ... )) вычисляет арифметические выражения.
# Код возврата противоположен коду возврата инструкции [ ... ] !
 
(( 0 ))
echo "Код возврата \"(( 0 ))\":  $?."         # 1
 
(( 1 ))
echo "Код возврата \"(( 1 ))\":  $?."         # 0
 
(( 5 > 4 ))                                   # true
echo "Код возврата \"(( 5 > 4 ))\":  $?."     # 0
 
(( 5 > 9 ))                                   # false
echo "Код возврата \"(( 5 > 9 ))\":  $?."     # 1
 
(( 5 - 5 ))                                   # 0
echo "Код возврата \"(( 5 - 5 ))\":  $?."     # 1
 
(( 5 / 4 ))                                   # Деление, все в порядке
echo "Код возврата \"(( 5 / 4 ))\":  $?."     # 0
 
(( 1 / 2 ))                                   # Результат деления < 1.
echo "Код возврата \"(( 1 / 2 ))\":  $?."     # Округляется до 0.
                                              # 1
 
(( 1 / 0 )) 2>/dev/null                       # Деление на 0.
echo "Код возврата \"(( 1 / 0 ))\":  $?."     # 1
 
# Для чего нужна инструкция "2>/dev/null" ?
# Что произойдет, если ее убрать?
# Попробуйте убрать ее и выполнить сценарий.
 
exit 0

Операции проверки файлов

  • -a file

истинно если файл существует.

  • -d file

истинно если файл существует и является директорией.

  • -f file

истинно если файл существует и является обычным файлом.

  • -r file

истинно если файл существует и доступен для чтения.

  • -s file

истинно если файл существует и его размер больше 0.

  • -w file

истинно если файл существует и доступен для записи.

  • -x file

истинно если файл существует и является исполняемым.

  • file1 -nt file2

истинно если файл file1 новее чем file2 или file1 (в соответствии со временем последнего изменения) существует, а file2 нет.

  • file1 -ot file2

истинно если файл file1 старше чем file2 или file2 существует, а file1 нет.

  • file1 -ef file2
истинно если оба файла ссылаются на одно и то же устройство или инод.
#!/bin/bash
    if [ -d $directory ]
        then linkchk $directory
        else
            echo "$directory не является каталогом"
            echo "Порядок использования: $0 dir1 dir2 ..."
    fi

Сравнение целых чисел

  • -eq равно

if [ "$a" -eq "$b" ]

  • -ne не равно

if [ "$a" -ne "$b" ]

  • -gt больше

if [ "$a" -gt "$b" ]

  • -ge больше или равно

if [ "$a" -ge "$b" ]

  • -lt меньше

if [ "$a" -lt "$b" ]

  • -le меньше или равно

if [ "$a" -le "$b" ]

  • < меньше (внутри двойных круглых скобок )

(("$a" < "$b"))

  • <= меньше или равно (внутри двойных круглых скобок)

(("$a" <= "$b"))

  • > больше (внутри двойных круглых скобок)

(("$a" > "$b"))

>= больше или равно (внутри двойных круглых скобок)

(("$a" >= "$b"))

Операции сравнения строк

  • -z string

истинно если строка имеет нулевую длину.

  • -n string

истинно если длина строки не нулевая.

  • string1 = string2

истинно если строки равны.

  • string1 != string2

истинно если не равны.

  • string1 < string2

истинно если строка 1 стоит в алфавитном порядке перед строкой 2.

  • string1 > string2

истинно если строка 1 стоит в алфавитном порядке после строки 2.

  • = равно

if [ "$a" = "$b" ]

  • == равно (Синоним оператора =).

if [ "$a" == "$b" ] $a == z* # истина, если $a начинается с символа "z" (сравнение по шаблону) $a == "z*" # истина, если $a равна z* [ $a == z* ] # имеют место подстановка имен файлов и разбиение на слова [ "$a" == "z*" ] # истина, если $a равна z*

  •  != не равно

if [ "$a" != "$b" ] Этот оператор используется при поиске по шаблону внутри ... .

  • < меньше, в смысле величины ASCII-кодов

if [[ "$a" < "$b" ]] if [ "$a" \< "$b" ] Обратите внимание! Символ "<" необходимо экранировать внутри [ ].

  • > больше, в смысле величины ASCII-кодов

if [[ "$a" > "$b" ]] if [ "$a" \> "$b" ] Обратите внимание! Символ ">" необходимо экранировать внутри [ ].

  • Оператор -n требует, чтобы строка была заключена в кавычки внутри квадратных скобок. Как правило, проверка строк, не заключенных в кавычки, оператором ! -z, или просто указание строки без кавычек внутри квадратных скобок, проходит нормально, однако это небезопасная, с точки зрения отказоустойчивости. Всегда заключайте проверяемую строку в кавычки.

Операторы

  • + сложение
  • - вычитание
  • '*' умножение
  • / деление
  • '**' В Bash, начиная с версии 2.02, был введен оператор возведения в степень -- "**".
let "z=5**3"
echo "z = $z"   # z = 125
  •  % модуль (деление по модулю), возвращает остаток от деления
  • += "плюс-равно" (увеличивает значение переменной на заданное число)

let "var += 5" значение переменной var будет увеличено на 5.

  • -= "минус-равно" (уменьшение значения переменной на заданное число)
  • *= "умножить-равно" (умножить значение переменной на заданное число, результат записать в переменную)

let "var *= 4" значение переменной var будет увеличено в 4 раза.

  • /= "слэш-равно" (уменьшение значения переменной в заданное число раз)
  •  %= "процент-равно" (найти остаток от деления значения переменной на заданное число, результат записать в переменную)

Арифметические операторы очень часто используются совместно с командами expr и let.


Битовые операции

  • << сдвигает на 1 бит влево (умножение на 2)
  • <<= "сдвиг-влево-равно"

let "var <<= 2" значение переменной var сдвигается влево на 2 бита (умножается на 4)

  • >> сдвиг вправо на 1 бит (деление на 2)
  • >>= "сдвиг-вправо-равно" (имеет смысл обратный <<=)
  • & по-битовое И (AND)
  • &= "по-битовое И-равно"
  • | по-битовое ИЛИ (OR)
  • |= "по-битовое ИЛИ-равно"
  • ~ по-битовая инверсия
  •  ! По-битовое отрицание
  • ^ по-битовое ИСКЛЮЧАЮЩЕЕ ИЛИ (XOR)
  • ^= "по-битовое ИСКЛЮЧАЮЩЕЕ-ИЛИ-равно"

Логические операции

  • && логическое И (and) оператор &&, в зависимости от контекста, может так же использоваться в И-списках для построения составных команд.
if [ $condition1 ] && [ $condition2 ]
# То же самое, что:  if [ $condition1 -a $condition2 ]
# Возвращает true если оба операнда condition1 и condition2 истинны...
 
if [[ $condition1 && $condition2 ]]    # То же верно
# Обратите внимание: оператор && не должен использоваться внутри [ ... ].
  • || логическое ИЛИ (or)
if [ $condition1 ] || [ $condition2 ]
# То же самое, что:  if [ $condition1 -o $condition2 ]
# Возвращает true если хотя бы один из операндов истинен...
 
if [[ $condition1 || $condition2 ]]    # Also works.
# Обратите внимание: оператор || не должен использоваться внутри [ ... ].

Прочие операции

  • , запятая

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

let "t1 = ((5 + 3, 7 - 1, 15 - 4))"
echo "t1 = $t1"               # t1 = 11
 
let "t2 = ((a = 9, 15 / 3))"  #  Выполняется присваивание "a" = 9,
                              #+ а затем вычисляется "t2".
echo "t2 = $t2    a = $a"     # t2 = 5    a = 9

Арифметические подстановки

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

  • Арифметические подстановки в обратных одиночных кавычках (часто используются совместно с командой expr)
z=`expr $z + 3`            # Команда 'expr' вычисляет значение выражения.
  • В арифметических подстановках, обратные одиночные кавычки могут быть заменены на двойные круглые скобки $((...)) или очень удобной конструкцией, с применением предложения let.
z=$(($z+3))
# $((EXPRESSION)) -- это подстановка арифметического выражения.  #  Не путайте с
                                                                 #+ подстановкой команд.
let z=z+3
let "z += 3"  # Кавычки позволяют вставлять пробелы и специальные операторы.
#  Оператор 'let' вычисляет арифметическое выражение,
#+ это не подстановка арифметического выражения.
  • Все вышеприведенные примеры эквивалентны. Вы можете использовать любую из этих форм записи "по своему вкусу".

Код завершения и возвращаемое значение

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

Функции

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

function function_name {
 command...
}
 
function_name () {
 command...
}
#!/bin/bash
 
funky ()
{
  echo "Это обычная функция."
} # Функция должна быть объявлена раньше, чем ее можно будет использовать.
 
  # Вызов функции.
 
funky
 
exit 0

Псевдонимы

  • Псевдонимы в Bash -- это ни что иное, как "горячие клавиши", средство, позволяющее избежать набора длинных строк в командной строке. Если, к примеру, в файл ~/.bashrc вставить строку alias lm="ls -l | more", то потом вы сможете экономить свои силы и время, набирая команду lm, вместо более длинной ls -l | more. Установив alias rm="rm -i" (интерактивный режим удаления файлов), вы сможете избежать многих неприятностей, потому что сократится вероятность удаления важных файлов по неосторожности.
  • Псевдонимы в сценариях могут иметь весьма ограниченную область применения. Было бы здорово, если бы псевдонимы имели функциональность, присущую макроопределениям в языке C, но, к сожалению, Bash не может "разворачивать" аргументы в теле псевдонима. [1] Кроме того, попытка обратиться к псевдониму, созданному внутри "составных конструкций", таких как if/then, циклы и функции, будет приводить к появлению ошибок. Практически всегда, действия, возлагаемые на псевдоним, более эффективно могут быть выполнены с помощью функций.
#!/bin/bash
shopt -s expand_aliases
# Эта опция должна быть включена, иначе сценарий не сможет "разворачивать" псевдонимы.
alias ll="ls -l"
# В определении псевдонима можно использовать как одиночные ('), так и двойные (") кавычки.
echo "Попытка обращения к псевдониму \"ll\":"
ll /usr/X11R6/bin/mk*   #* Работает.
echo
directory=/usr/X11R6/bin/
prefix=mk*  # Определить -- не будет ли проблем с шаблонами.
echo "Переменные \"directory\" + \"prefix\" = $directory$prefix"
echo
alias lll="ls -l $directory$prefix"
echo "Попытка обращения к псевдониму \"lll\":"
lll         # Список всех файлов в /usr/X11R6/bin, чьи имена начинаются с mk.

Циклы

Цикл -- это блок команд, который исполняется многократно до тех пор, пока не будет выполнено условие выхода из цикла.

циклы for

  • for (in) Это одна из основных разновидностей циклов. И она значительно отличается от аналога в языке C. Элементы списка желательно заключать в кавычки для того, чтобы предотвратить возможное разбиение их на отдельные аргументы (слова).
#!/bin/bash
# Список планет.
for planet in Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон
do
  echo $planet
done
 
# Если 'список аргументов' заключить в кавычки, то он будет восприниматься как единственный аргумент .
for planet in "Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон"
do
  echo $planet
done
 
exit 0

Цикл while

Оператор while проверяет условие перед началом каждой итерации и если условие истинно (если код возврата равен 0), то управление передается в тело цикла. В отличие от циклов for, циклы while используются в тех случаях, когда количество итераций заранее не известно.

while [condition]
do
 command...
done

Как и в случае с циклами for/in, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ ";" перед do. while [condition] ; do

Обратите внимание: в отдельных случаях, таких как использование конструкции getopts совместно с оператором while, синтаксис несколько отличается от приводимого здесь.


#!/bin/bash
var0=0
LIMIT=10
while [ "$var0" -lt "$LIMIT" ]
do
  echo -n "$var0 "        # -n подавляет перевод строки.
  var0=`expr $var0 + 1`   # допускается var0=$(($var0+1)).
done
echo
exit 0
#!/bin/bash
while [ "$var1" != "end" ]     # возможна замена на while test "$var1" != "end"
do
  echo "Введите значение переменной #1 (end - выход) "
  read var1                    # Конструкция 'read $var1' недопустима (почему?).
  echo "переменная #1 = $var1" # кавычки обязательны, потому что имеется символ "#".
  # Если введено слово 'end', то оно тоже выводится на экран.
  # потому, что проверка переменной выполняется в начале итерации (перед вводом).
  echo
done

Цикл until

Оператор цикла until проверяет условие в начале каждой итерации, но в отличие от while итерация возможна только в том случае, если условие ложно.
until [condition-is-true]
do
 command...
done

Обратите внимание: оператор until проверяет условие завершения цикла ПЕРЕД очередной итерацией, а не после, как это принято в некоторых языках программирования. Как и в случае с циклами for/in, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ ";" перед do.

until [condition-is-true] ; do

#!/bin/bash
until [ "$var1" = end ] # Проверка условия производится в начале итерации.
do
  echo "Введите значение переменной #1 "
  echo "(end - выход)"
  read var1
  echo "значение переменной #1 = $var1"
done  
exit 0

Управление ходом выполнения цикла

Для управления ходом выполнения цикла служат команды break и continue и точно соответствуют своим аналогам в других языках программирования. Команда break прерывает исполнение цикла, в то время как continue передает управление в начало цикло, минуя все последующие команды в теле цикла.

#!/bin/bash
 
LIMIT=19  # Верхний предел
echo "Печать чисел от 1 до 20 (исключая 3 и 11)."
a=0
 
while [ $a -le "$LIMIT" ]
do
 a=$(($a+1))
 
 if [ "$a" -eq 3 ] || [ "$a" -eq 11 ]  # Исключить 3 и 11
 then
   continue  # Переход в начало цикла.
 fi
 
 echo -n "$a "
done
 
# Почему число 20 тоже выводится?
echo Печать чисел от 1 до 20, но взгляните, что происходит после вывода числа 2

Команде break может быть передан необязательный параметр. Команда break без параметра прерывает тот цикл, в который она вставлена, а break N прерывает цикл, стоящий на N уровней выше (причем 1-й уровень -- это уровень текущего цикла).

#!/bin/bash
# break-levels.sh: Прерывание циклов.
# "break N" прерывает исполнение цикла, стоящего на N уровней выше текущего.
for outerloop in 1 2 3 4 5
do
  echo -n "Группа $outerloop:   "
  for innerloop in 1 2 3 4 5
  do
    echo -n "$innerloop "
    if [ "$innerloop" -eq 3 ]
    then
      break  # Попробуйте "break 2",
             # тогда будут прерываться как вложенный, так и внешний циклы
    fi
  done
  echo
done  
exit 0

Операторы выбора

  • Инструкции case и select технически не являются циклами, поскольку не предусматривают многократное исполнение блока кода. Однако, они, как и циклы, управляют ходом исполнения программы, в зависимости от начальных или конечных условий. Конструкция case эквивалентна конструкции switch в языке C/C++. Она позволяет выполнять тот или иной участок кода, в зависимости от результатов проверки условий. Она является, своего рода, краткой формой записи большого количества операторов if/then/else и может быть неплохим инструментом при создании разного рода меню.

CASE

case "$variable" in
 "$condition1" )
 command...
 ;;
 "$condition2" )
 command...
 ;;
esac
  • Заключать переменные в кавычки необязательно, поскольку здесь не производится разбиения на отдельные слова.
  • Каждая строка с условием должна завершаться правой (закрывающей) круглой скобкой ).
  • Каждый блок команд, отрабатывающих по заданному условию, должен завершаться двумя символами точка-с-запятой ;;.
  • Блок case должен завершаться ключевым словом esac (case записанное в обратном порядке).
#!/bin/bash
 
# Грубый пример базы данных
 
clear # Очистка экрана
 
echo "          Список"
echo "          ------"
echo "Выберите интересующую Вас персону:"
echo
echo "[E]vans, Roland"
echo "[J]ones, Mildred"
echo "[S]mith, Julie"
echo "[Z]ane, Morris"
echo
 
read person
 
case "$person" in
# Обратите внимание: переменная взята в кавычки.
 
  "E" | "e" )
  # Пользователь может ввести как заглавную, так и строчную букву.
  echo
  echo "Roland Evans"
  echo "4321 Floppy Dr."
  echo "Hardscrabble, CO 80753"
  echo "(303) 734-9874"
  echo "(303) 734-9892 fax"
  echo "revans@zzy.net"
  echo "Старый друг и партнер по бизнесу"
  ;;
# Обратите внимание: блок кода, анализирующий конкретный выбор, завершается
# двумя символами "точка-с-запятой".
 
  "J" | "j" )
  echo
  echo "Mildred Jones"
  echo "249 E. 7th St., Apt. 19"
  echo "New York, NY 10009"
  echo "(212) 533-2814"
  echo "(212) 533-9972 fax"
  echo "milliej@loisaida.com"
  echo "Подружка"
  echo "День рождения: 11 февраля"
  ;;
 
# Информация о Smith и Zane будет добавлена позднее.
 
          * )
   # Выбор по-умолчанию.
   # "Пустой" ввод тоже обрабатывается здесь.
   echo
   echo "Нет данных."
  ;;
 
esac
 
exit 0

#! /bin/bash
 
case "$1" in
"") echo "Порядок использования: ${0##*/} <filename>"; exit 65;;  # Параметры командной строки отсутствуют,
                                                  # или первый параметр -- "пустой".
# Обратите внимание на ${0##*/} это подстановка параметра ${var##pattern}. В результате получается $0.
 
-*) FILENAME=./$1;;   # Если имя файла (аргумент $1) начинается с "-",
                      # то заменить его на ./$1
                      # тогда параметр не будет восприниматься как ключ команды.
 
* ) FILENAME=$1;;     # В противном случае -- $1.
esac

SELECT

  • Оператор select был заимствован из Korn Shell, и является еще одним инструментом, используемым при создании меню. Этот оператор предлагает пользователю выбрать один из представленных вариантов. Примечательно, что select по-умолчанию использует в качестве приглашения к вводу (prompt) -- PS3 (#? ), который легко изменить.
  • Если в операторе select список in list не задан, то в качестве списка будет использоваться список аргументов ($@), передаваемый сценарию или функции.
select variable [in list]
do
 command...
 break
done
#!/bin/bash
# Создание меню с помощью select
PS3='Выберите ваш любимый овощ: ' # строка приглашения к вводу (prompt)
echo
select vegetable in "бобы" "морковь" "картофель" "лук" "брюква"
do
  echo
  echo "Вы предпочитаете $vegetable."
  echo ";-))"
  echo
  break  # если 'break' убрать, то получится бесконечный цикл.
done
exit 0