231

Изучаем GNU/Linux часть 30. bash скрипты #4

Продолжаем изучать GNU/Linux и готовиться к сертификации от Red Hat (RHCSA).


Для тех, кто видит мои посты впервые - я стараюсь очень лёгким языком с нуля научить вас работать с операционной системой GNU/Linux. Зачем? Потому что - Стоит ли делать курс по RHCSA?


Предыдущие темы:

Изучаем GNU/Linux часть 29. bash скрипты #3

Изучаем GNU/Linux часть 28. bash скрипты #2

Изучаем GNU/Linux часть 27. bash скрипты #1

Изучаем GNU/Linux часть 26. Программный RAID - MD

Изучаем GNU/Linux часть 25. Управление логическими томами - LVM (RHCSA)

Изучаем GNU/Linux часть 24. Работа с файловыми системами (RHCSA)

Изучаем GNU/Linux часть 23. Основы файловых систем

Изучаем GNU/Linux часть 22. Работа с дисками (RHCSA)

Изучаем GNU/Linux часть 21. Ядро Linux

Изучаем GNU/Linux часть 20. Права на файлы (RHCSA)

Изучаем GNU/Linux часть 19. Пользователи и группы (RHCSA)

Изучаем GNU/Linux часть 18. Sudo (RHCSA)

Изучаем GNU/Linux часть 17. Su и visudo (RHCSA)

Изучаем GNU/Linux часть 16. Процессы #3: Работа с процессами (RHCSA)

Изучаем GNU/Linux часть 15. Процессы #2: Информация о процессах #2 (RHCSA)

Изучаем GNU/Linux часть 14. Процессы #1: Информация о процессах

Изучаем GNU/Linux часть 13. Bash #2: переменные (RHCSA)

Изучаем GNU/Linux часть 12. Bash #1: bash-completion, alias, type

Изучаем GNU/Linux часть 11. Стандартные потоки (RHCSA)


Ссылки на темы 1 лвла - Изучаем GNU/Linux часть 10. Текстовые редакторы nano и vi (RHCSA)


Цикл на примере for, IFS, функции, select, case.

P.S. Текст из видео в комментариях. Также потихоньку начинаю составлять полноценный текстовой вариант и он доступен по ссылке https://gitlab.com/doatta/gnu-linux-rhcsa . Стоит ли переделывать старые посты на пикабу для добавления текстового варианта - решать вам и модераторам.


P.P.S. Мне бы пригодилась помощь в создании большого количества заданий и вопросов для обучающихся -> Задания, вопросы и ответы

GNU/Linux

1.2K пост15.6K подписчика

Правила сообщества

Все дистрибутивы хороши.

Будьте людьми.

4
Автор поста оценил этот комментарий

В прошлый раз мы остановились на том, что создали файл и в скрипте добавили возможность брать данные о пользователе и группе из этого файла. Теперь же попробуем в файле указать несколько пользователей (sudo nano /var/users) и добавить их разом. Если теперь выполнить ту же команду cut (sudo cut -d’ ‘ -f1 /var/users), то мы увидим весь список пользователей. Если передать команде useradd такой список пользователей, она этого не поймёт – useradd может добавлять только по одному пользователю за раз. А значит нам нужно будет для каждой строчки запускать отдельный useradd.


И так, задача у нас такая – для каждой строчки в файле /var/users создавать группу, проверять sudoers и создавать пользователя. То есть всё что ниже 30 строчки. Если речь про повторное запускание одной и той же команды – то речь обычно о циклах. Есть две стандартные команды для работы с циклами – for и while. for обычно связан со списком, а while – с условием, хотя нередко можно использовать и ту, и другую. Давайте начнём с for (nano for). Синтаксис такой – for переменная in список значений do команды done. При запуске команды переменная получит первое значение из списка значений, потом выполнятся все команды, а после команд переменная получит второе значение из списка значений и опять выполняться все команды. И так будет повторяться до тех пор, пока не закончатся все значения в списке, после чего цикл завершится. Вот это повторение называется итерацией.


