На данном этапе освоения bash у вас вполне могут возникнуть вопросы о том, как управлять работающими скриптами, как автоматизировать их запуск.
До сих пор мы вводили имена скриптов в командную строку и нажимали Enter, что приводило к немедленному запуску программ, но это — не единственный способ вызова сценариев. Сегодня мы поговорим о том как скрипт может работать с сигналами Linux, о различных подходах к запуску скриптов и к управлению ими во время работы.
Сигналы Linux
В Linux существует более трёх десятков сигналов, которые генерирует система или приложения. Вот список наиболее часто используемых, которые наверняка пригодятся при разработке сценариев командной строки.
Если вы здесь, вероятно, вы столкнулись с проблемой изучения Bash команд. Давайте это исправим 🙂
Первые команды – учимся ходить
Современные файловые системы – это древовидные каталоги с корневой и родительскими директориями. Движение вверх по родительским каталогам приводит к корневому. В некоторых системах встречается несколько корневых каталогов, например, всем известные диски Windows: C:\ , A:\, etc.
Для того, чтобы полноценно обрабатывать тексты в bash-скриптах с помощью sed и awk, просто необходимо разобраться с регулярными выражениями. Реализации этого полезнейшего инструмента можно найти буквально повсюду, и хотя устроены все регулярные выражения схожим образом, основаны на одних и тех же идеях, в разных средах работа с ними имеет определённые особенности. Тут мы поговорим о регулярных выражениях, которые подходят для использования в сценариях командной строки Linux.
Иногда нужен более совершенный инструмент для обработки данных, нечто вроде языка программирования. Собственно говоря, такой инструмент — awk.
Утилита awk, или точнее GNU awk, в сравнении с sed, выводит обработку потоков данных на более высокий уровень. Благодаря awk в нашем распоряжении оказывается язык программирования, а не довольно скромный набор команд, отдаваемых редактору. С помощью языка программирования awk можно выполнять следующие действия:
В этом материале продолжим рассказ о bash-скриптах, поговорим об управляющих конструкциях, которые позволяют выполнять повторяющиеся действия. Речь идёт о циклах forи while, о методах работы с ними и о практических примерах их применения.
Если вы работаете в ИТ, то как никто знаете о том, как ценно время. Оптимизация рабочего процесса — один из важнейших аспектов работы в ИТ. Так или иначе, наша работа (будь то верстка сайта, написание модулей, или тестирования приложений) требует повторения одних и тех же действий: быстрые скриншоты с загрузкой на сервер, обработка выделенного текста, конвертация файлов, парсинг данных и многое другое. Чтобы не делать лишних действий, а сконцентрироваться на идее и самой сути ее реализации, еще в 1978 году Стивен Борн разработал командную оболочку sh, которая впоследствии, в 1987 году была усовершенствована Брайаном Фоксом и переросла в то, что мы знаем сегодня как bash (Bourne again shell).
Вполне логично, что появляется вопрос: «Для чего мне нужно что-то, что написали почти полвека назад?» Так вот ответ на него прост: это «что-то» до сих пор является самым мощным инструментом автоматизации и, де-факто, стандартом для написания простых, но эффективных сценариев на всех unix-based системах. Именно поэтому знать общий синтаксис bash и уметь писать на нем — критический скилл для разработчика.
В этом посте автор описал синтаксические конструкции, привел примеры простых сценариев и указал на некоторые подводные камни в bash. Он уверен, что такой конспект обязательно пригодится как начинающим, так и опытным разработчикам, которые могли забыть некоторые особенности этой сверхмощной командной оболочки.
Сегодня мы поговорим о том, как добавить скриптам интерактивности, оснастив их возможностями по получению данных от пользователя и по обработке этих данных.
Пришло время более масштабных проектов. А именно, здесь вы найдёте два примера. Первый — скрипт для отправки сообщений, второй пример — скрипт, выводящий сведения об использовании дискового пространства.
Главная ценность этих примеров для тех, кто изучает bash, заключается в методике разработки. Когда перед программистом встаёт задача по автоматизации чего бы то ни было, его путь редко бывает прямым и быстрым. Задачу надо разбить на части, найти средства решения каждой из подзадач, а потом собрать из частей готовое решение.
Отправка сообщений в терминал пользователя
В наши дни редко кто прибегает к одной из возможностей Linux, которая позволяет общаться, отправляя сообщения в терминалы пользователей, вошедших в систему. Сама по себе команда отправки сообщений, write, довольно проста. Для того, чтобы ей воспользоваться, достаточно знать имя пользователя и имя его терминала. Однако, для успешной отправки сообщения, помимо актуальных данных о пользователе и терминале, надо знать, вошёл ли пользователь в систему, не запретил ли он запись в свой терминал. В результате, перед отправкой сообщения нужно выполнить несколько проверок.
Как видите, задача: «отправить сообщение», при ближайшем рассмотрении, оказалась задачей: «проверить возможность отправки сообщения, и, если нет препятствий, отправить его». Займёмся решением задачи, то есть — разработкой bash-скрипта.
▍Команды who и mesg
Ядром скрипта являются несколько команд, которые мы ещё не обсуждали. Всё остальное должно быть вам знакомо по предыдущим материалам.
Первое, что нам тут понадобится — команда who. Она позволяет узнать сведения о пользователях, работающих в системе. В простейшем виде её вызов выглядит так:
1
$who
Результаты вызова команды who
В каждой строчке, которую выводит команда who, нас интересуют первых два показателя — имя пользователя и сведения о его терминале.
По умолчанию запись в терминал разрешена, но пользователь может, с помощью команды mesg, запретить отправку ему сообщений. Таким образом, прежде чем пытаться что-то кому-то отправить, неплохо будет проверить, разрешена ли отправка сообщений. Если нужно узнать собственный статус, достаточно ввести команду mesg без параметров:
1
$mesg
Команда mesg
В данном случае команда вывела «is y», это значит, что пользователь, под которым мы работаем в системе, может принимать сообщения, отправленные в его терминал. В противном случае mesg выведет «is n».
Для проверки того, разрешена ли отправка сообщений какому-то другому пользователю, можно использовать уже знакомую вам команду who с ключом -T:
1
$who-T
При этом проверка возможна только для пользователей, которые вошли в систему. Если такая команда, после имени пользователя, выведет чёрточку (-), это означает, что пользователь запретил запись в свой терминал, то есть, сообщения ему отправлять нельзя. О том, что пользователю можно отправлять сообщения, говорит знак «плюс» (+).
Если у вас приём сообщений отключён, а вы хотите позволить другим пользователям отправлять вам сообщения, можно воспользоваться такой командой:
1
$mesgy
Включение приёма сообщений от других пользователей
После включения приёма сообщений mesg возвращает «is y».
Конечно, для обмена сообщениями нужны два пользователя, поэтому мы, после обычного входа в систему, подключились к компьютеру по ssh. Теперь можно поэкспериментировать.
▍Команда write
Основной инструмент для обмена сообщениями между пользователями, вошедшими в систему — команда write. Если приём сообщений у пользователя разрешён, с помощью этой команды ему можно отправлять сообщения, используя его имя и сведения о терминале.
Обратите внимание на то, что с помощью write можно отправлять сообщения пользователям, вошедшим в виртуальную консоль. Пользователи, которые работают в графическом окружении (KDE, Gnome, Cinnamon, и так далее), не могут получать подобные сообщения.
Итак, мы, работая под пользователем likegeeks, инициируем сеанс связи с пользователем testuser, который работает в терминале pts/1, следующим образом:
1
$writetestuserpts/1
Проверка возможности отправки сообщений и отправка сообщения
После выполнения вышеуказанной команды перед нами окажется пустая строка, в которую нужно ввести первую строку сообщения. Нажав клавишу ENTER, мы можем ввести следующую строку сообщения. После того, как ввод текста завершён, окончить сеанс связи можно, воспользовавшись комбинацией клавиш CTRL+D, которая позволяет ввести символ конца файла.
Вот что увидит в своём терминале пользователь, которому мы отправили сообщение.
Новое сообщение, пришедшее в терминал
Получатель может понять от кого пришло сообщение, увидеть время, когда оно было отправлено. Обратите внимание на признак конца файла, EOF, расположенный в нижней части окна терминала. Он указывает на окончание текста сообщения.
Полагаем, теперь у нас есть всё необходимое для того, чтобы автоматизировать отправку сообщений с помощью сценария командной строки.
▍Создание скрипта для отправки сообщений
Прежде чем заниматься отправкой сообщений, нужно определить, вошёл ли интересующий нас пользователь в систему. Сделать это можно с помощью такой команды:
1
logged_on=$(who|grep-i-m1$1|awk'{print $1}')
Здесь результаты работы команды who передаются команде grep. Ключ -i этой команды позволяет игнорировать регистр символов. Ключ -m1 включён в вызов команды на тот случай, если пользователь вошёл в систему несколько раз. Эта команда либо не выведет ничего, либо выведет имя пользователя (его мы укажем при вызове скрипта, оно попадёт в позиционную переменную $1), соответствующее первому найденному сеансу. Вывод grep мы передаём awk. Эта команда, опять же, либо не выведет ничего, либо выведет элемент, записанный в собственную переменную $1, то есть — имя пользователя. В итоге то, что получилось, попадает в переменную logged_on.
Теперь надо проверить переменную l ogged_on, посмотреть, есть ли в ней что-нибудь:
1
2
3
4
5
6
if[-z$logged_on ]
then
echo"$1 is not logged on."
echo"Exit"
exit
fi
Скрипт, содержащий вышеописанный код, сохраним в файле senderscript и вызовем, передав ему, в качестве параметра командной строки, имя пользователя testuser.
Проверка статуса пользователя
Тут мы проверяем, является ли logged_on переменной с нулевой длиной. Если это так, нам сообщат о том, что в данный момент пользователь в систему не вошёл и скрипт завершит работу с помощью команды exit. В противном случае выполнение скрипта продолжится.
▍Проверка возможности записи в терминал пользователя
Теперь надо проверить, принимает ли пользователь сообщения. Для этого понадобится такая конструкция, похожая на ту, которую мы использовали выше:
1
2
3
4
5
6
7
allowed=$(who-T|grep-i-m1$1|awk'{print $2}')
if[$allowed!="+"]
then
echo"$1 does not allowing messaging."
echo"Exit"
exit
fi
Проверка возможности отправки сообщений пользователю
Сначала мы вызываем команду who с ключом -T. В строке сведений о пользователе, который может принимать сообщения, окажется знак «плюс» (+), если же пользователь принимать сообщения не может — там будет чёрточка (-). То, что получилось после вызова who, передаётся grep, а потом — awk, формируя переменную allowed.
Далее, используя условный оператор, мы проверяем то, что оказалось в переменной allowed. Если знака «плюс» в ней нет, сообщим о том, что отправка сообщений пользователю запрещена и завершим работу. В противном случае выполнение сценария продолжится.
▍Проверка правильности вызова скрипта
Первым параметром скрипта является имя пользователя, которому мы хотим отправить сообщение. Вторым — текст сообщения, в данном случае — состоящий из одного слова. Для того, чтобы проверить, передано ли скрипту сообщение для отправки, воспользуемся таким кодом:
1
2
3
4
5
6
if[-z$2]
then
echo"No message parameter included."
echo"Exit"
exit
fi
Проверка параметров командной строки, указанных при вызове скрипта
Тут, если при вызове скрипта ему не было передано сообщение для отправки, мы сообщаем об этом и завершаем работу. В противном случае — идём дальше.
▍Получение сведений о терминале пользователя
Прежде чем отправить пользователю сообщение, нужно получить сведения о терминале, в котором он работает и сохранить имя терминала в переменной. Делается это так:
1
terminal=$(who|grep-i-m1$1|awk'{print $2}')
Теперь, после того, как все необходимые данные собраны, осталось лишь отправить сообщение:
1
echo$2|write$logged_on $terminal
Вызов готового скрипта выглядит так:
1
$./senderscripttestuserwelcome
Успешная отправка сообщения с помощью bash-скрипта
Как видно, всё работает как надо. Однако, с помощью такого сценария можно отправлять лишь сообщения, состоящие из одного слова. Хорошо бы получить возможность отправлять более длинные сообщения.
▍Отправка длинных сообщений
Попробуем вызвать сценарий senderscript, передав ему сообщение, состоящее из нескольких слов:
1
$./senderscriptlikegeekswelcometoshellscripting
Попытка отправки длинного сообщения
Как видно, отправлено было лишь первое слово. Всё дело в том, что каждое слово сообщения воспринимается внутри скрипта как отдельная позиционная переменная. Для того, чтобы получить возможность отправки длинных сообщений, обработаем параметры командной строки, переданные сценарию, воспользовавшись командой shift и циклом while.
1
2
3
4
5
6
shift
while[-n"$1"]
do
whole_message=$whole_message' '$1
shift
done
После этого, в команде отправки сообщения, воспользуемся, вместо применяемой ранее позиционной переменной $2, переменной whole_message:
1
echo$whole_message|write$logged_on $terminal
Вот полный текст сценария:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash
logged_on=$(who|grep-i-m1$1|awk'{print $1}')
if[-z$logged_on ]
then
echo"$1 is not logged on."
echo"Exit"
exit
fi
allowed=$(who-T|grep-i-m1$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-m1$1|awk'{print $2}')
shift
while[-n"$1"]
do
whole_message=$whole_message' '$1
shift
done
echo$whole_message|write$logged_on $terminal
Испытаем его:
1
$./senderscriptlikegeekswelcometoshellscripting
Успешная отправка длинного сообщения:
Длинное сообщение успешно дошло до адресата. Теперь рассмотрим следующий пример.
Скрипт для мониторинга дискового пространства
Сейчас мы собираемся создать сценарий командной строки, который предназначен для поиска в заданных директориях первой десятки папок, на которые приходится больше всего дискового пространства. В этом нам поможет команда du, которая выводит сведения о том, сколько места на диске занимают файлы и папки. По умолчанию она выводит сведения лишь о директориях, с ключом -a в отчёт попадают и отдельные файлы. Её ключ -s позволяет вывести сведения о размерах директорий. Эта команда позволяет, например, узнать объём дискового пространства, который занимают данные некоего пользователя. Вот как выглядит вызов этой команды:
1
$du-s/var/log/
Для наших целей лучше подойдёт ключ -S (заглавная S), так как он позволяет получить сведения как по корневой папке, так и по вложенным в неё директориям:
1
$du-S/var/log/
Вызов команды du с ключами -s и -S
Нам нужно найти директории, на которые приходится больше всего дискового пространства, поэтому список, который выдаёт du, надо отсортировать, воспользовавшись командой sort:
1
$du-S/var/log/|sort-rn
Отсортированный список объектов
Ключ -n указывает команде на то, что нужна числовая сортировка, ключ -r — на обратный порядок сортировки (самое большое число окажется в начале списка). Полученные данные вполне подходят для наших целей.
Для того, чтобы ограничить полученный список первыми десятью записями, воспользуемся потоковым редактором sed, который позволит удалить из полученного списка все строки, начиная с одиннадцатой. Следующий шаг — добавить к каждой полученной строке её номер. Тут также поможет sed, а именно — его команда N:
1
2
sed'{11,$D; =}'|
sed'N; s/\n/ /'|
Приведём полученные данные в порядок, воспользовавшись awk. Передадим awk то, что получилось после обработки данных с помощью sed, применив, как и в других случаях, конвейер, и выведем полученные данные с помощью команды printf:
1
awk'{printf $1 ":" "\t" $2 "\t" $3 "\n"}'
В начале строки выводится её номер, потом идёт двоеточие и знак табуляции, далее — объём дискового пространства, следом — ещё один знак табуляции и имя папки.
Соберём вместе всё то, о чём мы говорили:
1
2
3
4
5
$du-S/var/log/|
sort-rn|
sed'{11,$D; =}'|
sed'N; s/\n/ /'|
awk'{printf $1 ":" "\t" $2 "\t" $3 "\n"}'
Вывод сведений о дисковом пространстве
Для того, чтобы повысить эффективность работы скрипта, код которого вы совсем скоро увидите, реализуем возможность получения данных сразу по нескольким директориям. Для этого создадим переменную MY_DIRECTORIES и внесём в неё список интересующих нас директорий:
1
MY_DIRECTORIES="/home /var/log"
Переберём список с помощью цикла for и вызовем вышеописанную последовательность команд для каждого элемента списка. Вот что получилось в результате:
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
MY_DIRECTORIES="/home /var/log"
echo"Top Ten Disk Space Usage"
forDIRin$MY_DIRECTORIES
do
echo"The $DIR Directory:"
du-S$DIR2>/dev/null|
sort-rn|
sed'{11,$D; =}'|
sed'N; s/\n/ /'|
awk'{printf $1 ":" "\t" $2 "\t" $3 "\n"}'
done
exit
Получение сведений о нескольких директориях
Как видите, скрипт выводит, в виде удобного списка, сведения о директориях, список которых хранится в MY_DIRECTORIES.
Команду du в этом скрипте можно вызвать с другими ключами, полученный список объектов вполне можно отфильтровать, в целом — тут открывается широкий простор для самостоятельных экспериментов. В результате, вместо работы со списком папок, можно, например, найти самые большие файлы с расширением .log, или реализовать более сложный алгоритм поиска самых больших (или самых маленьких) файлов и папок.
В прошлый раз мы говорили о функциях в bash-скриптах, в частности, о том, как вызывать их из командной строки. Наша сегодняшняя тема — весьма полезный инструмент для обработки строковых данных — утилита Linux, которая называется sed. Её часто используют для работы с текстами, имеющими вид лог-файлов, конфигурационных и других файлов.
Если вы, в bash-скриптах, каким-то образом обрабатываете данные, вам не помешает знакомство с инструментами sed и gawk. Тут мы сосредоточимся на sed и на работе с текстами, так как это — очень важный шаг в нашем путешествии по бескрайним просторам разработки bash-скриптов.
Занимаясь разработкой bash-скриптов, вы рано или поздно столкнётесь с тем, что вам периодически приходится использовать одни и те же фрагменты кода. Постоянно набирать их вручную скучно, а копирование и вставка — не наш метод. Как быть? Хорошо бы найти средство, которое позволяет один раз написать блок кода и, когда он понадобится снова, просто сослаться на него в скрипте.
Оболочка bash предоставляет такую возможность, позволяя создавать функции. Функции bash — это именованные блоки кода, которые можно повторно использовать в скриптах.