Искусство программирования на языке сценариев командной оболочки

         

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


Часть 1. Введение

Shell -- это командная оболочка. Но это не просто промежуточное звено между пользователем и операционой системой, это еще и мощный язык программирования. Программы на языке shell называют сценариями, или скриптами. Фактически, из скриптов доступен полный набор команд, утилит и программ UNIX. Если этого недостаточно, то к вашим услугам внутренние команды shell -- условные операторы, операторы циклов и пр., которые увеличивают мощь и гибкость сценариев. Shell-скрипты исключительно хороши при программировании задач администрирования системы и др., которые не требуют для своего создания полновесных языков программирования.

Содержание

1. Зачем необходимо знание языка Shell?

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

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

2.2. Упражнения

Глава 1. Зачем необходимо знание языка Shell?

Знание языка командной оболочки является залогом успешного решения задач администрирования системы. Даже если вы не предполагаете заниматься написанием своих сценариев. Во время загрузки Linux выполняется целый ряд сценариев из /etc/rc.d, которые настраивают конфигурацию операционной системы и запускают различные сервисы, поэтому очень важно четко понимать эти скрипты и иметь достаточно знаний, чтобы вносить в них какие либо изменения.

Язык сценариев легок в изучении, в нем не так много специфических операторов и конструкций. [1] Синтаксис языка достаточно прост и прямолинеен, он очень напоминает команды, которые приходится вводить в командной строке. Короткие скрипты практически не нуждаются в отладке, и даже отладка больших скриптов отнимает весьма незначительное время.

Shell-скрипты очень хорошо подходят для быстрого создания прототипов сложных приложений, даже не смотря на ограниченный набор языковых конструкций и определенную "медлительность". Такая метода позволяет детально проработать структуру будущего приложения, обнаружить возможные "ловушки" и лишь затем приступить к кодированию на C, C++, Java, или Perl.

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



Для каких задач неприменимы скрипты

для ресурсоемких задач, особенно когда важна скорость исполнения (поиск, сортировка и т.п.)

для задач, связанных с выполнением математических вычислений, особенно это касается вычислений с плавающей запятой, вычислений с повышенной точностью, комплексных чисел (для таких задач лучше использовать C++ или FORTRAN)

для кросс-платформенного программирования (для этого лучше подходит язык C)

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

для целевых задач, от которых может зависеть успех предприятия.

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

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

для задач, выполняющих огромный объем работ с файлами

для задач, работающих с многомерными массивами

когда необходимо работать со структурами данных, такими как связанные списки или деревья

когда необходимо предоставить графический интерфейс с пользователем (GUI)

когда необходим прямой доступ к аппаратуре компьютера

когда необходимо выполнять обмен через порты ввода-вывода или сокеты

когда необходимо использовать внешние библиотеки

для проприетарных, "закрытых" программ (скрипты представляют из себя исходные тексты программ, доступные для всеобщего обозрения)

Если выполняется хотя бы одно из вышеперечисленных условий, то вам лучше обратиться к более мощным скриптовым языкам программирования, например Perl, Tcl, Python, Ruby или к высокоуровневым компилирующим языкам -- C, C++ или Java. Но даже в этом случае, создание прототипа приложения на языке shell может существенно облегчить разработку.



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

Пример 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, то это указывает системе какой интерпретатор следует использовать для исполнения сценария. Это двухбайтовая последовательность, или [4] -- специальный маркер, определяющий тип сценария, в данном случае -- сценарий командной оболочки (см. man magic). Более точно, sha-bang определяет интерпретатор, который вызывается для исполнения сценария, это может быть командная оболочка (shell), иной интерпретатор или утилита. [5]

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

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

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

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

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

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

<


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

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

Это:

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

или

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

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

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

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

2.2. Упражнения

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

Напишите сценарий, который выводит дату, время, список зарегистрировавшихся пользователей, и uptime системы и сохраняет эту информацию в системном журнале.

Часть 2. Основы

Содержание

3. Служебные символы

4. Переменные и параметры. Введение.

4.1. Подстановка переменных

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

4.3. Переменные Bash не имеют типа

4.4. Специальные типы переменных

5. Кавычки

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

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

7.1. Конструкции проверки условий

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

7.3. Операции сравнения

7.4. Вложенные условные операторы if/then

7.5. Проверка степени усвоения материала



8. Операции и смежные темы

8.1. Операторы

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

Глава 3. Служебные символы

Служебные символы, используемые в текстах сценариев.

#

Комментарии. Строки, начинающиеся с символа # (за исключением комбинации #!) -- являются комментариями.

# Эта строка -- комментарий.

Комментарии могут располагаться и в конце строки с исполняемым кодом.

echo "Далее следует комментарий." # Это комментарий.

Комментариям могут предшествовать пробелы (пробел, табуляция).

# Перед комментарием стоит символ табуляции.

Исполняемые команды не могут следовать за комментарием в той же самой строке. Пока что еще не существует способа отделения комментария от "исполняемого кода", следующего за комментарием в той же строке.

Само собой разумеется, экранированный символ # в операторе echo не воспринимается как начало комментария. Более того, он может использоваться в операциях подстановки параметров и в константных числовых выражениях.