Давайте посмотрим пример (for number in 1 two “line № 3”; do echo This is $number; done; chmod +x for; cat for; ./for ). Как видите, сначала переменная number получила первое значение – 1, выполнилась команда echo. Потом переменная number взяла второе значение – two. Ну и так далее. Вроде ничего сложного. Список значений можно задать по разному, например, взять его из вывода команды (nano for; for line in $(cat /var/users); do echo In this line: $line; done; cat for; sudo ./for). Но вместо того, чтобы увидеть в виде значения каждую строчку, мы видим пользователя и группу на разных строчках. То есть цикл сначала присвоил переменной line в виде значения имя первого юзера, а после итерации значение переменной стало имя группы. И так с каждой строчкой. То есть вместо того, чтобы разделять значения построчно, значения разделялись по пробелам. Помните мы в команде cut использовали опцию -d – разделитель (sudo cut -d’ ‘ -f1 /var/users). И мы этой опцией указали, что разделителем является пробел. bash, чтобы взять список значений, тоже использует разделитель – сначала он попытается разделить значения по пробелу, потом по табуляции и только потом по переводу строки. А чтобы bash в качестве разделителя использовал сразу перевод строки, нам нужно об этом ему сказать. Для этого есть переменная IFS – внутренний разделитель полей. Чтобы указать, что мы хотим в качестве разделителя использовать перевод строки, даём переменной такое значение (IFS=$’\n’). \n – это newline. Если захотим знак табуляции меняем n на t (IFS=$’\t’). Если брать, например, /etc/passwd, то там разделителем выступает двоеточие, тогда можно указать так (IFS=:). Но с этой переменной нужно быть осторожным – другие команды в скрипте также могут использовать эту переменную, а значит то что у вас работало с пробелами, может начать работать с новыми строками. И чтобы не пришлось переделывать пол скрипта, мы можем поменять эту переменную временно, а потом вернуть старое значение. Но для этого нужно старое значение предварительно сохранить (oldIFS=$IFS). После выполнения нужных команд можем восстановить старое значение (IFS=$oldIFS).


Но нам сейчас нужен newline (IFS=$’\n’). Попробуем запустить скрипт (sudo ./for). Теперь всё окей – при каждой итерации переменная будет получать в качестве значения целую строчку. Дальше нужно просто из этой переменной достать имя пользователя и группы, допустим, с помощью того же cut. А чтобы передать команде cut значение переменной, можно использовать пайп (echo $line | cut -d’ ‘ -f1). В итоге мы достанем из строчки имя пользователя. И чтобы это имя стало переменной, напишем так ( user=$(echo $line | cut -d’ ‘ -f1) ). Тоже самое с группой ( group=$(echo $line | cut -d’ ‘ -f2) ). Последний штрих (echo Username: $user Group: $group; cat for; sudo ./for ). Как видите, всё сработало как надо. Теперь попытаемся сделать тоже самое с нашим скриптом.


Предварительно сохраним значение переменной IFS (oldIFS=$IFS). Тут у нас уже есть строчки назначения переменных из файла, но это нам не подходит, потому что нам нужно брать переменные в цикле, поэтому эти строчки убираем. Попробуем вписать сюда наш цикл ( IFS=$’\n’; for line in $(cat $file); do user=$(echo $line | cut -d’ ‘ -f1); group=$(echo line | cut -d’ ‘ -f2); echo Username: $user Group: $group; done; IFS=$oldIFS ). Запустим и проверим (sudo ./myscript; tail /etc/passwd). У нас там было несколько пользователей, а создался только последний. Остановите видео, прокрутите назад и подумайте, почему так случилось? Продолжим (nano myscript). Обратите внимание на наш цикл – переменные получают свои значения, выполняется команда echo, а после итерации всё происходит заново, пока не дойдёт до последнего значения. И только после этого начинают выполняться все остальные команды – группы, sudoers и т.д. Нам же нужно, чтобы с каждой итерацией выполнялись все эти команды.


