tech-tips/Языки программирования/Shell/Цикл статей от RUVDS/Bash-скрипты, часть 10- практические примеры - Хабр.md

363 lines
26 KiB
Markdown
Raw Permalink Normal View History

[https://habr.com/ru/company/ruvds/blog/328346/](https://habr.com/ru/company/ruvds/blog/328346/)
---
В предыдущих материалах мы обсуждали различные аспекты разработки bash-скриптов, говорили о полезных инструментах, но до сих пор рассматривали лишь небольшие фрагменты кода. Пришло время более масштабных проектов. А именно, здесь вы найдёте два примера. Первый — скрипт для отправки сообщений, второй пример — скрипт, выводящий сведения об использовании дискового пространства.
Главная ценность этих примеров для тех, кто изучает bash, заключается в методике разработки. Когда перед программистом встаёт задача по автоматизации чего бы то ни было, его путь редко бывает прямым и быстрым. Задачу надо разбить на части, найти средства решения каждой из подзадач, а потом собрать из частей готовое решение.
## Отправка сообщений в терминал пользователя
В наши дни редко кто прибегает к одной из возможностей Linux, которая позволяет общаться, отправляя сообщения в терминалы пользователей, вошедших в систему. Сама по себе команда отправки сообщений, `write`, довольно проста. Для того, чтобы ей воспользоваться, достаточно знать имя пользователя и имя его терминала. Однако, для успешной отправки сообщения, помимо актуальных данных о пользователе и терминале, надо знать, вошёл ли пользователь в систему, не запретил ли он запись в свой терминал. В результате, перед отправкой сообщения нужно выполнить несколько проверок.
Как видите, задача: «отправить сообщение», при ближайшем рассмотрении, оказалась задачей: «проверить возможность отправки сообщения, и, если нет препятствий, отправить его». Займёмся решением задачи, то есть — разработкой bash-скрипта.
### ▍Команды who и mesg
Ядром скрипта являются несколько команд, которые мы ещё не обсуждали. Всё остальное должно быть вам знакомо по предыдущим материалам.
Первое, что нам тут понадобится — команда `who`. Она позволяет узнать сведения о пользователях, работающих в системе. В простейшем виде её вызов выглядит так:
```
$ who
```
![[5d04710940fc2750d0f14b5b14d197d1.png]]
_Результаты вызова команды who_
В каждой строчке, которую выводит команда `who`, нас интересуют первых два показателя — имя пользователя и сведения о его терминале.
По умолчанию запись в терминал разрешена, но пользователь может, с помощью команды `mesg`, запретить отправку ему сообщений. Таким образом, прежде чем пытаться что-то кому-то отправить, неплохо будет проверить, разрешена ли отправка сообщений. Если нужно узнать собственный статус, достаточно ввести команду `mesg` без параметров:
```
$ mesg
```
![[6753391a3a4aeed1acae38862ac9d883.png]]
_Команда mesg_
В данном случае команда вывела «is y», это значит, что пользователь, под которым мы работаем в системе, может принимать сообщения, отправленные в его терминал. В противном случае `mesg` выведет «is n».
Для проверки того, разрешена ли отправка сообщений какому-то другому пользователю, можно использовать уже знакомую вам команду `who` с ключом `-T`:
```
$ who -T
```
При этом проверка возможна только для пользователей, которые вошли в систему. Если такая команда, после имени пользователя, выведет чёрточку (-), это означает, что пользователь запретил запись в свой терминал, то есть, сообщения ему отправлять нельзя. О том, что пользователю можно отправлять сообщения, говорит знак «плюс» (+).
Если у вас приём сообщений отключён, а вы хотите позволить другим пользователям отправлять вам сообщения, можно воспользоваться такой командой:
```
$ mesg y
```
![[f01eda2dc79b7040fd3d144aa06d676d.png]]
_Включение приёма сообщений от других пользователей_
После включения приёма сообщений `mesg` возвращает «is y».
Конечно, для обмена сообщениями нужны два пользователя, поэтому мы, после обычного входа в систему, подключились к компьютеру по ssh. Теперь можно поэкспериментировать.
### ▍Команда write
Основной инструмент для обмена сообщениями между пользователями, вошедшими в систему — команда `write`. Если приём сообщений у пользователя разрешён, с помощью этой команды ему можно отправлять сообщения, используя его имя и сведения о терминале.
Обратите внимание на то, что с помощью `write` можно отправлять сообщения пользователям, вошедшим в виртуальную консоль. Пользователи, которые работают в графическом окружении (KDE, Gnome, Cinnamon, и так далее), не могут получать подобные сообщения.
Итак, мы, работая под пользователем `likegeeks`, инициируем сеанс связи с пользователем `testuser`, который работает в терминале `pts/1`, следующим образом:
```
$ write testuser pts/1
```
![[511a3970ab4a8546fd6ff1f07f87a307.png]]
роверка возможности отправки сообщений и отправка сообщения_
После выполнения вышеуказанной команды перед нами окажется пустая строка, в которую нужно ввести первую строку сообщения. Нажав клавишу `ENTER`, мы можем ввести следующую строку сообщения. После того, как ввод текста завершён, окончить сеанс связи можно, воспользовавшись комбинацией клавиш `CTRL + D`, которая позволяет ввести [символ конца файла](https://habrahabr.ru/company/ruvds/blog/326826/).
Вот что увидит в своём терминале пользователь, которому мы отправили сообщение.
![[a9551626918b606b20c58d9e30ed8ea0.png]]
_Новое сообщение, пришедшее в терминал_
Получатель может понять от кого пришло сообщение, увидеть время, когда оно было отправлено. Обратите внимание на признак конца файла, `EOF`, расположенный в нижней части окна терминала. Он указывает на окончание текста сообщения.
Полагаем, теперь у нас есть всё необходимое для того, чтобы автоматизировать отправку сообщений с помощью сценария командной строки.
### ▍Создание скрипта для отправки сообщений
Прежде чем заниматься отправкой сообщений, нужно определить, вошёл ли интересующий нас пользователь в систему. Сделать это можно с помощью такой команды:
```
logged_on=$(who | grep -i -m 1 $1 | awk '{print $1}')
```
Здесь результаты работы команды `who` передаются команде `grep`. Ключ `-i` этой команды позволяет игнорировать регистр символов. Ключ `-m 1` включён в вызов команды на тот случай, если пользователь вошёл в систему несколько раз. Эта команда либо не выведет ничего, либо выведет имя пользователя (его мы укажем при вызове скрипта, оно попадёт в позиционную переменную `$1`), соответствующее первому найденному сеансу. Вывод `grep` мы передаём `awk`. Эта команда, опять же, либо не выведет ничего, либо выведет элемент, записанный в собственную переменную `$1`, то есть — имя пользователя. В итоге то, что получилось, попадает в переменную `logged_on`.
Теперь надо проверить переменную l`ogged_on`, посмотреть, есть ли в ней что-нибудь:
```
if [ -z $logged_on ]
then
echo "$1 is not logged on."
echo "Exit"
exit
fi
```
Если вы не вполне уверенно чувствуете себя, работая с конструкцией `if`, взгляните на [этот](https://habrahabr.ru/company/ruvds/blog/325522/) материал.
Скрипт, содержащий вышеописанный код, сохраним в файле
`senderscript` и вызовем, передав ему, в качестве параметра командной строки, имя пользователя `testuser`.
![[76150b96e45ce0d0fcb4eb0e536e0d4f.png]]
роверка статуса пользователя_
Тут мы проверяем, является ли `logged_on` переменной с нулевой длиной. Если это так, нам сообщат о том, что в данный момент пользователь в систему не вошёл и скрипт завершит работу с помощью команды `exit`. В противном случае выполнение скрипта продолжится.
### ▍Проверка возможности записи в терминал пользователя
Теперь надо проверить, принимает ли пользователь сообщения. Для этого понадобится такая конструкция, похожая на ту, которую мы использовали выше:
```
allowed=$(who -T | grep -i -m 1 $1 | awk '{print $2}')
if [ $allowed != "+" ]
then
echo "$1 does not allowing messaging."
echo "Exit"
exit
fi
```
![[a3e08cf12f617fd3ff123265f8342fcc.png]]
роверка возможности отправки сообщений пользователю_
Сначала мы вызываем команду `who` с ключом `-T`. В строке сведений о пользователе, который может принимать сообщения, окажется знак «плюс» (+), если же пользователь принимать сообщения не может — там будет чёрточка (-). То, что получилось после вызова `who`, передаётся `grep`, а потом — `awk`, формируя переменную `allowed`.
Далее, используя условный оператор, мы проверяем то, что оказалось в переменной `allowed`. Если знака «плюс» в ней нет, сообщим о том, что отправка сообщений пользователю запрещена и завершим работу. В противном случае выполнение сценария продолжится.
### ▍Проверка правильности вызова скрипта
Первым параметром скрипта является имя пользователя, которому мы хотим отправить сообщение. Вторым — текст сообщения, в данном случае — состоящий из одного слова. Для того, чтобы проверить, передано ли скрипту сообщение для отправки, воспользуемся таким кодом:
```
if [ -z $2 ]
then
echo "No message parameter included."
echo "Exit"
exit
fi
```
![[63b910246e010ac396f28324de51ce10.png]]
роверка параметров командной строки, указанных при вызове скрипта_
Тут, если при вызове скрипта ему не было передано сообщение для отправки, мы сообщаем об этом и завершаем работу. В противном случае — идём дальше.
### ▍Получение сведений о терминале пользователя
Прежде чем отправить пользователю сообщение, нужно получить сведения о терминале, в котором он работает и сохранить имя терминала в переменной. Делается это так:
```
terminal=$(who | grep -i -m 1 $1 | awk '{print $2}')
```
Теперь, после того, как все необходимые данные собраны, осталось лишь отправить сообщение:
```
echo $2 | write $logged_on $terminal
```
Вызов готового скрипта выглядит так:
```
$ ./senderscript testuser welcome
```
![[a22e36dd5b5c6755fbba9efe3a8e09a7.png]]
_Успешная отправка сообщения с помощью bash-скрипта_
Как видно, всё работает как надо. Однако, с помощью такого сценария можно отправлять лишь сообщения, состоящие из одного слова. Хорошо бы получить возможность отправлять более длинные сообщения.
### ▍Отправка длинных сообщений
Попробуем вызвать сценарий `senderscript`, передав ему сообщение, состоящее из нескольких слов:
```
$ ./senderscript likegeeks welcome to shell scripting
```
![[6a8062728f8d848966abbd3bcf95a5ed.png]]
опытка отправки длинного сообщения_
Как видно, отправлено было лишь первое слово. Всё дело в том, что каждое слово сообщения воспринимается внутри скрипта как отдельная позиционная переменная. Для того, чтобы получить возможность отправки длинных сообщений, обработаем параметры командной строки, переданные сценарию, воспользовавшись [командой](https://habrahabr.ru/company/ruvds/blog/326328/) `shift` и циклом `while`.
```
shift
while [ -n "$1" ]
do
whole_message=$whole_message' '$1
shift
done
```
После этого, в команде отправки сообщения, воспользуемся, вместо применяемой ранее позиционной переменной `$2`, переменной `whole_message`:
```
echo $whole_message | write $logged_on $terminal
```
Вот полный текст сценария:
```
#!/bin/bash
logged_on=$(who | grep -i -m 1 $1 | awk '{print $1}')
if [ -z $logged_on ]
then
echo "$1 is not logged on."
echo "Exit"
exit
fi
allowed=$(who -T | grep -i -m 1 $1 | awk '{print $2}')
if [ $allowed != "+" ]
then
echo "$1 does not allowing messaging."
echo "Exit"
exit
fi
if [ -z $2 ]
then
echo "No message parameter included."
echo "Exit"
exit
fi
terminal=$(who | grep -i -m 1 $1 | awk '{print $2}')
shift
while [ -n "$1" ]
do
whole_message=$whole_message' '$1
shift
done
echo $whole_message | write $logged_on $terminal
```
Испытаем его:
```
$ ./senderscript likegeeks welcome to shell scripting
```
![[c85623c06f86712b250620ce794d7e6e.png]]
_Успешная отправка длинного сообщения:_
Длинное сообщение успешно дошло до адресата. Теперь рассмотрим следующий пример.
## Скрипт для мониторинга дискового пространства
Сейчас мы собираемся создать сценарий командной строки, который предназначен для поиска в заданных директориях первой десятки папок, на которые приходится больше всего дискового пространства. В этом нам поможет [команда](https://likegeeks.com/main-linux-commands-easy-guide/#du-Command) `du`, которая выводит сведения о том, сколько места на диске занимают файлы и папки. По умолчанию она выводит сведения лишь о директориях, с ключом `-a` в отчёт попадают и отдельные файлы. Её ключ `-s` позволяет вывести сведения о размерах директорий. Эта команда позволяет, например, узнать объём дискового пространства, который занимают данные некоего пользователя. Вот как выглядит вызов этой команды:
```
$ du -s /var/log/
```
Для наших целей лучше подойдёт ключ `-S` (заглавная S), так как он позволяет получить сведения как по корневой папке, так и по вложенным в неё директориям:
```
$ du -S /var/log/
```
![[42bb5e8caec726f7d312cf1f9df25a49.png]]
_Вызов команды du с ключами -s и -S_
Нам нужно найти директории, на которые приходится больше всего дискового пространства, поэтому список, который выдаёт `du`, надо отсортировать, воспользовавшись командой `sort`:
```
$ du -S /var/log/ | sort -rn
```
![[cffef29a8ee74eb8992799d26d56fa16.png]]
_Отсортированный список объектов_
Ключ `-n` указывает команде на то, что нужна числовая сортировка, ключ `-r —` на обратный порядок сортировки (самое большое число окажется в начале списка). Полученные данные вполне подходят для наших целей.
Для того, чтобы ограничить полученный список первыми десятью записями, воспользуемся [потоковым редактором](https://habrahabr.ru/company/ruvds/blog/327530/) `sed`, который позволит удалить из полученного списка все строки, начиная с одиннадцатой. Следующий шаг — добавить к каждой полученной строке её номер. Тут также поможет `sed`, а именно — его команда `N`:
```
sed '{11,$D; =}' |
sed 'N; s/\n/ /' |
```
Приведём полученные данные в порядок, воспользовавшись `awk`. Передадим `awk` то, что получилось после обработки данных с помощью `sed`, применив, как и в других случаях, конвейер, и выведем полученные данные с помощью команды `printf`:
```
awk '{printf $1 ":" "\t" $2 "\t" $3 "\n"}'
```
В начале строки выводится её номер, потом идёт двоеточие и знак табуляции, далее — объём дискового пространства, следом — ещё один знак табуляции и имя папки.
Соберём вместе всё то, о чём мы говорили:
```
$ du -S /var/log/ |
sort -rn |
sed '{11,$D; =}' |
sed 'N; s/\n/ /' |
awk '{printf $1 ":" "\t" $2 "\t" $3 "\n"}'
```
![[2857bc4fb85059664b25f99ee26ae472.png]]
_Вывод сведений о дисковом пространстве_
Для того, чтобы повысить эффективность работы скрипта, код которого вы совсем скоро увидите, реализуем возможность получения данных сразу по нескольким директориям. Для этого создадим переменную `MY_DIRECTORIES` и внесём в неё список интересующих нас директорий:
```
MY_DIRECTORIES="/home /var/log"
```
Переберём список с помощью цикла `for` и вызовем вышеописанную последовательность команд для каждого элемента списка. Вот что получилось в результате:
```
#!/bin/bash
MY_DIRECTORIES="/home /var/log"
echo "Top Ten Disk Space Usage"
for DIR in $MY_DIRECTORIES
do
echo "The $DIR Directory:"
du -S $DIR 2>/dev/null |
sort -rn |
sed '{11,$D; =}' |
sed 'N; s/\n/ /' |
awk '{printf $1 ":" "\t" $2 "\t" $3 "\n"}'
done
exit
```
![[c1868fd4a7092a83026dfee3032b1a80.png]]
олучение сведений о нескольких директориях_
Как видите, скрипт выводит, в виде удобного списка, сведения о директориях, список которых хранится в `MY_DIRECTORIES`.
Команду `du` в этом скрипте можно вызвать с другими ключами, полученный список объектов вполне можно отфильтровать, в целом — тут открывается широкий простор для самостоятельных экспериментов. В результате, вместо работы со списком папок, можно, например, найти самые большие файлы с расширением `.log,` или реализовать более сложный алгоритм поиска самых больших (или самых маленьких) файлов и папок.
## Итоги
Сегодня мы подробно разобрали пару примеров разработки скриптов. Тут хотелось бы напомнить, что наша главная цель — не в том, чтобы написать скрипт для отправки сообщений с помощью команды `write`, или сценарий, который помогает в поиске файлов и папок, занимающих много места на диске, а в описании самого процесса разработки. Освоив эти примеры, поэкспериментировав с ними, возможно — дополнив их или полностью переработав, вы научитесь чему-то новому, улучшите свои навыки разработки bash-скриптов.
На сегодня это всё. В следующий раз поговорим об автоматизации работы с интерактивными утилитами с помощью expect.
Уважаемые читатели! Есть ли у вас на примете несложные (а может быть и сложные, но понятные) bash-скрипты, разбор которых будет полезен новичкам?