echo "Символ # не означает начало комментария." echo 'Символ # не означает начало комментария.' echo Символ \# не означает начало комментария. echo А здесь символ # означает начало комментария.

echo ${PATH#*:} # Подстановка -- не комментарий. echo $(( 2#101011 )) # База системы счисления -- не комментарий.

# Спасибо, S.C.

Кавычки " ' и \ экранируют действие символа #.

В операциях поиска по шаблону символ # так же не воспринимается как начало комментария.

;

Разделитель команд.

[Точка-с-запятой] Позволяет записывать две и более команд в одной строке.

echo hello; echo there

Следует отметить, что символ ";" иногда так же как и # необходимо экранировать.

;;

Ограничитель в операторе выбора case .

[Двойная-точка-с-запятой]

case "$variable" in abc) echo "$variable = abc" ;; xyz) echo "$variable = xyz" ;; esac

.

команда "точка".

Эквивалент команды source (см. Пример 11-18). Это встроенная команда bash.

.

"точка" может являться частью имени файла . Если имя файла начинается с точки, то это "скрытый" файл, т.е.


команда ls при обычных условиях его не отображает.

bash$ touch .hidden-file

bash$ ls -l

total 10 -rw-r--r-- 1 bozo 4034 Jul 18 22:04 data1.addressbook -rw-r--r-- 1 bozo 4602 May 25 13:58 data1.addressbook.bak -rw-r--r-- 1 bozo 877 Dec 17 2000 employment.addressbook

bash$ ls -al

total 14 drwxrwxr-x 2 bozo bozo 1024 Aug 29 20:54 ./ drwx------ 52 bozo bozo 3072 Aug 29 20:51 ../ -rw-r--r-- 1 bozo bozo 4034 Jul 18 22:04 data1.addressbook -rw-r--r-- 1 bozo bozo 4602 May 25 13:58 data1.addressbook.bak -rw-r--r-- 1 bozo bozo 877 Dec 17 2000 employment.addressbook -rw-rw-r-- 1 bozo bozo 0 Aug 29 20:54 .hidden-file

Если подразумевается имя каталога, то одна точка означает текущий каталог и две точки -- каталог уровнем выше, или родительский каталог.

bash$ pwd

/home/bozo/projects

bash$ cd .

bash$ pwd

/home/bozo/projects

bash$ cd ..

bash$ pwd

/home/bozo/

Символ точка довольно часто используется для обозначения каталога назначения в операциях копирования/перемещения файлов.

