31. bash скрипты №5

В прошлый раз мы с вами разбирали циклы и функции. Если посмотреть список созданных циклом пользователей:

tail /etc/passwd

можно заметить ошибку – у всех пользователей оболочкой является bash, хотя так должно быть только у определённых пользователей.

Оболочка в нашем скрипте задаётся переменной shell и значение по умолчанию – nologin. При выполнении цикла запускается функция create_user, где есть условие, что если группой является it или security, указать значение переменной shell:

shell=/bin/bash

Функция у нас выполнится, создастся один пользователь, а при итерации значение переменной так и останется bash. Собственно поэтому у всех пользователей оболочкой остался bash.

Чтобы решить эту проблему, мы можем занести переменную shell в начало функции, чтобы при каждой итерации переменная получала значение nologin.

Касательно списка пользователей. В прошлый раз мы создали максимально простой файл, там были логины пользователей и группы, что легко было использовать в нашем скрипте. Но не всегда данные так хорошо подстроены под нас – вряд ли какой HR будет присылать нам файл в таком виде. Да и в большинстве реальных задач у вас есть файлы с большим количеством информации и вам нужно из этих файлов достать только нужное. Это чуть сложнее автоматизировать, но довольно увлекательно. В целом это называется парсингом – когда мы берём данные, структурируем их, чтобы в дальнейшем эти данные можно было использовать для других задач.

Для примера я подготовил excel файл со списком пользователей.

Такой файл не прочтёшь в командной строке - cat users.xlsx, так как это не просто текстовой файл. Но зачастую есть инструменты, которые позволяют превратить нечитаемые файлы в более подходящие форматы. Форматов много, ситуации разные, всё я вам показать не смогу, но с помощью гугла вы сможете найти многие решения. Давайте рассмотрим наш пример. И так, у нас есть excel таблица. Когда речь о каких-то таблицах, то обычно стоит попробовать превратить файл в csv формат.

К примеру, это умеет делать программа libreoffice – свободный офисный пакет программ. Хотя можно найти и установить более лёгкие альтернативы. Команда такая:

libreoffice --headless --convert-to csv users.xlsx

Теперь посмотрим полученный результат:

cat users.csv

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

И так, основы работы с таблицами. У таблицы есть две составляющие – столбцы и строки. Зачастую, в столбце у нас списки, а в столбике данные по определённому объекту. Например, второй столбец – это список имён, а вторая строка – вся информация про одного пользователя.

Мы уже знаем команду cut чтобы брать данные по столбцам. Тут у нас делителем выступает запятая. Допустим, выведем список всех департаментов:

cut -d, -f5 users.csv

Добавим ещё информации о пользователях:

cut -d, -f2,3,5 users.csv

Но первая строчка нам не очень нужна и будет мешать, поэтому нужно как-то начать со второй. Вспоминаем команду tail — с ключом -n она позволяет выводить текст с определённой строчки:

cut -d, -f2,3,5 users.csv | tail -n +2

Вот, собственно, это наши будущие пользователи и группы. Но для начала эту информацию надо превратить в более удобный вид.

Например, посмотрим на департаменты – все они содержат заглавные буквы. Мы знаем, что Linux – система регистрозависимая. У нас уже есть группа it:

grep -w it /etc/group

но команда groupadd IT создаст абсолютно другую группу, чего нам не нужно. Для удобства лучше держать все логины и группы в строчных буквах. То есть, мне нужно преобразовать все заглавные буквы в строчные.

Для этого мы можем использовать команду tr – translate. С помощью этой команды мы можем одни символы преобразовывать в другие, удалять какие-то символы и много всего интересного. Например, заменим o на 0:

echo Hello World
echo Hello World | tr o 0

Или заменим пробел на символ переноса строки:

echo Hello World | tr o 0 | tr ' ' '\n'

Заменим все строчные буквы на заглавные:

echo Hello World | tr o 0 | tr ' ' '\n' | tr a-z A-Z

Или наоборот:

echo Hello World | tr o 0 | tr ' ' '\n' | tr A-Z a-z

Хотя есть более правильный способ это сделать:

echo Hello World | tr '[:upper:]' '[:lower:]'

Более правильный, потому не во всех локалях буквы A и Z первая и последняя соответственно. Ну да ладно, вернёмся к нашей задаче. Превратим все заглавные буквы в строчные:

cut -d, -f2,3,5 users.csv | tail -n +2 | tr '[:upper:]' '[:lower:]'

Ну и для удобства можем все запятые заменить на пробелы:

cut -d, -f2,3,5 users.csv | tail -n +2 | tr '[:upper:]' '[:lower:]' | tr , ' '

Сохраним это в переменной:

users=$(cut -d, -f2,3,5 users.csv | tail -n +2 | tr '[:upper:]' '[:lower:]' | tr , ' ')