Что мне мешает поставить done в конце скрипта? Мы сейчас находимся в условии – я не могу просто посреди for закончить условие if и продолжить выполнять команды for. Команда началась внутри условия – там же она должна закончится. Есть вариант скопировать все оставшиеся команды сюда. Но это плохой вариант – это увеличит размер скрипта, в дальнейшем придётся редактировать команды и внутри цикла, и отдельно. Вот у нас есть куча команд и я не хочу, чтобы они повторялись в скрипте в нескольких местах. Чтобы решить эту проблему, я могу объединить все эти команды под одним названием. Для этого я пишу название, допустим, create_user() - ставлю после названия скобки, а потом внутри фигурных скобок указываю все нужные команды (create_user() { groupadd … } ). Это называется функцией. И в дальнейшем, когда я захочу запустить все эти команды, я просто запущу команду create_user. Но функция должна быть задана до того, как к ней обращаются, поэтому переместим нашу функцию наверх, скажем, после переменных. А теперь пропишем её в наших условиях – просто написав create_user в командах каждого из условий. Но тут еще один нюанс – IFS возвращает старое значение (IFS=$oldIFS) после выполнения цикла, а значит после выполнения всех команд в функции. А так как все наши команды там, то лучше перенести эту команду (IFS=$oldIFS) в начало функции.


Хорошо, давайте пройдёмся по скрипту. Вначале мы проверяем на root права. Задаём переменные. Создаём функцию – create_user – тут у нас все нужные команды для создания группы и пользователя. А в конце у нас проверка, как мы запускали программу – с параметрами, с файлом или интерактивно – в зависимости от этого назначаются переменные и запускается функция. Окей, давайте протестируем (sudo./myscript; tail /etc/passwd ). Как видите, все пользователи создались, всё работает.


Теперь немного проработаем наше интерактивное меню, то есть опцию else. Сейчас при запуске скрипта в этом режиме оно запросит юзернейм, пароль, создаст пользователя и закроется. Я бы хотел, чтобы после создания пользователя наш скрипт не завершался, а предлагал заново создать пользователя и всякие другие менюшки. Для этого мне понадобятся две команды. Первая будет показывать меню – это команда select (nano select). Синтаксис чем-то похож на for – select переменная in список значений do команды done. Например, (select number in One Two Three do echo This is $number; done; chmod+x select; ./select ). select показал нам меню, где с помощью цифр мы можем выбрать какое-то из значений и переменная получит это значение. Дальше выполнится команда и после неё опять появится меню.

раскрыть ветку (1)
4
Автор поста оценил этот комментарий

Теперь мне нужна команда, которая в зависимости от значения переменной будет запускать какие-то команды. Речь про команду case (nano case). Синтаксис такой - case $переменная in значение1) команды;; значение2) команды ;; *) команды если значения нет в списке ;; esac. Например, ( number=one; case $number in one) echo 1;; two) echo 2;; *) echo something wrong ;; esac ; chmod +x case; ./case ). Как видите, значение переменной было one. Оно подошло под первую опцию, в следствии чего сработала первая команда.

Теперь объединим select и case. Например ( select number in one two three; do case $number in one) echo 1;; two) echo 2 ;; *) echo something wrong ;; esac ; done; ./case ). Теперь, select предлагает нам выбрать одно из значений, это значение назначается переменной number, затем case в зависимости от значения переменной запускает соответствующую команду. А чтобы не застрять в бесконечной петле, в списке опций пропишем что-нибудь типа stop, а в case используем команду break, чтобы прекратить цикл. После break цикл прерывается и начинают выполняться другие команды после цикла (./case).


Теперь добавим это в нашем скрипте. Допустим, сделаем так, чтобы можно было добавить пользователя, посмотреть текущих пользователей, либо выйти (select option in “Add user” “Show users” “Exit” do case $option in “Add user”) read -p … ;; “Show users”) cut -d: -f1 /etc/passwd ;; “Exit”) break ;; *) echo Wrong option ;; esac ;; done ). Сохраним, удалим файл (sudo rm /var/users), чтобы наш скрипт предложил интерактивное меню и попробуем запустить скрипт (sudo ./myscript). Выбираем опцию 1 – у нас выходит приглашение ввести имя пользователя и группу. Окей, нажимаем enter – меню появилось еще раз. Теперь выбираем 2 – и видим список всех пользователей. Выбираем 3 и выходим.

