User Tools

Site Tools


bash:управление_потоком_выполнения

Управление потоком выполнения: ветвление при помощи if

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

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

x = 5
Если x = 5, тогда:
      Сказать "x равно 5".
Иначе:
      Сказать "x не равно 5".

Это – пример ветвления. Если условие x = 5? верно, выполняется строка: Сказать x равно 5. Иначе выполняется строка: Сказать x не равно 5.

Инструкция if

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

x=5
if [ $x = 5 ]; then
     echo "x equals 5."
else
     echo "x does not equal 5."
fi

То же самое можно выполнить непосредственно в командной строке, получается немного короче:

x=5
if [ $x = 5 ]; then echo "x equals 5"; else echo "x does not equals 5"; fi
x equals 5
 
x=0
if [ $x = 5 ]; then echo "x equals 5"; else echo "x does not equals 5"; fi
x does not equals 5

Инструкция if имеет следующий синтаксис:

if команды; then
 команды
[elif команды; then
 команды...]
[else
 команды]
fi

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

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

Команды (включая сценарии и функции, написанные нашими собственными руками) по завершении работы возвращают системе значение, которое называют кодом завершения (exit status). Это значение – целое число в диапазоне от 0 до 255 – сообщает об успешном или неуспешном завершении команды. По соглашениям значение 0 служит признаком успешного завершения, а любое другое – неуспешного. Командная оболочка поддерживает переменную, посредством которой можно определить код завершения. Например:

ls -d /usr/bin/
/usr/bin/
echo $?
0
 
ls -d /bin/usr
ls: cannot access '/bin/usr': No such file or directory
echo $?
2

Одни команды используют разные коды завершения, чтобы сообщить о характере ошибки, тогда как другие, столкнувшись с любой ошибкой, просто возвращают значение 1. Страницы справочного руководства часто включают раздел с заголовком «Exit Status» («Коды завершения»), описывающий возвращаемые коды. Однако 0 всегда служит признаком успешного выполнения.

Командной оболочкой поддерживаются две чрезвычайно простые встроенные команды, которые просто завершаются с кодом 0 или 1. Команда true всегда завершается с признаком успеха, а команда false – всегда с признаком ошибки:

true
echo $?
0
 
false
echo $?
1

Эти команды можно использовать для исследования особенностей работы инструкции if. Инструкция if в действительности просто оценивает код завершения команды:

if true; then echo "It's true"; fi
It's true
 
# в случае с false результат будет отсутсвовать
if false; then echo "It's true"; fi

Команда <html>echo “It's true.”</html> выполняется, только если команда, следующая за if, завершается успешно, и не выполняется, если команда, следующая за if, завершается с признаком ошибки. Если за if следует список команд, успешность выполнения всего списка определяется по последней команде:

if false; true; then echo "It's true"; fi
It's true
 
if true; false; then echo "It's true"; fi

Команда test

Вне всяких сомнений, чаще всего с инструкцией if используется команда test. Команда test может выполнять различные проверки и сравнения. Она имеет две эквивалентные формы:

test выражение

и более популярную

[ выражение ]

где выражение возвращает истинное (true) или ложное (false) значение. Команда test возвращает код завершения 0, если выражение истинно, и код завершения 1, если выражение ложно

Выражения для проверки файлов

