{% youtube src="https://www.youtube.com/watch?v=CtIurIPJHow" %}{% endyoutube %}
В прошлый раз мы остановились на том, что сделали скрипт более универсальным – вместо двух определённых пользователей мы теперь можем создавать разных пользователей, используя переменные:
cat myscript
Но сделав это мы перестали соответствовать начальным требованиям. Во-первых, мы говорили, что если группа it, её следует предварительно добавить в sudoers. Но в нашем скрипте всего одна переменная group и какое бы значение она не получила, она всё равно добавляется в sudoers. Если я буду добавлять пользователя с группой users, то она тоже попадёт в sudoers. Во-вторых, мы говорили, что оболочка bash только у пользователей группы it, у users должен быть nologin, значит сейчас скрипт подходит только для группы it. Для users мы можем создать отдельный скрипт, подходящий им. Но также можем это сделать в рамках одного скрипта.
Начнём с sudoers. Сформулируем задачу: если переменная group получит значение it, то нужно добавить такую-то строчку в sudoers. Если же переменная group получит любое другое значение, ничего в sudoers добавлять не надо. И так, у нас появилось слово «если» – это значит, что мы имеем дело с условиями. bash умеет работать с условиями, для этого у него есть команда if. Синтаксис команды выглядит так - if условие then команда, которую мы хотим выполнить, в случае если условие выполнилось, и в конце fi, чтобы показать, где у нас заканчивается команда if:
if условие
then команда
fi
Если с командой понятно, то чем же является условие? Тоже командой – cp, ls, grep и всё в таком духе, то есть просто команда. Вы спросите – if ls, где тут условие? ls просто покажет список файлов, о каком условии идёт речь? На самом деле, if интересует не сама команда, а результат её выполнения, так называемый «код выхода» или «статус выхода». Обычно это числа от 0 до 255.
Мы упоминали статус выхода, когда говорили про зомби процессы – дочерний процесс при завершении должен передать родительскому свой статус выхода. Зачем? Это принятый в программировании способ родительским процессам узнать, как выполнился дочерний процесс, без необходимости копаться в выводе дочернего процесса. Для процесса, который выполнился без всяких проблем, обычно статус выхода – 0. Если же что-то пошло не так, то статус выхода другой, и по нему иногда легче понять причину проблемы.
В bash-е с помощью специальной переменной:
$?
можно узнать статус выхода последней выполненной команды. Для примера возьмём команду ls. Просто выполнив команду и посмотрев значение переменной:
ls
echo $?
мы увидим, что код выхода - 0. Теперь давайте выполним команду:
ls file
Команда ругается, что такого файла нет. Посмотрим статус выхода ещё раз:
echo $?
Как видите, теперь он 2. Есть определённые правила, в каких случаях какие числа пишутся, но сейчас нас интересует только 0.
Если процесс завершился с кодом 0, то всё окей. Все остальные коды считаются за ошибку. Именно так думает if. Давайте напишем маленький пример:
nano test
if ls
then echo Success, because exit code is $?
fi
cat test
./test
Как видите, сработала команда ls, после чего команда echo.
Давайте направим вывод команды ls в никуда, чтобы не было лишней информации:
nano test
if ls > /dev/null
then echo File exists
fi
./test
Теперь сделаем так, чтобы if получила не 0, а что-то другое:
nano test
if ls file > /dev/null
then echo File exists
fi
./test
Как видите, теперь у нас условие выдаёт не 0, а значит команда после then не сработает.
Можно ещё if дополнить с помощью else, которая будет запускать команду, если в if условие не сработает. Выглядит это так:
if условие
then команда, если 0
else команда, если не 0
fi
Например:
if ls file
then echo Success, because exit code is $?
else echo Failure, because exit status is $?
fi
cat test
./test
Как видите, сработала вторая команда.
Окей, по какому принципу работает if разобрались. Но этого недостаточно – нам нужно узнать, переменная group получила в качестве значение it или что-то другое. То есть нам нужна команда, которая сравнит значение переменной с каким-то текстом. Если всё окей, у неё статус выхода будет 0 и тогда if выполнит нужную команду. Такая команда есть и она, можно сказать, специально написана для if. Команда выглядит как открывающаяся квадратная скобка:
[
а после выражения внутри ставится закрывающаяся квадратная скобка:
[ выражение ]
Причём даже есть такая программа в /usr/bin:
ll /usr/bin/[
но у bash обычно есть встроенная версия этой программы:
type [
а внешняя программа может понадобится для других оболочек. Есть такая же программа с названием test:
type test
и даже более продвинутая версия с двумя скобками:
type [[
но она есть на bash и паре других оболочек, а не на всех. В общем, если хотим, чтобы нашими скриптами можно было пользоваться не только на bash, будем обходиться без двойных скобок.
С помощью квадратных скобок можно сравнивать много чего – строки, числа, наличие файлов, директорий, их права и многое другое. Программа сравнивает, выдаёт значение 0 или 1, а дальше if действует так, как мы обсуждали. По ссылке показан синтаксис для различных сравнений, а пока посмотрим нашу ситуацию.
Нам нужно сравнить переменную group и текст it:
[ $group = it ]
Для примера, дадим переменной значение users, сравним и посмотрим код выхода:
group=users
[ $group = it ]
echo $?
Как видите = 1, то есть неправильно. Теперь дадим переменной значение it, сравним и посмотрим код выхода:
group=it
[ $group = it ]
echo $?
0 – значит всё правильно.
Кстати, важное замечание, если в переменной есть пробелы, то сравнение работать не будет:
group="a b c"
[ $group = it ]
так как bash превратит нашу переменную в её значение и в выражении получится много параметров, типа:
[ a b c = it ]
а это не подходит под синтаксис. Поэтому лучше переменные брать в кавычки:
[ "$group" = it ]
Хорошо, с выражением разобрались, вернёмся к нашему скрипту и попробуем его добавить.
Вспомним наше условие - если переменная group получит значение it, то нужно добавить такую-то строчку в sudoers:
nano myscript
Находим нашу строчку с командой echo и окружаем её условием:
if [ "$group" = it ]
then
...
fi
Тут else нам не нужен, а команды после then можно чуток подвинуть направо с помощью пробелов, чтобы было проще для глаз.
Теперь вспомним второе условие - оболочка bash только у пользователей группы it, у users должен быть nologin. Тоже довольно просто – наверху создаём переменную shell, чтобы легче было её поменять при надобности и даём ей значение:
shell=/sbin/nologin
А в if добавляем:
shell=/bin/bash
И не забываем в конце указать эту переменную в качестве оболочки:
-s $shell
Если у нас группа будет it, то значение переменной в if поменяется на bash, а если не it, то условие не выполнится и переменная shell останется nologin.
Попробуем запустить скрипт 2 раза – один раз для пользователя с группой it, а потом для пользователя с группой users:
sudo ./myscript
tail /etc/passwd
Всё сработало.
Но если посмотреть sudoers:
sudo tail -5 /etc/sudoers
можно заметить, что строчка it повторяется много раз, хотя одной строчки вполне достаточно.
Давайте решим и эту проблему. Сформулируем задачу – если мы добавляем пользователя с группой it и если в sudoers нет нужной строчки – то её нужно создать. То есть, если выполнилось первое условие, нужно проверить второе условие и если оно тоже выполнилось – то только тогда добавлять строчку.
Первое условие у нас уже прописано. После его выполнения, то есть после then пишем ещё один if. Теперь нужно написать условие – если такой-то строчки нет в sudoers. Поиском строчек занимается команда grep. Посмотрим, что он нам выдаёт, если мы попытаемся найти строчку. Необязательно искать всю строчку, если есть что-то про группу it, то этого достаточно:
sudo grep '%it' /etc/sudoers
И посмотрим код выхода:
echo $?
Ноль. Посмотрим, что будет, если он не найдёт строчку – напишем в grep какую-то несуществующую группу, чтобы он ничего не нашёл:
sudo grep '%itаа' /etc/sudoers
и посмотрим ещё раз:
echo $?
Один.
И так, если строчек нет, то grep выдаёт 1, если есть – 0. Мне же нужно, чтобы запись создавалась, если строчек нет, то есть grep должен выдать 1, а if должен получить 0. Чтобы вот так вот перевернуть полученное значение, нужно после if поставить восклицательный знак. Давайте проверим на том же ls, чтобы было проще. И так, если ls не выполнился, то команда после then не должна сработать:
if ls file
then echo File exists
fi
Как видите, ls выдал ошибку и условие провалилось.
Но если мы после if поставим восклицательный знак:
if ! ls file
then echo File exits
fi
то ls, опять же, не сработал, но if перевернул значение, условие выполнилось и команда сработала.
Попробуем тоже самое с нашим скриптом:
if ! grep "%$group" /etc/sudoers"
...
И так, прочтём – если переменная group равна it запускается второе условие – если grep не нашёл упоминание группы it в sudoers, то следует добавить в sudoers эту группу.
Давайте проверим. Для начала удалим все упоминания группы it в sudoers:
sudo visudo
Потом запустим наш скрипт и создадим пользователя с группой it:
sudo ./myscript
Теперь добавим ещё одного пользователя с группой it:
sudo ./myscript
Проверяем:
sudo tail -3 /etc/sudoers
всё также одна строчка. Условие работает.
Мы с вами разобрали условие if, которое добавляет вашим скриптам логики, чтобы они могли проверять какие-то условия и по результатам менять какие-то переменные или выполнять дополнительные команды. Также разобрались, для чего нужны коды выхода и использовали их для команды if. Не забудем и про программу квадратных скобок, которая позволяет нам сравнивать переменные, строки, числа и проверять файлы. Мы ещё разберём, как в команде if проверять другие условия с помощью elif и много чего другого.