Подводя итоги, сегодня мы с вами разобрали команду for, с помощью которой мы можем создавать циклы; переменную IFS; функции, с помощью которых можем вызывать одну или несколько заранее прописанных команд; команду select, с помощью которой мы можем создать интерактивное меню; и команду case, с помощью которой мы можем запускать команды в зависимости от значения переменной.

4
Автор поста оценил этот комментарий

В прошлый раз мы остановились на том, что создали файл и в скрипте добавили возможность брать данные о пользователе и группе из этого файла. Теперь же попробуем в файле указать несколько пользователей (sudo nano /var/users) и добавить их разом. Если теперь выполнить ту же команду cut (sudo cut -d’ ‘ -f1 /var/users), то мы увидим весь список пользователей. Если передать команде useradd такой список пользователей, она этого не поймёт – useradd может добавлять только по одному пользователю за раз. А значит нам нужно будет для каждой строчки запускать отдельный useradd.


И так, задача у нас такая – для каждой строчки в файле /var/users создавать группу, проверять sudoers и создавать пользователя. То есть всё что ниже 30 строчки. Если речь про повторное запускание одной и той же команды – то речь обычно о циклах. Есть две стандартные команды для работы с циклами – for и while. for обычно связан со списком, а while – с условием, хотя нередко можно использовать и ту, и другую. Давайте начнём с for (nano for). Синтаксис такой – for переменная in список значений do команды done. При запуске команды переменная получит первое значение из списка значений, потом выполнятся все команды, а после команд переменная получит второе значение из списка значений и опять выполняться все команды. И так будет повторяться до тех пор, пока не закончатся все значения в списке, после чего цикл завершится. Вот это повторение называется итерацией.


Давайте посмотрим пример (for number in 1 two “line № 3”; do echo This is $number; done; chmod +x for; cat for; ./for ). Как видите, сначала переменная number получила первое значение – 1, выполнилась команда echo. Потом переменная number взяла второе значение – two. Ну и так далее. Вроде ничего сложного. Список значений можно задать по разному, например, взять его из вывода команды (nano for; for line in $(cat /var/users); do echo In this line: $line; done; cat for; sudo ./for). Но вместо того, чтобы увидеть в виде значения каждую строчку, мы видим пользователя и группу на разных строчках. То есть цикл сначала присвоил переменной line в виде значения имя первого юзера, а после итерации значение переменной стало имя группы. И так с каждой строчкой. То есть вместо того, чтобы разделять значения построчно, значения разделялись по пробелам. Помните мы в команде cut использовали опцию -d – разделитель (sudo cut -d’ ‘ -f1 /var/users). И мы этой опцией указали, что разделителем является пробел. bash, чтобы взять список значений, тоже использует разделитель – сначала он попытается разделить значения по пробелу, потом по табуляции и только потом по переводу строки. А чтобы bash в качестве разделителя использовал сразу перевод строки, нам нужно об этом ему сказать. Для этого есть переменная IFS – внутренний разделитель полей. Чтобы указать, что мы хотим в качестве разделителя использовать перевод строки, даём переменной такое значение (IFS=$’\n’). \n – это newline. Если захотим знак табуляции меняем n на t (IFS=$’\t’). Если брать, например, /etc/passwd, то там разделителем выступает двоеточие, тогда можно указать так (IFS=:). Но с этой переменной нужно быть осторожным – другие команды в скрипте также могут использовать эту переменную, а значит то что у вас работало с пробелами, может начать работать с новыми строками. И чтобы не пришлось переделывать пол скрипта, мы можем поменять эту переменную временно, а потом вернуть старое значение. Но для этого нужно старое значение предварительно сохранить (oldIFS=$IFS). После выполнения нужных команд можем восстановить старое значение (IFS=$oldIFS).