Выражение Истинно, если
<html>файл1 -ef файл2</html> файл1 и файл2 имеют одно и то же число индексного узла (inode; то есть два имени принадлежат жестким ссылкам, ссылающимся на один и тот же файл
<html>файл1 -nt файл2</html> файл1 новее файла файл2
<html>файл1 -ot файл2</html> файл1 старше файла файл2
<html>-b файл</html> файл существует и является специальным файлом блочного устройства
<html>-с файл</html> файл существует и является специальным файлом символьного устройства
<html>-d файл</html> файл существует и является каталогом
<html>-e файл</html> файл существует
<html>-f файл</html> файл существует и является обычным файлом
<html>-g файл</html> файл существует и имеет атрибут set-group-ID (бит setgid)
<html>-G файл</html> файл существует и принадлежит действующей группе
<html>-k файл</html> файл существует и имеет атрибут «sticky bit»
<html>-L файл</html> файл существует и является символической ссылкой
<html>-O файл</html> файл существует и принадлежит действующему пользователю
<html>-p файл</html> файл существует и является именованным каналом
<html>-r файл</html> файл существует и доступен для чтения (имеет разрешение на чтение для действующего пользователя)
<html>-s файл</html> файл существует и имеет размер больше нуля
<html>-S файл</html> файл существует и является сетевым сокетом
<html>-t дескриптор_файла</html> дескриптор_файла представляет файл, подключенный к терминалу. Это выражение можно использовать для проверки стандартных потоков ввода/вывода/ошибок
<html>-u файл</html> файл существует и имеет атрибут setuid
<html>-w файл</html> файл существует и доступен для записи (имеет разрешение на запись для действующего пользователя)
<html>-x файл</html> файл существует и доступен для выполнения (имеет разрешение на выполнение для действ

Следующий сценарий демонстрирует применение некоторых выражений с файлами:

#!/bin/bash
 
# test-file: проверка файла
 
FILE=~/.bashrc
 
if [ -e "$FILE" ]; then
        if [ -f "$FILE" ]; then
                echo "$FILE is a regular file."
 
        fi
        if [ -d "$FILE" ]; then
                echo "$FILE is a directory."
        fi
        if [ -r "$FILE" ]; then
                echo "$FILE is readable."
        fi
        if [ -w "$FILE" ]; then
                echo "$FILE is writable."
        fi
        if [ -x "$FILE" ]; then
                echo "$FILE is executable/searchable."
        fi
else
        echo "$FILE does not exist"
        exit 1
fi

Запустим скрипт:

/home/nevvad/.bashrc is a regular file.
/home/nevvad/.bashrc is readable.
/home/nevvad/.bashrc is writable.

Сценарий проверяет файл, имя которого присвоено константе FILE, и выводит результат. Этот сценарий имеет две интересные особенности, на которые следует обратить внимание. Во-первых, отметьте, что параметр $FILE внутри выражений заключен в кавычки. Это не является обязательным требованием, но защищает от случаев, когда параметр пуст. Если механизм подстановки заменит $FILE пустым значением, это приведет к ошибке (операторы в этом случае будут интерпретироваться как непустые строки, а не как операторы). Использование кавычек гарантирует, что за оператором всегда будет следовать строка, даже если она пустая.

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

Функции командной оболочки тоже могут возвращать свой код завершения, передавая целочисленный аргумент команде return. Чтобы преобразовать сценарий, приведенный выше, в функцию для использования в больших программах, нужно заменить команды exit инструкциями return:

else
        echo "$FILE does not exist"
        return 1
fi

Выражения для проверки строк

Выражение Истинно, если
<html>строка</html> строка не пустая
<html>-n строка</html> Длина строки больше нуля
<html>-z строка</html> Длина строки равна нулю
<html>строка1 = строка2 строка1 == строка2</html> строка1 и строка2 равны. Допускается использовать один или два знака «равно», но предпочтительнее два
<html>строка1 != строка2</html> строка1 и строка2 не равны
<html>строка1 > строка2</html> строка1 больше, чем строка2, в смысле алфавитной сортировки
<html>строка1 < строка2</html> строка1 меньше, чем строка2, в смысле алфавитной сортировки

<WRAP center round info 99%> Nota Bene!

При использовании с командой test операторы > и < необходимо заключать в кавычки (или экранировать символом «обратный слеш»). Если этого не сделать, они будут интерпретироваться командной оболочкой как операторы перенаправления, что может привести к плачевным результатам. </WRAP> Следующий сценарий демонстрирует применение выражений для проверки строк:

#!/bin/bash
 
# test-string: проверка значения строки
 
ANSWER=maybe
 
if [ -z "$ANSWER" ]; then
      echo "There is no answer." >&2
      exit 1
fi
 
if [ "$ANSWER" == "yes" ]; then
       echo "The answer is YES."
elif [ "$ANSWER" == "no" ]; then
       echo "The answer is NO."
elif [ "$ANSWER" == "maybe" ]; then
       echo "The answer is MAYBE."
else
       echo "The answer is UNKNOWN."
fi

Запускаем скрипт

The answer is MAYBE.

В этом сценарии определяется константа ANSWER. Сначала сценарий проверяет, не является ли строка пустой. Если строка пустая, сценарий завершается с кодом 1. Обратите внимание на оператор перенаправления в команде echo. Он перенаправляет сообщение об ошибке There is no answer (Нет ответа) в стандартный вывод ошибок как «наиболее подходящий» для сообщений об ошибках. Если строка не пустая, сценарий сравнивает ее значение со строками yes, no или maybe. Проверки выполняются с использованием инструкции elif, которая является краткой формой записи для else if. Инструкция elif позволяет конструировать более сложные логические проверки.

Выражения для проверки целых чисел

Выражения для проверки целых чисел

Выражение Истинно, если
<html>число1 -eq число2</html> число1 и число2 равны
<html>число1 -ne число2</html> число1 и число2 не равны
<html>число1 -le число2</html> число1 меньше или равно числу2
<html>число1 -lt число2</html> число1 меньше, чем число2
<html>число1 -ge число2</html> число1 больше или равно числу2
<html>число1 -gt число2</html> число1 больше, чем число2

Следующий сценарий демонстрирует их применение:

test_int2
#!/bin/bash
 
# test_int2: проверка целочисленного значения.
 
INT=-5
 
if [ -z "$INT" ]; then
      echo "INT is empty." >&2
      exit 1
fi
 
if [ $INT -eq 0 ]; then
      echo "INT is zero."
else
      if [ $INT -lt 0 ]; then
            echo "INT is negative."
      else
            echo "INT is positive."
      fi
      if [ $((INT % 2)) -eq 0 ]; then
            echo "INT is even."
      else
            echo "INT is odd."
      fi
fi

Обратите внимание на то, как сценарий определяет четность (even) или нечетность (odd) целого числа. Он возвращает остаток от деления числа на 2, по значению которого можно судить о четности или нечетности числа.

Более современная версия команды test

Последние версии bash реализуют составную команду, которая действует как улучшенная замена для команды test. Она имеет следующий синтаксис:

[[ выражение ]]

Где выражение возвращает истинное (true) или ложное (false) значение. Команда <html>управление_потоком_выполнения</html> очень похожа на команду test (она поддерживает те же выражения), но добавляет новое выражение для проверки строк:

строка1 =~ регулярное_выражение

Возвращающее истинное значение, если строка1 соответствует расширенному регулярному выражению. Это открывает широкие перспективы для решения таких задач, как проверка корректности данных. Предыдущий сценарий, демонстрирующий применение выражений проверки целых чисел, может завершиться с ошибкой, если константе INT присвоить любое значение, не являющееся целым числом. Для надежности сценарию необходима возможность убедиться, что константа действительно содержит целое число. Используя <html>управление_потоком_выполнения</html> с оператором проверки строки <html>=~</html>, мы усовершенствуем его, как показано ниже:

#!/bin/bash
 
# test-integer2: проверка целочисленного значения.
 
INT=-5
 
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
	if [ $INT -eq 0 ]; then
		echo "INT is zero."
 	else
 		if [ $INT -lt 0 ]; then
 			echo "INT is negative."
		else
 			echo "INT is positive."
 		fi
 		if [ $((INT % 2)) -eq 0 ]; then
 			echo "INT is even."
 		else
 			echo "INT is odd."
 		fi
 	fi
else
 	echo "INT is not an integer." >&2
 	exit 1
fi

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

Еще одна дополнительная особенность <html>управление_потоком_выполнения</html>: оператор <html>==</html> поддерживает сопоставление с шаблоном по аналогии с механизмом подстановки путей. Например:

FILE=foo.bar
if [[ $FILE == foo.* ]]; then
> echo "$FILE matches pattern 'foo.*'"
> fi
foo.bar matches pattern 'foo.*'

Она превращает <html>управление_потоком_выполнения</html> в удобный инструмент проверки имен файлов и путей.

(( )) — для проверки целых чисел

В дополнение к составной команде <html>управление_потоком_выполнения</html> bash поддерживает также составную команду <html>1)</html>, которую удобно использовать для работы с целыми числами. Она поддерживает полное множество арифметических операторов.

Команда <html>2)</html> применяется для проверки истинности арифметических выражений. Арифметическое выражение считается истинным, если его результат отличается от нуля.

