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

8.1. Операторы

присваивание

variable assignment

Инициализация переменной или изменение ее значения

=

Универсальный оператор присваивания, пригоден как для сравнения целых чисел, так и для сравнения строк.

var=27
category=minerals  # Пробелы до и после оператора "=" -- недопустимы.


Caution

Пусть вас не смущает, что оператор присваивания ("="), по своему внешнему виду, совпадает с оператором сравнения (=).

#    Здесь знак "="  выступает в качестве оператора сравнения

if [ "$string1" = "$string2" ]
# if [ "X$string1" = "X$string2" ] более отказоустойчивый вариант,
# предохраняет от "сваливания" по ошибке в случае, когда одна из переменных пуста.
# (добавленные символы "X" компенсируют друг друга.)
then
   command
fi


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

+

сложение

-

вычитание

*

умножение

/

деление

**

возведение в степень

# В Bash, начиная с версии 2.02, был введен оператор возведения в степень -- "**".

let "z=5**3"
echo "z = $z"   # z = 125


%

модуль (деление по модулю), возвращает остаток от деления

bash$ echo `expr 5 % 3`
2
        


Этот оператор может применяться в алгоритмах генерации псевдослучайных чисел в заданном диапазоне (см. Пример 9-23 и Пример 9-26), для форматирования вывода на экран (см. Пример 25-15 и Пример A-7), и даже для генерации простых чисел (см. Пример A-18). На удивление часто операцию деления по модулю можно встретить в различных численных алгоритмах.

Пример 8-1. Наибольший общий делитель

#!/bin/bash
# gcd.sh: поиск наибольшего общего делителя
#         по алгоритму Эвклида

#  Под "наибольшим общим делителем" (нод) двух целых чисел
#+ понимается наибольшее целое число, которое делит оба делимых без остатка.

#  Алгоритм Эвклида выполняет последовательное деление.
#  В каждом цикле,
#+ делимое  <---  делитель
#+ делитель <---  остаток
#+ до тех пор, пока остаток не станет равным нулю (остаток = 0).
#+ The gcd = dividend, on the final pass.
#
#  Замечательное описание алгоритма Эвклида можно найти
#  на сайте Jim Loy, http://www.jimloy.com/number/euclids.htm.


# ------------------------------------------------------
# Проверка входных параметров
ARGS=2
E_BADARGS=65

if [ $# -ne "$ARGS" ]
then
  echo "Порядок использования: `basename $0` первое-число второе-число"
  exit $E_BADARGS
fi
# ------------------------------------------------------


gcd ()
{

                                 #  Начальное присваивание.
  dividend=$1                    #  В сущности, не имеет значения
  divisor=$2                     #+ какой из них больше.
                                 #  Почему?

  remainder=1                    #  Если переменные неинициализировать,
                                 #+ то работа сценария будет прервана по ошибке
                                 #+ в первом же цикле.

  until [ "$remainder" -eq 0 ]
  do
    let "remainder = $dividend % $divisor"
    dividend=$divisor            # Повторить цикл с новыми исходными данными
    divisor=$remainder
  done                           # алгоритм Эвклида

}                                # последнее $dividend и есть нод.


gcd $1 $2

echo; echo "НОД чисел $1 и $2 = $dividend"; echo


# Упражнение :
# --------
#  Вставьте дополнительную проверку входных аргументов,
#+ и предусмотрите завершение работы сценария с сообщением об ошибке, если
#+ входные аргументы не являются целыми числами.

exit 0
+=

"плюс-равно" (увеличивает значение переменной на заданное число)

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

-=

"минус-равно" (уменьшение значения переменной на заданное число)

*=

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

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

/=

"слэш-равно" (уменьшение значения переменной в заданное число раз)

%=

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

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

Пример 8-2. Арифметические операции

#!/bin/bash
# От 1 до 6 пятью различными способами.

n=1; echo -n "$n "

let "n = $n + 1"   # let "n = n + 1"   тоже допустимо
echo -n "$n "

: $((n = $n + 1))
# оператор ":" обязателен, поскольку в противном случае, Bash будет
#+ интерпретировать выражение "$((n = $n + 1))" как команду.
echo -n "$n "

n=$(($n + 1))
echo -n "$n "

: $[ n = $n + 1 ]
# оператор ":" обязателен, поскольку в противном случае, Bash будет
#+ интерпретировать выражение "$[ n = $n + 1 ]" как команду.
# Не вызывает ошибки даже если "n" содержит строку.
echo -n "$n "

n=$[ $n + 1 ]
#  Не вызывает ошибки даже если "n" содержит строку.
#* Старайтесь избегать употребления такой конструкции,
#+ поскольку она уже давно устарела и не переносима.
echo -n "$n "; echo

# Спасибо Stephane Chazelas.

exit 0
Note

Целые числа в Bash фактически являются длинными целыми (32-бит) со знаком, с диапазоном изменений от -2147483648 до 2147483647. Если в результате какой либо операции эти пределы будут превышены, то результат получится ошибочным.

a=2147483646
echo "a = $a"      # a = 2147483646
let "a+=1"         # Увеличить "a" на 1.
echo "a = $a"      # a = 2147483647
let "a+=1"         # увеличить "a" еще раз, с выходом за границы диапазона.
echo "a = $a"      # a = -2147483648
                   #      ОШИБКА! (выход за границы диапазона)


Версия Bash 2.05b, поддерживает 64-ьитные целые числа.

Caution

Bash ничего не знает о существовании чисел с плавающей запятой. Такие числа, из-за наличия символа десятичной точки, он воспринимает как строки.

a=1.5

let "b = $a + 1.3"  # Ошибка.
# t2.sh: let: b = 1.5 + 1.3: syntax error in expression (error token is ".5 + 1.3")

echo "b = $b"       # b=1
Для работы с числами с плавающей запятой в сценариях можно использовать утилиту-калькулятор bc.

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

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

<<

сдвигает на 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 ]]    # То же верно
# Обратите внимание: оператор && не должен использоваться внутри [ ... ].


