Bash операторы сравнения и прилеры базовых скриптов

Опубликовано: 2018-06-08
Операторы сравнения:
Примеры:

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

Возвращается true в случае, если:


Сравнение целочисленных значений:


Сравнение строк:


Примеры:

Проверка ссылок на корректность
#!/bin/bash
# broken-link.sh

# Безупречный shell-скрипт, предназначенный для поиска "мертвых" символических
# ссылок и вывода их имён в кавычках. Кавычки необходимы для того, чтобы
# можно было скормить ссылки команде xargs, которая сама с ними разберётся:
# sh broken-link.sh /somedir /someotherdir | xargs rm
#
# Это, однако, лучший метод:
#
# find "somedir" -type l -print0 |\
# xargs -r0 file |\
# grep "broken symbolic" |\
# sed -e 's/^\|: *broken symbolic.*$/"/g'
#
# но это не чистый Bash; мы это исправим.
# Предупреждение: опасайтесь файловой системы /proc и любых циклических ссылок!
# ----------------------------------------------------------

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

[ $# -eq 0 ] && directorys=`pwd` || directorys=$@

# Устанавливается функция linkchk проверяющая переданный ей с помощью
# аргумента каталог на наличие символических ссылок на несуществующие файлы
# и выводящая имена этих ссылок в кавычках.
# Если элемент директории - поддиректория, то он передается
# функции linkchk для обработки.

linkchk ()
{
     for element in $1/*; do
         [ -h "$element" -a ! -e "$element" ] && echo \"$element\"
         [ -d "$element" ] && linkchk $element
         # Конечно же, опция '-h' используется для проверки является ли файл ссылкой, а '-d' - является ли файл директорией.
     done
}

# Каждый полученный аргумент, который является корректной директорией, отправляется на
# обработку функции linkchk(). Если аргумент - не директория, то выводится сообщение об ошибке и
# информация по использованию скрипта.

for directory in $directorys
do
     if [ -d $directory ]
     then
         linkchk $directory
     else
         echo "$directory is not a directory"
         echo "Usage: $0 dir1 dir2 ..."
     fi
done

exit $?

Арифметические и строковые сравнения
#!/bin/bash

a=4
b=5

# Здесь переменные "a" и "b" могут быть рассмотрены либо как целые числа, либо как строки.
# Так как в Bash переменные не имеют четкого типа, есть различия между
# арифметическим и строковым сравнением.

# Bash разрешает производить арифметические операции и сравнения для переменных,
# значения которых целиком состоят из числовых символов.

echo

if [ "$a" -ne "$b" ]
then
     echo "$a не равно $b"
     echo "(арифметическое сравнение)"
fi

echo

if [ "$a" != "$b" ]
then
     echo "$a не равно $b."
     echo "(строковое сравнение)"
     #      "4" != "5"
     # ASCII 52 != ASCII 53
fi

# В данном частном случае оба варианта ( "-ne" и "!=" ) работают.

echo

exit 0

Проверка пустых (null) строк
#!/bin/bash
# str-test.sh: Проверка пустых и незаключенных в кавычки строк

# Исползование if [ ... ]
# Если строка не была проинициализирована, то она не имеет определённого значения.
# Такое состояние называют "null" (это не то же самое, что ноль!).

# string1 не была объявлена или инициализирована
if [ -n $string1 ]
then
     echo "Строка \"string1\" не пустая."
else
     echo "Строка \"string1\" пустая."
fi
# Получаем НЕверный результат.
# Проверка показывает, что $string1 не пустая, хотя и не была инициализирована.

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

# Давайте попробуем еще раз.
# На этот раз $string1 помещена в кавычки.
if [ -n "$string1" ]
then
     echo "Строка \"string1\" не пустая."
else
     echo "Строка \"string1\" пустая."
fi
# Заключайте строки в кавычки при использовании внутри квадратных скобок !

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

# Теперь $string1 осталась "голой".
if [ $string1 ]
then
     echo "Строка \"string1\" не пустая."
else
     echo "Строка \"string1\" пустая."
fi
# Сработало хорошо
# Оператор проверки [ ... ] без ключей и других операторов может определить пустую строку.
# Но, несомненно, хорошим тоном является всё-таки заключение в кавычки(if [ "$string1" ]).
# if [ $string1 ] имеет один аргумент: "]"
# if [ "$string1" ] имеет два аргумента: пустую переменную "$string1" и "]"

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

string1=initialized

# И снова $string1 остаётся без кавычек.
if [ $string1 ]
then
     echo "Строка \"string1\" не пустая."
else
     echo "Строка \"string1\" пустая."
fi
# Вновь получаем верный результат
# Но до сих пор предпочтительнее использовать кавычки ("$string1"), потому что . . .

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

string1="a = b"

# Опять $string1 без кавычек.
if [ $string1 ]
then
     echo "Строка \"string1\" не пустая."
else
     echo "Строка \"string1\" пустая."
fi
# Не заключённая в кавычки переменная "$string1" явилась причиной неверного результата!

exit 0

zmore
#!/bin/bash
# zmore

# Просмотр сжатых gzip файлов с использованием фильтра 'more'.

E_NOARGS=65
E_NOTFOUND=66
E_NOTGZIP=67

if [ $# -eq 0 ]
# Такой же эффект, как от: if [ -z "$1" ]
# $1 может существовать, но быть пустым: zmore "" arg2 arg3
then
     echo "Использование: `basename $0` имя_файла" >&2
     # Сообщение об ошибке направляем в stderr.
     exit $E_NOARGS
     # Скрипт возвращает значение 65 (код ошибки).
fi

filename=$1

if [ ! -f "$filename" ]
# Заключение в кавычки переменной $filename разрешает использование пробелов в ее содержимом.
then
     echo "Файл $filename не найден!" >&2
     # Сообщение об ошибке направляем в stderr.
     exit $E_NOTFOUND
fi

if [ ${filename##*.} != "gz" ]
# Используем фигурные скобки для подстановки.
then
     echo "Файл $1 не является архивом gzip!"
     exit $E_NOTGZIP
fi

zcat $1 | more

# Используем фильтр 'more'.
# При желании, можно использовать 'less'.

exit $?

# Скрипт возвращает код завершения канала.
# На самом деле команда "exit $?" вовсе необязательна, так как скрипт в любом случае
# вернёт код завершения последней выполненной команды.

Fileinfo: обработка списка файлов, содержащегося в переменной
#!/bin/bash
# fileinfo.sh

# Список интересующих нас файлов.
# В список добавлен фиктивный файл /usr/bin/fakefile.
FILES="/usr/sbin/accept
/usr/sbin/pwck
/usr/sbin/chroot
/usr/bin/fakefile
/sbin/badblocks
/sbin/ypbind"

echo

for file in $FILES

do
     # Проверка существования файла.
     if [ ! -e "$file" ]
     then
         echo "$file не существует."; echo
         continue
         # Переход к следующей итерации.
     fi

     # Печать 2 полей.
     ls -l $file | awk '{ print $8 "     размер файла: " $5 }'
     whatis `basename $file`
     # Информация о файле.
     # Заметим, что нужно создать индекс базы данных для whatis для того, чтобы предыдущая команда работала.
     # Чтобы сделать это, из-под пользователя root запустите /usr/bin/makewhatis.
     # (Для Debian/Ubuntu используйте команду /usr/bin/mandb - примеч. перев.)

     echo
done

exit 0

Обработка списка файлов в цикле for
#!/bin/bash
# list-glob.sh: Создание списка файлов в цикле for с использованием
# "глоббинга".

echo

for file in *
#           ^ Bash выполняет раскрытие имён файлов
#              в выражениях, которые являются результатом глоббинга.
do
     ls -l "$file"
     # Отсортированный список всех файлов в $PWD (текущем каталоге).
     # Напоминаю, что символу "*" соответствует любое имя файла,
     # однако, в для глоббинга имеется исключение - имена файлов, начинающиеся с точки.
     # Такие файлы по умолчанию не отображаются в списке совпадающих с шаблоном имен файлов.

     # Если шаблону не соответствует ни один файл, то за имя файла принимается сам шаблон.
     # Чтобы избежать этого, используйте ключ nullglob
     # (shopt -s nullglob).
done

echo
echo

for file in [jx]*
do
     rm -f $file
     # Удаление файлов, начинающихся с "j" или "x" в $PWD.
     echo "Удален файл \"$file\"".
done

echo

exit 0

grep для бинарных файлов
#!/bin/bash
# bin-grep.sh: Вывод строк, содержащих указанную подстроку в бинарном файле.

# Замена "grep" для бинарных файлов.
# Аналогична команде "grep -a"

E_BADARGS=65
E_NOFILE=66

if [ $# -ne 2 ]
then
     echo "Использование: `basename $0` искомая_строка имя_файла"
     exit $E_BADARGS
fi

if [ ! -f "$2" ]
then
     echo "Файл \"$2\" не существует."
     exit $E_NOFILE
fi

IFS=$'\012'
# В строке 23 раньше, было: IFS="\n"

for word in $( strings "$2" | grep "$1" )
# Команда "strings" возвращает список строк в двоичных файлах.
# Который затем передается по конвейеру команде "grep" для выполнения поиска.
do
     echo $word
done
# (30 строка)

# строки 23 - 30 могут быть заменены более простым
# strings "$2" | grep "$1" | tr -s "$IFS" '[\n*]'

exit 0

Список всех пользователей системы
#!/bin/bash
# userlist.sh

PASSWORD_FILE=/etc/passwd

# Число пользователей
n=1

for name in $( awk 'BEGIN{FS=":"}{print $1}' < "$PASSWORD_FILE" )
# Разделитель полей = :  ^^^^^
# Вывод первого поля                 ^^^^^^^
# Данные берутся из файла паролей                 ^^^^^^^^^^^^^
do
     echo "Пользователь #$n = $name"
     let "n += 1"
done

# Пользователь #1 = root
# Пользователь #2 = bin
# Пользователь #3 = daemon
# ...
# Пользователь #30 = bozo

exit 0

Список символических ссылок в каталоге
#!/bin/bash
# symlinks.sh: Список символических ссылок в каталоге.

directory=${1-`pwd`}
# По умолчанию в текущем каталоге, если не определен иной.
# Эквивалентен блоку кода, данному ниже.
# ----------------------------------------------------------
# # Ожидается один аргумент командной строки.
# ARGS=1
#
# # Если каталог поиска не задан...
# if [ $# -ne "$ARGS" ]
# then
#      directory=`pwd`
#      # текущий каталог
# else
#      directory=$1
# fi
# ----------------------------------------------------------

echo "symbolic links in directory \"$directory\""

for file in "$( find $directory -type l )"
# -type l = символические ссылки
do
     echo "$file"
done | sort
# sort иначе получится неотсортированный список.
# Строго говоря, в действительности цикл не нужен здесь,
# так как каждая выводимая командой "find" строка раскрывается в одиночное слово.
# Тем не менее, этот способ легко понять и проиллюстрировать.

# Как отмечает Доминик 'Aeneas' Шнитцер,
# в случае отсутствия кавычек для $( find $directory -type l )
# сценарий перестанет выполняться, как только встретится файл, содержащий пробел в имени.
# Сценарий "подавится" именами файлов, содержащими пробел.

exit 0
# альтернатива:

echo "symbolic links in directory \"$directory\""

# Сохранить текущее значение IFS в другой переменной. Никогда не помешает быть чуточку осмотрительным.
OLDIFS=$IFS
IFS=:

for file in $(find $directory -type l -printf "%p$IFS")
do
     echo "$file"
     done | sort
# можно изменить альтернативу таким образом:

OLDIFS=$IFS
# Пустое значение IFS означает, что нет разрыва между словами
IFS=''

for file in $( find $directory -type l )
do
     echo $file
     done | sort

# Это работает в "патологическом" случае - если имя каталога содержит двоеточие.
# "Это также исправляет патологический случай имени каталога, содержащего
# двоеточие (или пробел в примере выше)."

Использование команды efax в пакетном режиме
#!/bin/bash
# Отправка факса (пакет 'efax' должен быть установлен).

EXPECTED_ARGS=2
E_BADARGS=85
# Порт может быть другим на вашей машине. Порт по умолчанию PCMCIA-модема.
MODEM_PORT="/dev/ttyS2"

# Проверка необходимого количества аргументов командной строки.
if [ $# -ne $EXPECTED_ARGS ]
then
     echo "Использование: `basename $0` телефон# текстовый файл"
     exit $E_BADARGS
fi

if [ ! -f "$2" ]
then
     echo "Файл $2 не является текстовым файлом."
     # Не обычный (regular) файл, либо файл не существует.
     exit $E_BADARGS
fi

# Создать fax-файлы из текстовых файлов
fax make $2

for file in $(ls $2.0*)
# Соединять конвертированные файлы.
# используется шаблон ("глоббинг" имен файлов)
# в списке.
do
     fil="$fil $file"
done

# Отправить.
efax -d "$MODEM_PORT" -t "T$1" $fil
# Попробуйте добавить опцию "-o1", если команда выше завершилась неудачно.
# Можно обойтись без цикла for-loop с помощью
# efax -d /dev/ttyS2 -o1 -t "T$1" $2.0*
# но это не так поучительно [;-)].

exit $?

# efax также отправляет диагностические сообщения на стандартный вывод (stdout).

Простой цикл while
#!/bin/bash

var0=0
LIMIT=10

while [ "$var0" -lt "$LIMIT" ]
# Пробелы, так как используются квадратные скобки - аналог команды test . . .
do
     echo -n "$var0 "
     # -n подавляет перевод строки. Пробел, чтобы разделить выводимые числа.

     var0=`expr $var0 + 1`
     # var0=$(($var0+1)) также допустимо.
     # var0=$((var0 + 1)) также допустимо.
     # let "var0 += 1" также допустимо.
done

echo

exit 0

Другой пример цикла while
#!/bin/bash

echo

while [ "$var1" != "end" ]
# Эквивалентна команде: while test "$var1" != "end"
do
     echo "Введите значение переменной #1 (end - для выхода) "
     read var1
     echo "Значение переменной #1 = $var1"
     # кавычки обязательны, потому что имеется символ "#". . . .
     # Если введено слово 'end', то оно тоже выводится на экран,
     # потому, что проверка переменной выполняется в начале итерации (перед вводом).

     echo
done

exit 0

Результат работы break и continue в цикле
#!/bin/bash

# Верхний предел
LIMIT=19

echo
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 "
     # Эта строка не выполнится для 3 и 11.
done

echo
echo

# ----------------------------------------------------------
# Тот же цикл, только 'continue' заменено на 'break'.

a=0

while [ "$a" -le "$LIMIT" ]
do
     a=$(($a+1))

     if [ "$a" -gt 2 ]
     then
         break
         # Завершение работы цикла.
     fi

     echo -n "$a "
done

echo
echo
echo

exit 0

Реальный пример использования команды continue N
# Допустим, есть большое количество задач, обрабатывающих некоторые данные,
# которые хранятся в файлах с именами, задаваемыми по шаблону,
# в заданном каталоге.

# Есть несколько машин, которым открыт доступ к этому каталогу
# и я хочу распределить обработку информации между машинами.
# Тогда я для каждой машины обычно пишу нечто подобное:

while true
do
     for n in .iso.*
     do
         [ "$n" = ".iso.opts" ] && continue
         beta=${n#.iso.}
         #удаление подстроки ".iso" (см. гл. 9.2) - прим. перев.)
         [ -r .Iso.$beta ] && continue
         [ -r .lock.$beta ] && sleep 10 && continue
         lockfile -r0 .lock.$beta || continue
         echo -n "$beta: " `date`
         run-isotherm $beta
         date
         ls -alF .Iso.$beta
         [ -r .Iso.$beta ] && rm -f .lock.$beta
         continue 2
     done
     break
done
Тонкости использования, в особенности sleep N, индивидуальны для
каждого конкретного приложения, но основной шаблон выглядит так:
while true
do
     for job in {pattern}
     do
         {задача, уже выполненная или выполняемая} && continue
         {отметить задачу как запущенную, выполнить задачу, отметить задачу как выполненную}
         continue 2
     done
     break
     # Или что-то наподобие `sleep 600', если нужно избежать завершения цикла.
done

# Этот сценарий завершит работу после того как все данные будут обработаны
# (включая данные, которые поступили во время обработки). Использование
# соответствующих lock-файлов позволяет выполнять обработку на нескольких машинах
# одновременно, не производя дублирующих вычислений [которые, в моем случае,
# выполняются в течении нескольких часов, так что я реально хочу избежать этого].
# Кроме того, поскольку поиск необработанных файлов всегда начинается с
# самого начала, можно задавать приоритеты в именах файлов. Конечно, мы могли бы
# обойтись и без `continue 2', но тогда придется ввести дополнительную
# проверку -- действительно ли была выполнена или нет та или иная задача,
# так, чтобы мы немедленно перейти к поиску следующего необработанного файла.
# (В этом случае мы завершим или приостановим текущую задачу на долгое время для того, чтобы чтобы
# перейти к поиску следующего необработанного файла).

Использование case
#!/bin/bash
# Проверка диапазонов символов.

echo
echo "Нажмите клавишу и затем нажмите Return (Enter)."

read Keypress

case "$Keypress" in
     [[:lower:]]      ) echo "Буква в нижнем регистре";;
     [[:upper:]]     ) echo "Буква в верхнем регистре";;
     [0-9]             ) echo "Цифра";;
     *                  ) echo "Знак пунктуации, пробел или что-то другое";;
esac
# Допускаются диапазоны символов в [квадратных скобках]
# или POSIX-диапазоны в [[двойных квадратных скобках]].

# В первой версии этого примера,
# проверка для символов в верхнем или нижнем регистре учитывала только символы
# [a-z] и [A-Z].
# Это вообще не работает для определенных локалей и/или дистрибутивов Linux.
# Классы символов POSIX (см. BRE - Basic Regular Expressions - прим. перев.) более переносимы.

exit 0

Создание меню, используя 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 Flash Dr."
     echo "Hardscrabble, CO 80753"
     echo "(303) 734-9874"
     echo "(303) 734-9892 (факс)"
     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 (факс)"
     echo "milliej@loisaida.com"
     echo "Экс-подруга"
     echo "День рождения: 11 февраля"
     ;;

# Информация о Smith и Zane будет добавлена позднее.

     * )
     # Выбор по умолчанию.
     # Пустой ввод (после нажатия RETURN (ENTER)) тоже обрабатывается здесь.
     echo
     echo "Таких данных в базе нет."
     ;;

esac

echo

exit 0

Использование подстановки команды для генерирования значения переменной для case
#!/bin/bash
# case-cmd.sh: Использование подстановки команды для генерирования значения переменной для case.

case $( arch ) in
     # Команда "arch" возвращает тип архитектуры.
     # Эквивалентна команде 'uname -m' ...
     # (На некоторых системах (например Ubuntu 7.10 и выше)
     # команда arch может отсутствовать, прим. перев.)

     i386 ) echo "Машина на базе процессора 80386";;
     i486 ) echo "Машина на базе процессора 80486";;
     i586 ) echo "Машина на базе процессора Pentium";;
     i686 ) echo "Машина на базе процессора Pentium 2 или выше";;
     *     ) echo "Другой тип процессора";;
esac

exit 0

Простое сравнение строк
#!/bin/bash
# match-string.sh: Простое сравнение строк.

# Точное сравнение строк.
match_string ()
{
     MATCH=0
     E_NOMATCH=90
     PARAMS=2
     # Функция требует два аргумента.
     E_BAD_PARAMS=91

     [ $# -eq $PARAMS ] || return $E_BAD_PARAMS

     case "$1" in
         "$2" ) return $MATCH;;
         *     ) return $E_NOMATCH;;
     esac
}

a=one
b=two
c=three
d=two

match_string $a
echo $?
# Неверное количество параметров.
# 91

match_string $a $b
echo $?
# Нет совпадения
# 90

match_string $b $d
echo $?
# Совпадение
# 0

exit 0

Создание меню, используя select
#!/bin/bash

PS3='Выберите ваш любимый овощ: '
# Строка приглашения.
# Иначе по умолчанию берется #? .

echo

select vegetable in "бобы" "морковь" "картофель" "лук" "брюква"
do
     echo
     echo "Ваш любимый овощ: $vegetable."
     echo "Какая гадость!"
     echo
     break
done

exit 0

Создание меню, используя selectв функции
#!/bin/bash

PS3='Выберите ваш любимый овощ: '

echo

choice_of()
{
select vegetable
# Если [in list] не задан, то 'select' использует аргументы, переданные функции.
do
     echo
     echo "Ваш любимый овощ - $vegetable."
     echo "Фу, какая гадость!"
     echo
     break
done
}

choice_of бобы рис морковь редиска помидор шпинат
# $1 $2 $3 $4 $5 $6
# передано в функцию choice_of()

exit 0
© Все права защищены 2011-2018