if ((1)); then echo "It is true."; fi
It is true.
if ((0)); then echo "It is true."; fi

Применив <html>3)</html>, можно немного упростить сценарий test_int2, как показано ниже:

test_int_a
#!/bin/bash
 
# test_int_a: проверка целочисленного значения.
 
INT=-5
 
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
	if ((INT == 0)); then
		echo "INT is zero."
 	else
 	if ((INT < 0)); then
 		echo "INT is negative."
 	else
 		echo "INT is positive."
 	fi
 	if (( ((INT % 2)) == 0)); then
 		echo "INT is even."
 	else
 		echo "INT is odd."
 	fi
 fi
else
 	echo "INT is not an integer." >&2
 	exit 1
fi

Обратите внимание, что здесь мы использовали знак <html>«/html>, а равенство проверяется с помощью оператора <html>==</html>. Такой синтаксис выглядит более естественным при работе с целыми числами. Отметьте также, что составная команда <html>4)</html> является частью синтаксиса командной оболочки, а не обычной командой, может применяться только к целым числам, распознает переменные по именам и не требует выполнять подстановку.

Объединение выражений

Для более сложных вычислений существует возможность объединения выражений. Объединяются выражения с помощью логических операторов. Мы уже встречались с ними в 5) о команде find. Всего команды test и <html>управление_потоком_выполнения</html> поддерживают три логические операции. Это И (AND), ИЛИ (OR) и НЕ(NOT). Для представления этих операций test и <html>управление_потоком_выполнения</html> используют разные операторы, как показано в таблице ниже:

Операция test <html>управление_потоком_выполнения и 6)</html>
<html>И</html> <html>-a</html> <html>&&</html>
<html>ИЛИ</html> <html>-o</html> <html></html>
<html>НЕ</html> <html>!</html> <html>!</html>

Ниже приводится пример использования операции И (AND). Следующий сценарий определяет вхождение целочисленного значения в определенный диапазон

#!/bin/bash
 
# test-integer3: проверка вхождения целочисленного значения
# в определенный диапазон.
 
MIN_VAL=1
MAX_VAL=100
 
INT=50
 
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
	if [[ "$INT" -ge "$MIN_VAL" && "$INT" -le "$MAX_VAL" ]]; then
 		echo "$INT is within $MIN_VAL to $MAX_VAL."
 	else
 		echo "$INT is out of range."
 	fi
else
 	echo "$INT is not an integer." >&2
 	exit 1
fi

Этот сценарий определяет, попадает ли целочисленное значение INT в диапазон между MIN_VAL и MAX_VAL. Эта операция выполняется единственной командой <html>управление_потоком_выполнения</html>, включающей два выражения, разделенных оператором <html>&&</html>. Ту же проверку можно выполнить с помощью test:

if [ "$INT" -ge "$MIN_VAL" -a "$INT" -le "$MAX_VAL" ]; then
      echo "$INT is within $MIN_VAL to $MAX_VAL."
else
      echo "$INT is out of range."
fi

Оператор отрицания <html>!</html> обращает результат выражения. Он возвращает истинное значение, если выражение ложно, и ложное значение, если выражение истинно. В следующем сценарии мы изменили логику вычислений, чтобы определить, находится ли значение INT за пределами указанного диапазона:

#!/bin/bash
 
# test-integer4: проверка выхода целочисленного значения
# за границы определенного диапазона.
 