Но нам сейчас нужен newline (IFS=$’\n’). Попробуем запустить скрипт (sudo ./for). Теперь всё окей – при каждой итерации переменная будет получать в качестве значения целую строчку. Дальше нужно просто из этой переменной достать имя пользователя и группы, допустим, с помощью того же cut. А чтобы передать команде cut значение переменной, можно использовать пайп (echo $line | cut -d’ ‘ -f1). В итоге мы достанем из строчки имя пользователя. И чтобы это имя стало переменной, напишем так ( user=$(echo $line | cut -d’ ‘ -f1) ). Тоже самое с группой ( group=$(echo $line | cut -d’ ‘ -f2) ). Последний штрих (echo Username: $user Group: $group; cat for; sudo ./for ). Как видите, всё сработало как надо. Теперь попытаемся сделать тоже самое с нашим скриптом.


Предварительно сохраним значение переменной IFS (oldIFS=$IFS). Тут у нас уже есть строчки назначения переменных из файла, но это нам не подходит, потому что нам нужно брать переменные в цикле, поэтому эти строчки убираем. Попробуем вписать сюда наш цикл ( IFS=$’\n’; for line in $(cat $file); do user=$(echo $line | cut -d’ ‘ -f1); group=$(echo line | cut -d’ ‘ -f2); echo Username: $user Group: $group; done; IFS=$oldIFS ). Запустим и проверим (sudo ./myscript; tail /etc/passwd). У нас там было несколько пользователей, а создался только последний. Остановите видео, прокрутите назад и подумайте, почему так случилось? Продолжим (nano myscript). Обратите внимание на наш цикл – переменные получают свои значения, выполняется команда echo, а после итерации всё происходит заново, пока не дойдёт до последнего значения. И только после этого начинают выполняться все остальные команды – группы, sudoers и т.д. Нам же нужно, чтобы с каждой итерацией выполнялись все эти команды.


Что мне мешает поставить done в конце скрипта? Мы сейчас находимся в условии – я не могу просто посреди for закончить условие if и продолжить выполнять команды for. Команда началась внутри условия – там же она должна закончится. Есть вариант скопировать все оставшиеся команды сюда. Но это плохой вариант – это увеличит размер скрипта, в дальнейшем придётся редактировать команды и внутри цикла, и отдельно. Вот у нас есть куча команд и я не хочу, чтобы они повторялись в скрипте в нескольких местах. Чтобы решить эту проблему, я могу объединить все эти команды под одним названием. Для этого я пишу название, допустим, create_user() - ставлю после названия скобки, а потом внутри фигурных скобок указываю все нужные команды (create_user() { groupadd … } ). Это называется функцией. И в дальнейшем, когда я захочу запустить все эти команды, я просто запущу команду create_user. Но функция должна быть задана до того, как к ней обращаются, поэтому переместим нашу функцию наверх, скажем, после переменных. А теперь пропишем её в наших условиях – просто написав create_user в командах каждого из условий. Но тут еще один нюанс – IFS возвращает старое значение (IFS=$oldIFS) после выполнения цикла, а значит после выполнения всех команд в функции. А так как все наши команды там, то лучше перенести эту команду (IFS=$oldIFS) в начало функции.


Хорошо, давайте пройдёмся по скрипту. Вначале мы проверяем на root права. Задаём переменные. Создаём функцию – create_user – тут у нас все нужные команды для создания группы и пользователя. А в конце у нас проверка, как мы запускали программу – с параметрами, с файлом или интерактивно – в зависимости от этого назначаются переменные и запускается функция. Окей, давайте протестируем (sudo./myscript; tail /etc/passwd ). Как видите, все пользователи создались, всё работает.


Теперь немного проработаем наше интерактивное меню, то есть опцию else. Сейчас при запуске скрипта в этом режиме оно запросит юзернейм, пароль, создаст пользователя и закроется. Я бы хотел, чтобы после создания пользователя наш скрипт не завершался, а предлагал заново создать пользователя и всякие другие менюшки. Для этого мне понадобятся две команды. Первая будет показывать меню – это команда select (nano select). Синтаксис чем-то похож на for – select переменная in список значений do команды done. Например, (select number in One Two Three do echo This is $number; done; chmod+x select; ./select ). select показал нам меню, где с помощью цифр мы можем выбрать какое-то из значений и переменная получит это значение. Дальше выполнится команда и после неё опять появится меню.

