Курс
30. bash скрипты №4
В прошлый раз мы остановились на том, что создали файл и в скрипте добавили возможность брать данные о пользователе и группе из этого файла. Теперь же попробуем в файле указать несколько пользователей:
1
sudo nano /var/users
Copied!
и добавить их разом. Выполнив ту же команду cut:
1
cut -d' ' -f1 /var/users
Copied!
мы увидим весь список пользователей. Если передать команде useradd такой список пользователей, она этого не поймёт – useradd может добавлять только по одному пользователю за раз. А значит нам нужно будет для каждой строчки запускать отдельный useradd.
И так, задача у нас такая – для каждой строчки в файле /var/users создавать группу, проверять sudoers и создавать пользователя. То есть всё что ниже 30 строчки:
1
tail -n +30 myscript
Copied!
Если речь про повторное запускание одной и той же команды – то речь обычно о циклах. Есть две стандартные команды для работы с циклами – for и while. for обычно связан со списком, а while – с условием, хотя нередко можно использовать и ту, и другую. Давайте начнём с for. Синтаксис такой:
1
for переменная in список значений
2
do команды
3
done
Copied!
При запуске команды переменная получит первое значение из списка значений, потом выполнятся все команды, а после команд переменная получит второе значение из списка значений и опять выполнятся все команды. И так будет повторяться до тех пор, пока не закончатся все значения в списке, после чего цикл завершится. Каждое повторение называется итерацией.
Давайте посмотрим пример:
1
nano for
Copied!
1
for number in 1 two "line № 3"
2
do echo This is $number
3
done
Copied!
1
chmod +x for
2
./for
Copied!
Как видите, сначала переменная number получила первое значение – 1, выполнилась команда echo. Потом переменная number взяла второе значение – two. Ну и так далее. Вроде ничего сложного.
Список значений можно задать по разному, например, взять его из вывода команды:
1
nano for
Copied!
1
for line in $(cat /var/users)
2
do echo In this line: $line
3
done
Copied!
1
sudo ./for
Copied!
Но вместо того, чтобы увидеть в виде значения каждую строчку, мы видим пользователя и группу на разных строчках. То есть цикл сначала присвоил переменной line в виде значения имя первого юзера, а после итерации значение переменной стало имя группы. И так с каждой строчкой. То есть, вместо того, чтобы разделять значения построчно, значения разделялись по пробелам.
Помните, мы в команде cut использовали опцию -d – разделитель:
1
sudo cut -d' ' -f1 /var/users
Copied!
И мы этой опцией указали, что разделителем является пробел. bash, чтобы взять список значений, тоже использует разделитель – сначала он попытается разделить значения по пробелу, потом по табуляции и только потом по переводу строки. А чтобы bash в качестве разделителя использовал сразу перевод строки, нам нужно об этом ему сказать. Для этого есть переменная IFS – внутренний разделитель полей.
Чтобы указать, что мы хотим в качестве разделителя использовать перевод строки, даём переменной такое значение:
1
IFS=#x27;\n'
Copied!
\n – это newline. Если захотим знак табуляции меняем n на t:
1
IFS=#x27;\t'
Copied!
Если брать, например, /etc/passwd, то там разделителем выступает двоеточие, тогда можно указать так:
1
IFS=:
Copied!
Но с этой переменной нужно быть осторожным – другие команды в скрипте также могут использовать эту переменную, а значит то что у вас работало с пробелами, может начать работать с новыми строками. И чтобы не пришлось переделывать пол скрипта, мы можем поменять эту переменную временно, а потом вернуть старое значение. Но для этого нужно старое значение предварительно сохранить:
1
oldIFS=$IFS
Copied!
выполнения нужных команд можем восстановить старое значение:
1
IFS=$oldIFS
Copied!
Но нам сейчас нужен newline:
1
IFS=#x27;\n'
Copied!
Попробуем запустить скрипт:
1
sudo ./for
Copied!
Теперь всё окей – при каждой итерации переменная будет получать в качестве значения целую строчку.
Дальше нужно просто из этой переменной достать имя пользователя и группы, допустим, с помощью того же cut. А чтобы передать команде cut значение переменной, можно использовать пайп:
1
echo $line | cut -d' ' -f1
Copied!
В итоге мы достанем из строчки имя пользователя. И чтобы это имя стало переменной, напишем так:
1
user=$(echo $line | cut -d' ' -f1)
Copied!
Тоже самое с группой:
1
group=$(echo $line | cut -d' ' -f2)
Copied!
Последний штрих:
1
echo Username: $user Group: $group
Copied!
1
sudo ./for
Copied!
Как видите, всё сработало как надо. Теперь попытаемся сделать тоже самое с нашим скриптом.
Предварительно сохраним значение переменной IFS:
1
oldIFS=$IFS
Copied!
Тут у нас уже есть строчки назначения переменных из файла, но это нам не подходит, потому что нам нужно брать переменные в цикле, поэтому эти строчки убираем. Попробуем вписать сюда наш цикл:
1
IFS=#x27;\n'
2
for line in $(cat $file)
3
do
4
user=$(echo $line | cut -d' ' -f1)
5
group=$(echo line | cut -d' ' -f2)
6
echo Username: $user Group: $group
7
done
8
IFS=$oldIFS
Copied!
Запустим и проверим:
1
sudo ./myscript
2
tail -5 /etc/passwd
Copied!
У нас там было несколько пользователей, а создался только последний. Подумайте, почему так случилось?
Обратите внимание на наш цикл – переменные получают свои значения, выполняется команда echo, а после итерации всё происходит заново, пока не дойдёт до последнего значения. И только после этого начинают выполняться все остальные команды – группы, sudoers и т.д. Нам же нужно, чтобы с каждой итерацией выполнялись все эти команды.
Что мне мешает поставить done в конце скрипта? Мы сейчас находимся в условии – я не могу просто посреди for закончить условие if и продолжить выполнять команды for. Команда началась внутри условия – там же она должна закончится. Есть вариант скопировать все оставшиеся команды сюда. Но это плохой вариант – это увеличит размер скрипта, в дальнейшем придётся редактировать команды и внутри цикла, и отдельно.
Вот у нас есть куча команд и я не хочу, чтобы они повторялись в скрипте в нескольких местах. Чтобы решить эту проблему, я могу объединить все эти команды под одним названием. Для этого я пишу название, допустим: create_user() - ставлю после названия скобки, а потом внутри фигурных скобок указываю все нужные команды:
1
create_user() {
2
groupadd …
3
}
Copied!
Это называется функцией. И в дальнейшем, когда я захочу запустить все эти команды, я просто запущу команду:
1
create_user
Copied!
Но функция должна быть задана до того, как к ней обращаются, поэтому переместим нашу функцию наверх, скажем, после переменных. Но тут ещё один нюанс – IFS возвращает старое значение:
1
IFS=$oldIFS
Copied!
после выполнения цикла, а значит после выполнения всех команд в функции. А так как все наши команды там, то лучше перенести эту команду:
1
IFS=$oldIFS
Copied!
в начало функции.
А теперь пропишем её в наших условиях – просто написав create_user в командах каждого из условий.
Хорошо, давайте пройдёмся по скрипту. Вначале мы проверяем на root права. Задаём переменные. Создаём функцию – create_user – тут у нас все нужные команды для создания группы и пользователя. А в конце у нас проверка, как мы запускали программу – с параметрами, с файлом или интерактивно – в зависимости от этого назначаются переменные и запускается функция.
Окей, давайте протестируем:
1
sudo ./myscript
2
tail -5 /etc/passwd
Copied!
Как видите, все пользователи создались, всё работает.
Теперь немного проработаем наше интерактивное меню, то есть опцию else. Сейчас, при запуске скрипта, в этом режиме оно запросит юзернейм, пароль, создаст пользователя и закроется. Я бы хотел, чтобы после создания пользователя наш скрипт не завершался, а предлагал заново создать пользователя и всякие другие менюшки. Для этого мне понадобятся две команды. Первая будет показывать меню – это команда select. Синтаксис чем-то похож на for:
1
select переменная in список значений
2
do команды
3
done
Copied!
Например:
1
nano select
Copied!
1
select number in 1 2 3
2
do echo This is number: $number
3
done
Copied!
1
chmod+x select
2
./select
Copied!
select показал нам меню, где с помощью цифр мы можем выбрать какое-то из значений и переменная получит это значение. Дальше выполнится команда и после неё опять появится меню.
Теперь мне нужна команда, которая будет запускать какие-то команды в зависимости от значения переменной. Речь про команду case. Синтаксис такой:
1
case $переменная in
2
значение 1) команды;;
3
значение 2) команды ;;
4
*) команды, если значения нет в списке ;;
5
esac
Copied!
Например:
1
nano case
Copied!
1
number=one
2
case $number in
3
one) echo 1;;
4
two) echo 2;;
5
*) echo something wrong ;;
6
esac
Copied!
1
chmod +x case
2
./case
Copied!
Как видите, значение переменной было one. Оно подошло под первую опцию, в следствии чего сработала первая команда.
Теперь объединим select и case. Например:
1
select number in 1 2 3
2
do
3
case $number in
4
1) echo One;;
5
2) echo Two;;
6
3) echo Three;;
7
*) echo something wrong ;;
8
esac ;
9
done
Copied!
1
./case
Copied!
Теперь, select предлагает нам выбрать одно из значений, это значение назначается переменной number, затем case, в зависимости от значения переменной, запускает соответствующую команду.
А чтобы не застрять в бесконечной петле, в списке опций пропишем что-нибудь типа stop, а в case используем команду:
1
break
Copied!
чтобы прекратить цикл. После break цикл прерывается и начинают выполняться другие команды после цикла:
1
./case
Copied!
Теперь добавим это в нашем скрипте. Допустим, сделаем так, чтобы можно было добавить пользователя, посмотреть текущих пользователей, либо выйти:
1
select option in "Add user" "Show users" "Exit"
2
do
3
case $option in
4
"Add user") read -p … ;;
5
"Show users") cut -d: -f1 /etc/passwd ;;
6
"Exit") break ;;
7
*) echo Wrong option ;;
8
esac ;;
9
done
Copied!
Сохраним, удалим файл:
1
sudo rm /var/users
Copied!
чтобы наш скрипт предложил интерактивное меню и попробуем запустить скрипт:
1
sudo ./myscript
Copied!
Выбираем опцию 1 – у нас выходит приглашение ввести имя пользователя и группу. Окей, нажимаем enter – меню появилось ещё раз. Теперь выбираем 2 – и видим список всех пользователей. Выбираем 3 и выходим.
Подводя итоги, сегодня мы с вами разобрали команду for, с помощью которой мы можем создавать циклы; переменную IFS; функции, с помощью которых можем вызывать одну или несколько заранее прописанных команд; команду select, с помощью которой мы можем создать интерактивное меню; и команду case, с помощью которой мы можем запускать команды в зависимости от значения переменной.
Last modified 27d ago
Export as PDF
Copy link