Обратите внимание – если просто писать echo $users - то bash подставляет значение переменной в строку, при этом используя переменную IFS, где разделителем является сначала пробел, потом табуляция и только потом перевод строки. Поэтому у нас эти четыре строки начали разделяться пробелом, а не переводом строки. Это легко решить, если взять переменную в кавычки:

echo "$users"

И так, у нас есть переменная, в которой содержится имя, фамилия и группа пользователя. Зачастую, в компаниях логин пользователя представляет из себя комбинацию имени и фамилии. Сделаем так, чтобы логин начинался с первой буквы имени, потом точка, а затем фамилия. Например, b.wayne. У нас в скрипте уже есть цикл, который построчно считывает список, поэтому для теста используем одну из строк:

echo "$users" | head -1

И так, для полученияпервой буквы имени, всё также используем cut, но с опцией -c1 – первый символ:

echo "$users" | head -1 | cut -c1

Для получения фамилии cut c делителем в виде пробела:

echo "$users" | head -1 | cut -d' ' -f2

Теперь объединим выводы этих команд с помощью echo:

echo $(echo "$users" | head -1 | cut -c1).$(echo "$users" | head -1 | cut -d' ' -f2)

Можно было бы предварительно превратить выводы команд в переменные, а уже потом объединять. Но, в целом, команда не такая сложная, да и head тут лишний, в конечном счёте мы будем работать с каждой строкой отдельно.

И так, логин мы получили, а группа и так в доступном виде. Теперь давайте добавим это в наш скрипт. Скопируем команду для получения списка пользователей:

cut -d, -f2,3,5 users.csv | tail -n +2 | tr '[:upper:]' '[:lower:]' | tr , ' '

и заменим в скрипте на эту команду. Заменим users.csv переменной $file. Скопируем команду для получения логина пользователя и вставим как значение переменной user. Заменим переменную users на line и уберём head:

user=$(echo $(echo "$line" | cut -c1).$(echo "$line" | cut -d' ' -f2))

Заменим значение переменной group:

group=$(echo "$line" | cut -d' ' -f3)

И так, вроде переменные user и group должны получить свои значения, остальные команды трогать не нужно.

Разве что заменим значение переменной file - file=/var/users.csv. Вы, по желанию, можете добавить, чтобы excel файл конвертировался при запуске скрипта. А пока скопируем сам файл:

sudo cp users.csv /var/

Давайте протестируем:

sudo ./myscript
tail /etc/passwd

Как видите, все пользователи создались, группы соответствующие, оболочки тоже.

Но задумайтесь вот о чём – если мы генерируем логины на основе имени и фамилии, в компании могут появиться тёзки, однофамильцы, какая-нибудь Clara Kent. Наш скрипт это проигнорирует, а команда useradd откажется создавать пользователя, так как такой логин уже есть. Поэтому такое вам задание – сделайте так, чтобы скрипт определял, есть ли уже такой логин, и, в случае чего, логин второго пользователя был немного другим, допустим, полное имя и фамилия, либо какое-то число в конце.

Скорее всего, вы будете определять по списку текущих пользователей. Наверняка вы обратитесь к файлу passwd, будете использовать cut и grep:

cut -d: -f1 /etc/passwd | grep b.wayne

Но такого grep-а может не хватить. Обратите внимание, я ищу пользователя user1:

cut -d: -f1 /etc/passwd | grep user1

Такого пользователя нет, но grep всё равно выдал мне результат, а скрипту главное результат. grep ищет соответствие, а все результаты содержат в себе user1. Мне нужно как-то сказать grep-u, что строчка в выводе должна заканчиваться на user1.

Для этого я могу использовать символ $ в конце искомой строки, то есть grep user1$:

cut -d: -f1 /etc/passwd | grep user1$

Теперь он ничего не нашёл. Но попробуем найти просто user$:

cut -d: -f1 /etc/passwd | grep user$

Как видите, опять несоответствие – есть некий rpcuser, в нём содержится слово user и оно заканчивается на него. Теперь мне нужно указать, чтобы строка начиналась на user – для этого можно использовать символ ^ - он называется карет. Указывая его перед выражением:

cut -d: -f1 /etc/passwd | grep ^user

мы говорим grep-u, что строка должна начинаться на эти символы.

И так, указывая карет перед выражением, а доллар в конце:

cut -d: -f1 /etc/passwd | grep ^user$

мы говорим grep-у, что строка должна начинаться и заканчиваться на слове user. Таким образом, с помощью карет и знака доллара мы использовали так называемые регулярные выражения. Это специальный язык, с помощью которого мы можем более гибко работать с текстом. Регулярных выражений много, их можно применять по разному. Это большая тема, которую мы разберём в другой раз. Однако, в случае с grep, если мы ищем точное совпадение, можно использовать ключ -w:

cut -d: -f1 /etc/passwd | grep -w user

Подводя итоги, сегодня мы с вами взяли excel файл, вытянули оттуда данные, преобразовали эти данные в нужный нам вид и использовали в нашем скрипте.