показать ответы
1
Автор поста оценил этот комментарий

Нет, ну не так.

раскрыть ветку (1)
2
Автор поста оценил этот комментарий
Просто смешной момент :)
2
Автор поста оценил этот комментарий

Если Вы остановитесь, я найду Вас и заставлю продолжить. Шутка. Наверно.

раскрыть ветку (1)
1
DELETED
Автор поста оценил этот комментарий

Основная рабочая часть небольшая :). Это хорошо у вас монитор не 8k :-D.

В текущих записях, как вижу, уже экран меньше.

раскрыть ветку (1)
2
Автор поста оценил этот комментарий

Да, я по началу по глупости лепил видео в 2к. Потом понял, что тут FHD с головой хватит и одумался =)

показать ответы
Автор поста оценил этот комментарий

Нет, он просто здесь не нужен. Как доярке не нужна ядерная физика.

раскрыть ветку (1)
1
Автор поста оценил этот комментарий

На хабре - про ремонт квартиры - https://habr.com/ru/post/520874/
На пикабу - про линукс

Мир не тот, что прежде.

0
Автор поста оценил этот комментарий
Кстати, чем пишете скринкасты? Я вчера после долгих мучений открыл для себя obs-studio, а ffmpeg / recordmydesktop / {ещё несколько программ} не взлетели. То только мышь записывается, то вообще ничего не пишет (но размерчик выходного файла ого-го при этом).
раскрыть ветку (1)
1
Автор поста оценил этот комментарий
С помощью obs-studio. Как раз стримы тоже поддерживает, лепота
показать ответы
Автор поста оценил этот комментарий

Зря ты со своими линуксами пришел, пикабу для быдла, а быдло сидит на винде и ржет над несмешнымы мемасами про котов.

раскрыть ветку (1)
1
Автор поста оценил этот комментарий

Зачем оскорблять людей? Люди разные и всех под одну гребенку не надо. Пикабу просто сайт, на котором сидят люди - будь то быдло или интеллигенция. Плюс в том, что каждый находит что-то своё - кому интересны "несмешные мемасы про котов" - тот будет смотреть их. Но в целом вкусы - лично дело каждого. Лучшее, что можно сделать - думать о своих вкусах. Если вам нравится материал - можете поставить лайк/подписаться. Если не нравится -  заминусить и отправить в игнор. Единственная жизнь, которую вы живёте - свою. Поэтому оставьте заботы о чужих вкусах и наслаждайтесь своими =)

показать ответы
4
Автор поста оценил этот комментарий

Случайно наткнулся на Ваш пост.  И... Одно слово: БЛЯХАМУХА (это то что цензура позволила)!

ну вот почему до этого в ленте не попадались? А ведь это все приходилось гуглить и курить форумы.  Сохранил. Подписался. Пошел читать с первого поста :)

раскрыть ветку (1)
1
Автор поста оценил этот комментарий

Надеюсь будет полезно =)

показать ответы
0
DELETED
Автор поста оценил этот комментарий

Просьба уменьшить размер экрана при записи. На ноутбуке не видно текста.

Иллюстрация к комментарию
раскрыть ветку (1)
1
Автор поста оценил этот комментарий

Это было в 2-3 местах. В дальнейшем, если понадобится снова работать с интерфейсом virtualbox, постараюсь использовать виртуальную лупу.

показать ответы
0
DELETED
Автор поста оценил этот комментарий

Вопрос, который, быть может, уже задавали: что используете в качестве консоли для команд на винде? Я пользую старый TypeAndRun, из спортивного интереса интересны аналоги.

Иллюстрация к комментарию
раскрыть ветку (1)
0
Автор поста оценил этот комментарий

У меня на ноуте и десктопе Linux, пользуюсь albert. Коллега рассказывал что есть что-то похожее и крутое на Windows, но не помню название. Если интересно, могу завтра у него уточнить