MIN_VAL=1
MAX_VAL=100
 
INT=50
 
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
 	if [[ ! ("$INT" -ge "$MIN_VAL" && "$INT" -le "$MAX_VAL") ]]; then
 		echo "$INT is outside $MIN_VAL to $MAX_VAL."
 	else
 		echo "$INT is in range."
 	fi
else
 	echo "$INT is not an integer." >&2
 	exit 1
fi

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

if [ ! \( "$INT" -ge "$MIN_VAL" -a "$INT" -le "$MAX_VAL" \) ]; then
      echo "$INT is outside $MIN_VAL to $MAX_VAL."
else
      echo "$INT is in range."
fi

Поскольку все выражения и операторы в команде test интерпретируются командной оболочкой как аргументы (в отличие от <html>управление_потоком_выполнения</html> и <html>7)</html>), символы, имеющие специальное значение для bash, такие как <html>«/html>, <html»</html>, <html>( и )</html>, необходимо заключать в кавычки или экранировать.

Учитывая, что команды test и <html>управление_потоком_выполнения</html> до определенной степени равноценны, возникает вопрос: какой из них отдать предпочтение? Команда test является традиционной (и частью стандарта POSIX), тогда как команда <html>управление_потоком_выполнения</html> характерна для bash. Уметь пользоваться командой test крайне важно, потому что она применяется очень широко, но команда <html>управление_потоком_выполнения</html> проще и удобнее, поэтому она часто используется в современных сценариях.

Операторы управления: еще один способ ветвления

bash поддерживает два оператора управления, которые используются для ветвления. Операторы <html>&&</html> (И) и <html>||</html> (ИЛИ) действуют подобно логическим операторам в составной команде <html>управление_потоком_выполнения</html>. Они имеют следующий синтаксис:

команда1 && команда2

и

команда1 || команда2

Важно понимать, как они действуют. В последовательности с оператором <html>&&</html> первая команда выполняется всегда, а вторая — только если первая завершилась успехом. В последовательности с оператором <html>||</html> первая команда выполняется всегда, а вторая — только если первая завершилась неудачей.

В практическом смысле это означает, что можно выполнить следующую последовательность команда

mkdir temp && cd temp

Она создаст каталог с именем <html>temp</html> и, если эта операция завершится успехом, каталог <html>temp</html> будет назначен текущим рабочим каталогом. Попытка выполнить вторую команду будет произведена, только если команда mkdir завершится успехом. Аналогично, следующая команда:

[ -d temp ] || mkdir temp

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

[ -d temp ] || exit 1

Если сценарий требует наличия каталога <html>temp</html>, а он не существует, тогда сценарий завершится с кодом 1.

Cценарий sys_info_page

Мы начали эту статью с вопроса, оставшегося без ответа в предыдущей статье: как сценарию sys_info_page определить, имеет ли текущий пользователь права на чтение всех домашних каталогов? После знакомства с инструкцией if эту проблему можно решить, добавив следующий код в функцию report_home_space

report_home_space () {
    if [[ $(id -u) -eq 0 ]]; then
            cat <<- _EOF_
                <h2>Home Space Utilization (All Users)</h2>
 		<pre>$(du -sh /home/*)</pre>
 		_EOF_
    else
 	    cat <<- _EOF_
 		<h2>Home Space Utilization ($USER)</h2>
		<pre>$(du -sh $HOME)</pre>
 		_EOF_
 	    fi
 	    return
}

Здесь проверяется вывод команды id. Если вызвать команду id с параметром <html>-u</html>, она выведет числовой идентификатор действующего пользователя. Суперпользователю всегда присваивается числовой идентификатор 0. Зная это, мы сконструировали два разных вложенных документа: один пользуется преимуществом привилегий суперпользователя, а другой ограничивается домашним каталогом текущего пользователя.

1) , 2) , 3) , 4) , 7)
5)
Логические операторы, поддерживаемые командой find указаны в статье
bash/управление_потоком_выполнения.txt · Last modified: 2023/04/06 10:18 (external edit)