bash$ cp /home/bozo/current_work/junk/* .

.

Символ "точка" в операциях поиска. При выполнении поиска по шаблону , в регулярных выражениях, символ "точка" обозначает одиночный символ.

"

Двойные кавычки . В строке "STRING", ограниченной двойными кавычками не выполняется интерпретация большинства служебных символов, которые могут находиться в строке. см. Глава 5.

'

Одинарные кавычки .

[Одинарные кавычки] 'STRING'

экранирует все служебные символы в строке STRING. Это более строгая форма экранирования. Смотрите так же Глава 5.

,

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

let "t2 = ((a = 9, 15 / 3))" # Присваивает значение переменной "a" и вычисляет "t2".

\

escape. [обратный слэш] Комбинация \X "экранирует" символ X. Аналогичный эффект имеет комбинация с "одинарными кавычками", т.е. 'X'.


Символ \ может использоваться для экранирования кавычек " и '.

Более детальному рассмотрению темы экранирования посвящена Глава 5.

/

Разделитель, используемый в указании пути к каталогам и файлам. [слэш] Отделяет элементы пути к каталогам и файлам (например /home/bozo/projects/Makefile).

В арифметических операциях -- это оператор деления.

`

Подстановка команд. [обратные кавычки] Обратные кавычки могут использоваться для записи в переменную команды `command`.

:

пустая команда. [двоеточие] Это эквивалент операции "NOP" (no op, нет операции). Может рассматриваться как синоним встроенной команды true. Команда ":" так же является встроенной командой Bash, которая всегда возвращает "true" (0).

: echo $? # 0

Бесконечный цикл:

while : do operation-1 operation-2 ... operation-n done

# То же самое: # while true # do # ... # done

Символ-заполнитель в условном операторе if/then:

if condition then : # Никаких действий не производится и управление передается дальше else take-some-action fi

Как символ-заполнитель в операциях, которые предполагают наличие двух операндов, см. Пример 8-2 и параметры по-умолчанию.

: ${username=`whoami`} # ${username=`whoami`} без символа : выдает сообщение об ошибке, # если "username" не является командой...

Как символ-заполнитель для оператора вложенного документа. См. Пример 17-9.

В операциях с подстановкой параметров (см. Пример 9-13).

: ${HOSTNAME?} ${USER?} ${MAIL?} #Вывод сообщения об ошибке, если одна или более переменных не определены.

В операциях замены подстроки с подстановкой значений переменных.

В комбинации с оператором > (оператор перенаправления вывода), усекает длину файла до нуля. Если указан несуществующий файл -- то он создается.

: > data.xxx # Файл "data.xxx" -- пуст

# Тот же эффект имеет команда cat /dev/null >data.xxx # Однако в данном случае не производится создание нового процесса, поскольку ":" является встроенной командой.



См. так же Пример 12-11.

В комбинации с оператором >> -- оператор перенаправления с добавлением в конец файла и обновлением времени последнего доступа (: >> new_file). Если задано имя несуществующего файла, то он создается. Эквивалентно команде touch.

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

Символ : может использоваться для создания комментариев, хотя и не рекомендуется. Если строка комментария начинается с символа #, то такая строка не проверяется интерпретатором на наличие ошибок. Однако в случае оператора : это не так.

: Это комментарий, который генерирует сообщение об ошибке, ( if [ $x -eq 3] ).

Символ ":" может использоваться как разделитель полей в /etc/passwd и переменной $PATH.

bash$ echo $PATH

/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/sbin:/usr/sbin:/usr/games

!

инверсия (или логическое отрицание) используемое в условных операторах. Оператор ! инвертирует код завершения команды, к которой он применен. (см. Пример 6-2). Так же используется для логического отрицания в операциях сравнения, например, операция сравнения "равно" ( = ), при использовании оператора отрицания, преобразуется в операцию сравнения -- "не равно" ( != ). Символ ! является зарезервированным ключевым словом BASH.

В некоторых случаях символ ! используется для косвенного обращения к переменным.

Кроме того, из командной строки оператор ! запускает механизм историй Bash (см. Приложение F). Примечательно, что этот механизм недоступен из сценариев (т.е. исключительно из командной строки).

*

символ-шаблон. [звездочка] Символ * служит "шаблоном" для подстановки в имена файлов. Одиночный символ * означает любое имя файла в заданном каталоге.

bash$ echo *

abs-book.sgml add-drive.sh agram.sh alias.sh

В регулярных выражениях токен * представляет любое количество (в том числе и 0) символов.

*

арифметический оператор.

В арифметических выражениях символ * обозначает операцию умножения.



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

?

Оператор проверки условия. В некоторых выражениях символ ? служит для проверки выполнения условия.

В конструкциях с двойными скобками, символ ? подобен трехместному оператору языка C. См. Пример 9-28.

В выражениях с подстановкой параметра, символ ? проверяет -- установлена ли переменная.

?

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

$

Подстановка переменной.

var1=5 var2=23skidoo

echo $var1 # 5 echo $var2 # 23skidoo

Символ $, предшествующий имени переменной, указывает на то, что будет получено значение переменной.

$

end-of-line (конец строки). В регулярных выражениях, символ "$" обозначает конец строки.

${}

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

$*, $@

параметры командной строки.

$?

код завершения. Переменная $? хранит код завершения последней выполненной команды, функции или сценария.

$$

id процесса. Переменная $$ хранит id процесса

сценария.

()

группа команд.

(a=hello; echo $a)

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

Переменные, создаваемые в дочернем процессе не видны в "родительском" сценарии. Родительский процесс-сценарий, не может обращаться к переменным, создаваемым в дочернем процессе.

a=123 ( a=321; )

echo "a = $a" # a = 123 # переменная "a" в скобках подобна локальной переменной.

инициализация массивов.

Array=(element1 element2 element3)

{xxx,yyy,zzz,...}

Фигурные скобки.

grep Linux file*.{txt,htm*} # Поиск всех вхождений слова "Linux" # в файлах "fileA.txt", "file2.txt", "fileR.html", "file-87.htm", и пр.

Команда интерпретируется как список команд, разделенных точкой с запятой, с вариациями, представленными в фигурных скобках. [11] При интерпретации имен файлов (подстановка) используются параметры, заключенные в фигурные скобки.



Поэтому ссылки на переменные называются подстановкой переменных.

$

Необходимо всегда помнить о различиях между именем переменной и ее значением. Если variable1 -- это имя переменной, то $variable1

-- это ссылка на ее значение. "Чистые" имена переменных, без префикса $, могут использоваться только при объявлении переменный, при присваивании переменной некоторого значения, при удалении (сбросе), при экспорте и в особых случаях -- когда переменная представляет собой название сигнала (см. Пример 29-5). Присваивание может производится с помощью символа = (например: var1=27), инструкцией read и в заголовке цикла (for var2 in 1 2 3).

Заключение ссылки на переменную в двойные кавычки (" ") никак не сказывается на работе механизма подстановки. Этот случай называется "частичные кавычки", иногда можно встретить название "нестрогие кавычки". Одиночные кавычки (' ') заставляют интерпретатор воспринимать ссылку на переменную как простой набор символов, потому в одинарных кавычках операции подстановки не производятся. Этот случай называется "полные", или "строгие" кавычки. Дополнительную информацию вы найдете в Глава 5.

Примечательно, что написание $variable

фактически является упрощенной формой написания ${variable}. Более строгая форма записи ${variable}

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

Пример 4-1. Присваивание значений переменным и подстановка значений переменных

#!/bin/bash

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

a=375 hello=$a

#------------------------------------------------------------------------- # Использование пробельных символов # с обеих сторон символа "=" присваивания недопустимо.

# Если записать "VARIABLE =value", #+ то интерпретатор попытается выполнить команду "VARIABLE" с параметром "=value".

# Если записать "VARIABLE= value", #+ то интерпретатор попытается установить переменную окружения "VARIABLE" в "" #+ и выполнить команду "value". #-------------------------------------------------------------------------



echo hello # Это не ссылка на переменную, выведет строку "hello".

echo $hello echo ${hello} # Идентично предыдущей строке.

echo "$hello" echo "${hello}"

echo

hello="A B C D" echo $hello # A B C D echo "$hello" # A B C D # Здесь вы сможете наблюдать различия в выводе echo $hello и echo "$hello". # Заключение ссылки на переменную в кавычки сохраняет пробельные символы.

echo

echo '$hello' # $hello # Внутри одинарных кавычек не производится подстановка значений переменных, #+ т.е. "$" интерпретируется как простой символ.

# Обратите внимание на различия, существующие между этими типами кавычек.

hello= # Запись пустого значения в переменную. echo "\$hello (пустое значение) = $hello" # Обратите внимание: запись пустого значения -- это не то же самое, #+ что сброс переменной, хотя конечный результат -- тот же (см. ниже).

# --------------------------------------------------------------

# Допускается присваивание нескольких переменных в одной строке, #+ если они отделены пробельными символами. # Внимание! Это может снизить читабельность сценария и оказаться непереносимым.

var1=variable1 var2=variable2 var3=variable3 echo echo "var1=$var1 var2=$var2 var3=$var3"

# Могут возникнуть проблемы с устаревшими версиями "sh".

# --------------------------------------------------------------

echo; echo

numbers="один два три" other_numbers="1 2 3" # Если в значениях переменных встречаются пробелы, # то использование кавычек обязательно. echo "numbers = $numbers" echo "other_numbers = $other_numbers" # other_numbers = 1 2 3 echo

echo "uninitialized_variable = $uninitialized_variable" # Неинициализированная переменная содержит "пустое" значение. uninitialized_variable= # Объявление неинициализированной переменной #+ (то же, что и присваивание пустого значения, см. выше). echo "uninitialized_variable = $uninitialized_variable" # Переменная содержит "пустое" значение.



uninitialized_variable=23 # Присваивание. unset uninitialized_variable # Сброс. echo "uninitialized_variable = $uninitialized_variable" # Переменная содержит "пустое" значение.

echo

exit 0

Неинициализированная переменная хранит "пустое" значение - не ноль!. Использование неинициализированных переменных может приводить к ошибкам разного рода в процессе исполнения.

Не смотря на это в арифметических операциях допускается использовать неинициализированные переменные.

echo "$uninitialized" # (пустая строка) let "uninitialized += 5" # Прибавить 5. echo "$uninitialized" # 5

# Заключение: # Неинициализированные переменные не имеют значения, однако #+ в арифметических операциях за значение таких переменных принимается число 0. # Это недокументированная (и возможно непереносимая) возможность.

См. так же Пример 11-19.

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

=

оператор присваивания (пробельные символы до и после оператора -- недопустимы)

Не путайте с операторами сравнения = и -eq!

Обратите внимание: символ = может использоваться как в качестве оператора присваивания, так и в качестве оператора сравнения, конкретная интерпретация зависит от контекста применения.

Пример 4-2. Простое присваивание

#!/bin/bash # Явные переменные

echo

# Когда перед именем переменной не употребляется символ '$'? # В операциях присваивания.

# Присваивание a=879 echo "Значение переменной \"a\" -- $a."

# Присваивание с помощью ключевого слова 'let' let a=16+5 echo "Значение переменной \"a\" теперь стало равным: $a."

echo

# В заголовке цикла 'for' (своего рода неявное присваивание) echo -n "Значения переменной \"a\" в цикле: " for a in 7 8 9 11 do echo -n "$a " done

echo echo

# При использовании инструкции 'read' (тоже одна из разновидностей присваивания) echo -n "Введите значение переменной \"a\" " read a echo "Значение переменной \"a\" теперь стало равным: $a."



echo

exit 0

Пример 4-3. Присваивание значений переменным простое и замаскированное

#!/bin/bash

a=23 # Простейший случай echo $a b=$a echo $b

# Теперь немного более сложный вариант (подстановка команд).

a=`echo Hello!` # В переменную 'a' попадает результат работы команды 'echo' echo $a # Обратите внимание на восклицательный знак (!) в подстанавливаемой команде #+ этот вариант не будет работать при наборе в командной строке, #+ поскольку здесь используется механизм "истории команд" BASH # Однако, в сценариях, механизм истории команд запрещен.

a=`ls -l` # В переменную 'a' записывается результат работы команды 'ls -l' echo $a # Кавычки отсутствуют, удаляются лишние пробелы и пустые строки. echo echo "$a" # Переменная в кавычках, все пробелы и пустые строки сохраняются. # (См. главу "Кавычки.")

exit 0

Присваивание переменных с использованием $(...) (более современный метод, по сравнению с обратными кавычками)

# Взято из /etc/rc.d/rc.local R=$(cat /etc/redhat-release) arch=$(uname -m)

4.3. Переменные Bash не имеют типа

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

Пример 4-4. Целое число или строка?

#!/bin/bash # int-or-string.sh: Целое число или строка?

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 echo

# А что происходит с "пустыми" переменными? e="" echo "e = $e" # e = let "e += 1" # Арифметические операции допускают использование "пустых" переменных? echo "e = $e" # e = 1 echo # "Пустая" переменная становится целочисленной.

# А что происходит с необъявленными переменными? echo "f = $f" # f = let "f += 1" # Арифметические операции допустимы? echo "f = $f" # f = 1 echo # Необъявленная переменная трансформируется в целочисленную.

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

exit 0

Отсутствие типов -- это и благословение и проклятие. С одной стороны -- отсутствие типов делает сценарии более гибкими (чтобы повеситься -- достаточно иметь веревку!) и облегчает чтение кода. С другой -- является источником потенциальных ошибок и поощряет привычку к "неряшливому" программированию.

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

4.4. Специальные типы переменных

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

переменные, область видимости которых ограничена блоком кода или телом функции (см так же локальные переменные в функциях)

переменные окружения

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

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

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

<


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

bash$ eval "`seq 10000 | sed -e 's/.*/export var&=ZZZZZZZZZZZZZZ/'`"