Note

оператор &&, в зависимости от контекста, может так же использоваться в И-списках для построения составных команд.

||

логическое ИЛИ (or)

if [ $condition1 ] || [ $condition2 ]
# То же самое, что:  if [ $condition1 -o $condition2 ]
# Возвращает true если хотя бы один из операндов истинен...

if [[ $condition1 || $condition2 ]]    # Also works.
# Обратите внимание: оператор || не должен использоваться внутри [ ... ].


Note

Bash производит проверку кода возврата КАЖДОГО из операндов в логических выражениях.

Пример 8-3. Построение сложных условий, использующих && и ||

#!/bin/bash

a=24
b=47

if [ "$a" -eq 24 ] && [ "$b" -eq 47 ]
then
  echo "Первая проверка прошла успешно."
else
  echo "Первая проверка не прошла."
fi

# ОКА:  if [ "$a" -eq 24 && "$b" -eq 47 ]
#          пытается выполнить  ' [ "$a" -eq 24 '
#          и терпит неудачу наткнувшись на ']'.
#
#    if [[ $a -eq 24 && $b -eq 24 ]]   это правильный вариант
#    (в строке 17 оператор "&&" имеет иной смысл, нежели в строке 6.)
#    Спасибо Stephane Chazelas.


if [ "$a" -eq 98 ] || [ "$b" -eq 47 ]
then
  echo "Вторая проверка прошла успешно."
else
  echo "Вторая проверка не прошла."
fi


#  Опции -a и -o предоставляют
#+ альтернативный механизм проверки условий.
#  Спасибо Patrick Callahan.


if [ "$a" -eq 24 -a "$b" -eq 47 ]
then
  echo "Третья проверка прошла успешно."
else
  echo "Третья проверка не прошла."
fi


if [ "$a" -eq 98 -o "$b" -eq 47 ]
then
  echo "Четвертая проверка прошла успешно."
else
  echo "Четвертая проверка не прошла."
fi


a=rhino
b=crocodile
if [ "$a" = rhino ] && [ "$b" = crocodile ]
then
  echo "Пятая проверка прошла успешно."
else
  echo "Пятая проверка не прошла."
fi

exit 0

Операторы && и || могут использоваться и в арифметических вычислениях.

bash$ echo $(( 1 && 2 )) $((3 && 0)) $((4 || 0)) $((0 || 0))
1 0 1 0
        


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

,

запятая

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

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


Оператор запятая чаще всего находит применение в циклах for. См. Пример 10-12.