bash$ du

bash: /usr/bin/du: Argument list too long

(Спасибо S. C. за вышеприведенный пример и пояснения.)

Если сценарий изменяет переменные окружения, то они должны "экспортироваться", т.е передаваться окружению, локальному по отношению к сценарию. Эта функция возложена на команду export.

Сценарий может экспортировать

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

экспортировать переменные "на верх" командной оболочке. Дочерний процесс не может экспортировать переменные родительскому процессу.

---

позиционные параметры

аргументы, передаваемые скрипту из командной строки -- $0, $1, $2, $3..., где $0 -- это название файла сценария, $1 -- это первый аргумент, $2 -- второй, $3 -- третий и так далее. [13] Аргументы, следующие за $9, должны заключаться в фигурные скобки, например: ${10}, ${11}, ${12}.

Специальные переменные $* и $@ содержат все позиционные параметры (аргументы командной строки).

Пример 4-5. Позиционные параметры

#!/bin/bash

# Команда вызова сценария должна содержать по меньшей мере 10 параметров, например # ./scriptname 1 2 3 4 5 6 7 8 9 10 MINPARAMS=10

echo

echo "Имя файла сценария: \"$0\"." # Для текущего каталога добавит ./ echo "Имя файла сценария: \"`basename $0`\"." # Добавит путь к имени файла (см. 'basename')

echo

if [ -n "$1" ] # Проверяемая переменная заключена в кавычки. then echo "Параметр #1: $1" # необходимы кавычки для экранирования символа # fi

if [ -n "$2" ] then echo "Параметр #2: $2" fi



if [ -n "$3" ] then echo "Параметр #3: $3" fi

# ...

if [ -n "${10}" ] # Параметры, следующие за $9 должны заключаться в фигурные скобки then echo "Параметр #10: ${10}" fi

echo "-----------------------------------" echo "Все аргументы командной строки: "$*""

if [ $# -lt "$MINPARAMS" ] then echo echo "Количество аргументов командной строки должно быть не менее $MINPARAMS !" fi

echo

exit 0

Скобочная нотация

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

args=$# # Количество переданных аргументов. lastarg=${!args} # Обратите внимание: lastarg=${!$#} неприменимо.

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

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

variable1_=$1_ # Это предотвратит появление ошибок, даже при отсутствии входного аргумента.

critical_argument01=$variable1_

# Дополнительные символы всегда можно "убрать" позднее. # Это может быть сделано примерно так: variable1=${variable1_/_/} # Побочный эффект возникает только если имя переменной # $variable1_ будет начинаться с символа "_". # Здесь используется один из вариантов подстановки параметров, обсуждаемых в Главе 9. # Отсутствие шаблона замены приводит к удалению.

# Более простой способ заключается #+ в обычной проверке наличия позиционного параметра. if [ -z $1 ] then exit $POS_PARAMS_MISSING fi



---

Пример 4-6. wh, whois выяснение имени домена

#!/bin/bash

# Команда 'whois domain-name' выясняет имя домена на одном из 3 серверов: # ripe.net, cw.net, radb.net

# Разместите этот скрипт под именем 'wh' в каталоге /usr/local/bin

# Требуемые символические ссылки: # ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe # ln -s /usr/local/bin/wh /usr/local/bin/wh-cw # ln -s /usr/local/bin/wh /usr/local/bin/wh-radb

if [ -z "$1" ] then echo "Порядок использования: `basename $0` [domain-name]" exit 65 fi

case `basename $0` in # Проверка имени скрипта и, соответственно, имени сервера "wh" ) whois $1@whois.ripe.net;; "wh-ripe") whois $1@whois.ripe.net;; "wh-radb") whois $1@whois.radb.net;; "wh-cw" ) whois $1@whois.cw.net;; * ) echo "Порядок использования: `basename $0` [domain-name]";; esac

exit 0

---

Команда shift "сдвигает" позиционные параметры, в результате чего парметры "сдвигаются" на одну позицию влево.

$1 <--- $2, $2 <--- $3, $3 <--- $4, и т.д.

Прежний аргумент $1 теряется, но аргумент $0 (имя файла сценария) остается без изменений. Если вашему сценарию передается большое количество входных аргументов, то команда shift позволит вам получить доступ к аргументам, с порядковым номером больше 9, без использования {фигурных скобок}.

Пример 4-7. Использование команды shift

#!/bin/bash # Использование команды 'shift' с целью перебора всех аргументов командной строки.

# Назовите файл с этим сценарием, например "shft", #+ и вызовите его с набором аргументов, например: # ./shft a b c def 23 skidoo

until [ -z "$1" ] # До тех пор пока не будут разобраны все входные аргументы... do echo -n "$1 " shift done

echo # Дополнительная пустая строка.

exit 0

Команда shift может применяться и к входным аргументам функций. См. Пример 33-10.

Глава 5. Кавычки

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



bash$ ls -l [Vv]*

-rw-rw-r-- 1 bozo bozo 324 Apr 2 15:05 VIEWDATA.BAT -rw-rw-r-- 1 bozo bozo 507 May 4 14:25 vartrace.sh -rw-rw-r-- 1 bozo bozo 539 Apr 14 17:11 viewdata.sh

bash$ ls -l '[Vv]*'

ls: [Vv]*: No such file or directory

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

bash$ grep '[Пп]ервая' *.txt

file1.txt:Это первая строка в file1.txt. file2.txt:Это Первая строка в file2.txt.

Примечательно, что "не окавыченный" вариант команды grep [Пп]ервая *.txt будет правильно исполняться в Bash, но не в tcsh.

Вообще, желательно использовать двойные кавычки (" ") при обращении к переменным. Это предотвратит интерпретацию специальных символов, которые могут содержаться в именах переменных, за исключением $, ` (обратная кавычка) и \ (escape -- обратный слэш). [14] То, что символ $ попал в разряд исключений, позволяет выполнять обращение к переменным внутри строк, ограниченных двойными кавычками ("$variable"), т.е. выполнять подстановку значений переменных (см. Пример 4-1, выше).

Двойные кавычки могут быть использованы для предотвращения разбиения строки на слова. [15] Заключение строки в кавычки приводит к тому, что она передается как один аргумент, даже если она содержит пробельные символы - разделители.

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 пробелами).

# Спасибо S.C.

Заключение в кавычки аргументов команды echo необходимо только в том случае, когда разбиение на отдельные слова сопряжено с определенными трудностями.

Пример 5-1. Вывод "причудливых" переменных

#!/bin/bash # weirdvars.sh: Вывод "причудливых" переменных

var="'(]\\{}\$\"" echo $var # '(]\{}$" echo "$var" # '(]\{}$" Никаких различий.

echo

IFS='\' echo $var # '(] {}$" \ символ-разделитель преобразован в пробел. echo "$var" # '(]\{}$"

# Примеры выше предоставлены S.C.

exit 0

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

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

echo "Why can't I write 's between single quotes"

echo

# Обходной метод. echo 'Why can'\''t I write '"'"'s between single quotes' # |-------| |----------| |-----------------------| # Три строки, ограниченных одинарными кавычками, # и экранированные одиночные кавычки между ними.

# Пример любезно предоставлен Stephane Chazelas.

Экранирование -- это способ заключения в кавычки одиночного символа. Экранирующий (escape) символ (\) сообщает интерпретатору, что следующий за ним символ должен восприниматься как обычный символ.

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

<


Специальное назначение некоторых экранированных символов

используемых совместно с echo и sed

\n

перевод строки (новая строка)

\r

перевод каретки

\t

табуляция

\v

вертикальная табуляция

\b

забой (backspace)

\a

"звонок" (сигнал)

\0xx

ASCII-символ с кодом 0xx в восьмеричном виде)

Пример 5-2. Экранированные символы

#!/bin/bash # escaped.sh: экранированные символы

echo; echo

echo "\v\v\v\v" # Вывод последовательности символов \v\v\v\v. # Для вывода экранированных символов следует использовать ключ -e. echo "=============" echo "ВЕРТИКАЛЬНАЯ ТАБУЛЯЦИЯ" echo -e "\v\v\v\v" # Вывод 4-х вертикальных табуляций. echo "=============="

echo "КАВЫЧКИ" echo -e "\042" # Выводит символ " (кавычки с восьмеричным кодом ASCII 42). echo "=============="

# Конструкция $'\X' делает использование ключа -e необязательным. echo; echo "НОВАЯ СТРОКА И ЗВОНОК" echo $'\n' # Перевод строки. echo $'\a' # Звонок (сигнал).

echo "===============" echo "КАВЫЧКИ" # Bash версии 2 и выше допускает использование конструкции $'\nnn'. # Обратите внимание: здесь под '\nnn' подразумевается восьмеричное значение. echo $'\t \042 \t' # Кавычки (") окруженные табуляцией.

# В конструкции $'\xhhh' допускается использовать и шестнадцатеричные значения. echo $'\t \x22 \t' # Кавычки (") окруженные табуляцией. # Спасибо Greg Keraunen, за это примечание. # Ранние версии Bash допускали употребление конструкции в виде '\x022'. echo "===============" echo

# Запись ASCII-символов в переменную. # ---------------------------------------- quote=$'\042' # запись символа " в переменную. echo "$quote Эта часть строки ограничена кавычками, $quote а эта -- нет."

echo

# Конкатенация ASCII-символов в переменную. triple_underline=$'\137\137\137' # 137 -- это восьмеричный код символа '_'. echo "$triple_underline ПОДЧЕРКИВАНИЕ $triple_underline"



echo

ABC=$'\101\102\103\010' # 101, 102, 103 это A, B и C соответственно. echo $ABC

echo; echo

escape=$'\033' # 033 -- восьмеричный код экранирующего символа. echo "\"escape\" выводится как $escape" # вывод отсутствует.

echo; echo

exit 0

Еще один пример использования конструкции $' '

вы найдете в Пример 34-1.

\"

кавычки

echo "Привет" # Привет echo "Он сказал: \"Привет\"." # Он сказал: "Привет".

\$

символ доллара (если за комбинацией символов \$ следует имя переменной, то она не будет разыменована)

echo "\$variable01" # выведет $variable01

\\

обратный слэш

echo "\\" # выведет \

Поведение символа \ сильно зависит от того экранирован ли он, ограничен ли кавычками или находится внутри конструкции подстановки команды или во вложенном документе.

# Простое экранирование и кавычки echo \z # z echo \\z # \z echo '\z' # \z echo '\\z' # \\z echo "\z" # \z echo "\\z" # \z

# Подстановка команды echo `echo \z` # z echo `echo \\z` # z echo `echo \\\z` # \z echo `echo \\\\z` # \z echo `echo \\\\\\z` # \z echo `echo \\\\\\\z` # \\z echo `echo "\z"` # \z echo `echo "\\z"` # \z

# Встроенный документ cat <<EOF \z EOF # \z

cat <<EOF \\z EOF # \z

# Эти примеры предоставил Stephane Chazelas.

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

variable=\ echo "$variable" # Не работает - дает сообщение об ошибке: # test.sh: : command not found # В "чистом" виде экранирующий (escape) символ не может быть записан в переменную. # # Фактически, в данном примере, происходит экранирование символа перевода строки #+ в результате получается такая команда: variable=echo "$variable" #+ ошибочное присваивание

variable=\ 23skidoo echo "$variable" # 23skidoo # Здесь все в порядке, поскольку вторая строка #+ является нормальным, с точки зрения присваивания, выражением.

variable=\ # \^ За escape-символом следует пробел echo "$variable" # пробел

variable=\\ echo "$variable" # \

variable=\\\ echo "$variable" # Не работает - сообщение об ошибке: # test.sh: \: command not found # # Первый escape-символ экранирует второй, а третий оказывается неэкранированным, #+ результат тот же, что и в первом примере.

variable=\\\\ echo "$variable" # \\ # Второй и четвертый escape-символы экранированы. # Это нормально.

<


Экранирование пробелов предотвращает разбиение списка аргументов командной строки на отдельные аргументы.

file_list="/bin/cat /bin/gzip /bin/more /usr/bin/less /usr/bin/emacs-20.7" # Список файлов как аргумент(ы) командной строки.

# Добавить два файла в список и вывести список. ls -l /usr/X11R6/bin/xsetroot /sbin/dump $file_list

echo "-------------------------------------------------------------------------"

# Что произойдет, если экранировать пробелы в списке? ls -l /usr/X11R6/bin/xsetroot\ /sbin/dump\ $file_list # Ошибка: первые три файла будут "слиты" воедино # и переданы команде 'ls -l' как один аргумент # потому что два пробела, разделяющие аргументы (слова) -- экранированы.

Кроме того, escape-символ позволяет писать многострочные команды. Обычно, каждая команда занимает одну строку, но escape-символ позволяет экранировать символ перевода строки, в результате чего одна команда может занимать несколько строк.

(cd /source/directory && tar cf - . ) | \ (cd /dest/directory && tar xpvf -) # Команда копирования дерева каталогов. # Разбита на две строки для большей удобочитаемости.

# Альтернативный вариант: tar cf - -C /source/directory . | tar xpvf - -C /dest/directory # См. примечание ниже. # (Спасибо Stephane Chazelas.)

Если строка сценария заканчивается символом создания конвейера |, то необходимость в применении символа \, для экранирования перевода строки, отпадает. Тем не менее, считается хорошим тоном, всегда использовать символ "\" в конце промежуточных строк многострочных команд.

echo "foo bar" #foo #bar

echo

echo 'foo bar' # Никаких различий. #foo #bar

echo

echo foo\ bar # Перевод строки экранирован. #foobar

echo

echo "foo\ bar" # Внутри "нестрогих" кавычек символ "\" интерпретируется как экранирующий. #foobar

echo

echo 'foo\ bar' # В "строгих" кавычках обратный слэш воспринимается как обычный символ. #foo\ #bar

# Примеры предложены Stephane Chazelas.



Глава 6. Завершение и код завершения

  ... эта часть Bourne shell покрыта мраком, тем не менее все пользуются ею.

  Chet Ramey
Команда exit может использоваться для завершения работы сценария, точно так же как и в программах на языке C. Кроме того, она может возвращать некоторое значение, которое может быть проанализировано вызывающим процессом.

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

Аналогичным образом ведут себя функции, расположенные внутри сценария, и сам сценарий, возвращая код завершения. Код, возвращаемый функцией или сценарием, определяется кодом возврата последней команды. Команде exit можно явно указать код возврата, в виде: exit nnn, где nnn -- это код возврата (число в диапазоне 0 - 255).

Когда работа сценария завершается командой exit без параметров, то код возврата сценария определяется кодом возврата последней исполненной командой.

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

Пример 6-1. завершение / код завершения

#!/bin/bash

echo hello echo $? # код возврата = 0, поскольку команда выполнилась успешно.

lskdf # Несуществующая команда. echo $? # Ненулевой код возврата, поскольку команду выполнить не удалось.

echo

exit 113 # Явное указание кода возврата 113. # Проверить можно, если набрать в командной строке "echo $?" # после выполнения этого примера.



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

Переменная $? особенно полезна, когда необходимо проверить результат исполнения команды (см. Пример 12-27 и Пример 12-13).

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

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

true # встроенная команда "true". echo "код возврата команды \"true\" = $?" # 0

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

# Спасибо S.C.

В отдельных случаях коды возврата должны иметь предопределенные значения и не должны задаваться пользователем.

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

практически любой язык программирования включает в себя условные операторы, предназначенные для проверки условий, чтобы выбрать тот или иной путь развития событий в зависимости от этих условий. В Bash, для проверки условий, имеется команда test, различного вида скобочные операторы и условный оператор if/then.

7.1. Конструкции проверки условий

Оператор if/then проверяет -- является ли код завершения списка команд 0 (поскольку 0 означает "успех"), и если это так, то выполняет одну, или более, команд, следующие за словом then.

Существует специальная команда -- [ (левая квадратная скобка). Она является синонимом команды test, и является встроенной командой (т.е. более эффективной, в смысле производительности). Эта команда воспринимает свои аргументы как выражение сравнения или как файловую проверку и возвращает код завершения в соответствии с результатами проверки (0 -- истина, 1 -- ложь).

Начиная с версии 2.02, Bash предоставляет в распоряжение программиста конструкцию [[ ... ]] расширенный вариант команды test, которая выполняет сравнение способом более знакомым программистам, пишущим на других языках программирования.


Обратите внимание: [[ -- это зарезервированное слово, а не команда.

Bash исполняет [[ $a -lt $b ]] как один элемент, который имеет код возврата.

Круглые скобки (( ... )) и предложение let ... так же возвращают код 0, если результатом арифметического выражения является ненулевое значение. Таким образом, арифметические выражения могут учавствовать в операциях сравнения.

Предложение let "1<2" возвращает 0 (так как результат сравнения "1<2" -- "1", или "истина") (( 0 && 1 )) возвращает 1 (так как результат операции "0 && 1" -- "0", или "ложь")

Условный оператор if проверяет код завершения любой команды, а не только результат выражения, заключенного в квадратные скобки.

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 допускает наличие вложенных проверок.

if echo "Следующий *if* находится внутри первого *if*."

if [[ $comparison = "integer" ]] then (( a < b )) else [[ $a < $b ]] fi

then echo '$a меньше $b' fi

Это детальное описание конструкции "if-test" любезно предоставлено Stephane Chazelas.

Пример 7-1. Что есть "истина"?

#!/bin/bash

echo

echo "Проверяется \"0\"" if [ 0 ] # ноль then echo "0 -- это истина." else echo "0 -- это ложь." fi # 0 -- это истина.

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 # Пустая переменная -- это ложь.

echo

# Кргда "ложь" истинна?

echo "Проверяется \"false\"" if [ "false" ] # это обычная строка "false". then echo "\"false\" -- это истина." #+ и она истинна. else echo "\"false\" -- это ложь." fi # "false" -- это истина.

echo

echo "Проверяется \"\$false\"" # Опять неинициализированная переменная. if [ "$false" ] then echo "\"\$false\" -- это истина." else echo "\"\$false\" -- это ложь." fi # "$false" -- это ложь. # Теперь мв получили ожидаемый результат.

echo

exit 0

Упражнение. Объясните результаты, полученные в Пример 7-1.




if [ condition-true ] then command 1 command 2 ... else # Необязательная ветка (можно опустить, если в ней нет необходимости). # Дополнительный блок кода, # исполняемый в случае, когда результат проверки -- "ложь". command 3 command 4 ... fi

Когда if и then располагаются в одной строке, то конструкция if должна завершаться точкой с запятой. И if, и then -- это зарезервированные слова. Зарезервированные слова начинают инструкцию, которая должна быть завершена прежде, чем в той же строке появится новая инструкция.

if [ -x "$filename" ]; then

Else if и elif

elif

elif -- это краткая форма записи конструкции else if. Применяется для построения многоярусных инструкций if/then.

if [ condition1 ] then command1 command2 command3 elif [ condition2 ] # То же самое, что и else if then command4 command5 else default-command fi

Конструкция if test condition-true является точным эквивалентом конструкции if [ condition-true ], где левая квадратная скобка [ выполняет те же действия, что и команда test. Закрывающая правая квадратная скобка ] не является абсолютно необходимой, однако, более новые версии Bash требуют ее наличие.

Команда 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