Как повысить эффективность Bash-скриптов
Bash-скрипты — эффективное решение для автоматизации рутинных задач, но не всегда самое простое. Объемные сценарии характеризуются низкой производительностью и сложны для чтения. В этой статье мы рассмотрим, как оптимизировать работу, упростить с помощью утилит sed и awk и не совершать очевидных ошибок в написании скриптов.
Настройка выполнения скриптов
Управление процессами в Linux увеличивает коэффициент полезного использования ЦП, памяти, устройств ввода-вывода и других составляющих системы. Рассмотрим, как применить эти принципы и команды при запуске bash-скриптов в работу.
Скорость работы сценария зависит от количества операций и их «энергозатратности» для ресурсов системы. Проблема в том, что во время его выполнения системный администратор не может использовать консоль. Как решить эту задачу?
Использовать терминальные мультиплексоры: screen или более продвинутый tmux, о которых слышали даже «новички» в Unix-среде. Программы позволяют разделить терминал, создав несколько окон и поддерживая несколько сессий одновременно.
Перемещать процесс выполнения скрипта в фоновый режим. Если скрипт еще не запущен, то добавьте амперсанд & в конце команды
$ ./slurmscript &
Когда скрипт уже выполняется, приостановите его сочетанием клавиш Ctrl+Z и используйте команду $ bg для продолжения его работы, но уже в фоновом режиме. А чтобы вернуть скрипт на «передний план», введите после Ctrl+Z $ fg. Без указания дополнительных параметров опция будет применяться к текущему выполняемому заданию. Список всех задач оболочки отображается командой jobs:
$ jobs
[1] Stopped ./slurmscript
[2] Running ./slurmscript > slurmfile &
$ fg 1 #перевод в активный режим задачи №1 в списке
Несмотря на то, что сценарий работает в фоновом режиме, выходные данные выдаются на экран терминала. А выполнение скрипта остановится при отключении от сервера. Чтобы этого избежать:
Команда nohup нарушит прямую связь между выполняемым скриптом и терминалом. Сценарий не остановится в момент выхода из сессии, а вывод данных запишется в файл nohup.log.
$ nohup ./slurmscript &
Если скрипт не выводит никаких данных, файл останется пустым. Перенаправьте вывод в /dev/null, чтобы файл не был создан автоматически.
$ nohup ./slurmscript >/dev/null &
$ ./slurmscript & +
[1] 4545
$jobs
[1] + Running ./slurmscript &
[2] - Running ping slurm.io & >/dev/null
$disown -h
$jobs
[1] + Running ping slurm.io & >/dev/null
Disown -h можно использовать без дополнительных параметров, когда она применяется для текущего процесса. В других случаях, необходимо ввести номер строки в списке или pid (идентификатор) процесса. В результате выполняемый скрипт исчез из списка заданий оболочки и не получит сигнала о выходе из терминала. Чтобы убедиться, что процесс продолжается, используйте команды ps или top.
$ ./slurmscript > slurmfile &
Не менее полезные инструменты для управления скриптами — at, cron и anacron. Утилиты автоматизируют любые процессы, в том числе запуск сценариев:
# время дата
$at -f /home/slurm/slurm1 10:30 01092022
Ключ -f необходимо использовать для указания файла утилите, вместо конкретного процесса. Команда at распознает разнообразные форматы указания даты: $at 01:00 PM (час дня), $at now+10 minutes (через 10 минут), $at 09.00 AM next month (ровно через месяц в 10 утра), $at tomorrow (через 24 часа). Для завершения работы с установкой времени, нажмите Ctrl+D, просмотреть заданные параметры утилиты — $atq, а удалить — $atrm c указанием номера в списке.
Cron — позволяет многократно выполнять процессы по заданному расписанию. Установить дату и время для скриптов можно в конфигурационном файле crontab. Для работы с файлом используются три основные команды: $crontab -l (просмотреть все задачи cron), $crontab -r (удалить все записи) и $crontab -e (внести изменения в задачи для cron). Редактирование планировщика строится на заполнении данных пяти полей временного интервала и поля, указывающего полный путь к скрипту:
#мин чаcы день месяц день недели скрипт
* * * * * /home/slurm/slurm1
Допустимые значения для категории минут — от нуля до 59, часов — от 0 до 23, дней месяца — от 1 до 31, месяцев — от 1 до 12 и дней недели — от 0 до 6 или от 1 до 7 (в некоторых версиях воскресенье обозначается нулем, а в других — семеркой). Если ни одно поле не будет заполнено, то планировщик станет запускать скрипт каждую минуту, каждого часа и т.д.
0 8,20 * */2 1-5 /home/slurm/slurm1
Согласно настройкам скрипт slurm1 будет запускаться в фоновом режиме в восемь утра и восемь вечера (8, 20) ноль минут (0), независимо от числа (*), но только с пн по пт (1–5) и каждые два месяца (*/2).
Anacron — отличается от Cron тем, что изменения в конфигурационный файл может вносить только root, учитываются невыполненные задачи во время отключения компьютера, вместо точного времени можно задать только интервал. Для настройки периодичности заданий используются те же команды, что и для Cron.
Sed и awk
Инструменты обработки текста значительно расширяют возможности оболочки bash. Но с командами sed и awk можно не только редактировать вывод и файлы, включая сами скрипты. Утилиты служат наиболее эффективным решением некоторых задач автоматизации процессов.
Sed — потоковый редактор файлов, позволяющий сэкономить время на выполнении простых функций: удаление, замена, вставка текста. Как это работает? Утилите передается информация в виде набора условий. Она по очереди «читает» строки в файле или файлах и применяет заданные правила: sed »[область применения] [опция] / [шаблон для изменения] / [новый шаблон] / [w обновленный файл]» [исходный файл].
Например: заменить в третьей строке файла Slurm выражение «New course» на «Linux Mega» и сохранить изменения в копии исходного файла slurm1
$ sed '3s/new course/linux mega/w slurm1' slurm
Количество задаваемых параметров может варьироваться. Если правило не указано, то действуют настройки по умолчанию. Без указания исходного и нового файлов данные берутся и выводятся в терминал.
Область применения заданных правил можно обозначить конкретной строкой, как в примере, диапазоном строк (2,3 — вторая и третья; 4,$ — с четвертой строки и до конца файла) или как полный текст (ключ g после [новый шаблон] —
$ sed 's/new course/linux mega/g' slurm)
Если не конкретизировать область, то по умолчанию операция производится над первым соответствующим выражением в каждой строке.
Опции, которые могут пригодиться для создания скрипта: s — замена шаблона выражений, y — замена шаблона символов, d — удаление строк, i — вставка перед указанной строкой, а — вставка текста после указанной строки. Команда может содержать несколько правил внесения изменений, перечисленных через точку с запятой. Чтобы она сработала необходимо добавить ключ -e перед «областью применения»:
$sed -e '/^#/d; /^$/d' slurm1
Sed читает первую строку файла slurm1 в поисках совпадений с одним или всеми заданными в слешах (//) шаблонами. Набор символов »^#» обозначает строки, начинающиеся (^) со знака #, то есть комментарии. А символы »^$» — пустые строки, в которых нет никаких знаков от начала строки (^) до ее окончания ($). Если первая строка подходит под заданные параметры, sed анализирует, входит ли строка в область применения и выполняет необходимое действие, то есть удаляет ее, а затем обращается к следующей строке.
Эту команду можно ввести разными способами: перечислив шаблоны через запятую перед «d» или отделяя каждую опцию переходом на новую строку вместо »;». Но наиболее часто используемый вариант написания в скриптах:
$sed -e '/^#/d' -e '/^$/d' slurm1
Команда может содержать несколько шаблонов, операций или длинную строку регулярных выражений. Потому повторное введение ключа -e упрощает чтение и понимание файла сценария администратором.
Awk — язык программирования, синтаксис которого напоминает языки C и Perl. Хотя awk работает по тому же «построчному» принципу, но значительно превосходит sed по функциональным возможностям. При написании bash-скриптов инструмент удобно использовать для работы со структурированными данными, так как awk воспринимает поля (область текста, отделенную пробелами или табуляцией), переменные, арифметические функции и др.
Данные по умолчанию принимаются утилитой и выводятся после применения новых условий в терминал. Но можно обозначить в команде необходимый файл для обработки, когда это требуется.
Если выстроить параметры по аналогии с примером для утилиты sed, то команда будет выглядеть так: $awk [опция] »{[функция] [шаблон для изменения]}» [исходный файл]. Подобная схема получилась условной, так как в [опциях] и [функциях] могут использоваться дополнительные ключи, переменные, циклы, операторы обработки текста, например, сравнения и совпадения. Ниже отрывок из скрипта для выявления пользователей, расходующих большой объем дискового пространства:
for name in $(cut -d: -f1,3 /etc/passwd |
awk -F: '$2 > 99 {print $1}')
do
/bin/echo -en "User $name exceeds disk quota.\n"
done
Где -F: — опция для разделения текста (двоеточие — пример знака, по которому строки делятся на поля), $2 и $1 — обозначение столбца данных, print — функция для вывода измененных данных, а /etc/password — имя исходного файла. То есть утилита просматривает данные второго столбца в файле, уже отредактированном командой cut, сравнивает значения с числом 99 и выводит данные первого столбца, то есть имена пользователей из файла /etc/passwd.
Бесполезный для сценариев, но более ясный пример с использованием нескольких команд, перечисленных через точку с запятой:
$ echo "Moscow is the capital of GB" | awk '{$1="London"; print $0}'
London is the capital of GB
Сначала утилита заменяет текст первого поля на значение в кавычках «London», а затем выводит всю строку ($0) измененного комментария.
Awk использует множество опций. Помимо самой распространенной -F: могут потребоваться -v var=value — задать переменную, -o — вывести обработанный текст в файл, -f — указать файл сценария для выполнения.
Процесс работы awk делиться на 3 этапа: до, обработка текста и после нее. Используя опцию BEGIN, можно задать переменные или вставить текст до отображения данных:
$ awk 'BEGIN {FIELDWIDTHS="1 3 5"} {print $1, $2, $3}' newfile
Прежде, чем вывести числовые значения из файла, утилита структурирует их согласно заданной опции FIELDWIDTHS по количеству знаков. Тогда в первом столбце будет показана 1 цифра из строки, через пробел еще 3 цифры и потом 5 оставшихся. А с помощью опции END получится вывести результат выполненного действия:
$ awk 'END { print "Course consists of", NR, "modules"}' file.txt
Например, в основном процессе обработки были внесены изменения количества строк в файле, а после ее завершения необходимо вывести это количество (опция NR) в удобочитаемом варианте.
Кроме собственных опций и ключей утилиты sed и awk поддерживают использование регулярных выражений. Так можно задать команде awk вывод строк, содержащих цифры, специальным набором символов [[: digit:]].
awk '/[[:digit:]]/{print $0}'
И более сложный пример для утилиты sed — замена первых пяти вхождений конструкций «цифра-цифра-точка» из каждой строки файла:
sed -e 's/\([[:digit:]][[:digit:]]\.\)\{5\}//g' \
-e 's/^/ /'
Такое нагромождение знаков вызвано необходимостью выделить (/\ или ) метасимволы ({, [, ^ и т.д.), чтобы оболочка верно их прочитала.
Bash-скрипт Unrm
На профессиональных форумах часто ведутся споры о выборе правильного инструмента для решения задачи. Некоторые специалисты считают, что bash-скрипты должны ограничиваться 10–15 строками, другие — одним циклом или одной функцией. Тогда как, третьи пишут сложные многоуровневые скрипты. Вот один из примеров реально работающих сценариев для ознакомления, часть действий которого проще и эффективнее организовать функциями утилит sed и awk.
Bash-скрипт для восстановления резервных копий.
#!/bin/bash
# Unrm — находит в архиве резервных копий удаленных файлов тот, который запрашивается пользователем. В случае наличия нескольких резервных копий одного файла, выводит их списком с сортировкой по времени.
archivedir="$HOME/.deleted-files"
realrm="$(which rm)"
move="$(which mv)"
dest=$(pwd)
if [ ! -d $archivedir ] ; then
echo "$0: No deleted files directory: nothing to unrm" >&2
exit 1
fi
cd $archivedir
if [ $# -eq 0 ] ; then
echo "Contents of your deleted files archive (sorted by date):"
# sed используется с регулярным выражением и удаляет заданные шаблоны символов из вывода каждой строки команды ls. Такая информация не имеет ценности и загромождает вывод списка
ls -FC | sed -e 's/\([[:digit:]][[:digit:]]\.\)\{5\}//g' \
-e 's/^/ /'
exit 0
fi
matches="$(ls -d *"$1" 2> /dev/null | wc -l)"
if [ $matches -eq 0 ] ; then
echo "No match for \"$1\" in the deleted file archive." >&2
exit 1
fi
if [ $matches -gt 1 ] ; then
echo "More than one file or directory match in the archive:"
index=1
for name in $(ls -td *"$1")
do
datetime="$(echo $name | cut -c1-14| \
# awk заменяет префикс имени файла на дату удаления исходного файла
awk -F. '{ print $5"/"$4" at "$3":"$2":"$1 }')"
filename="$(echo $name | cut -c16-)"
if [ -d $name ] ; then
filecount="$(ls $name | wc -l | sed 's/[^[:digit:]]//g')"
echo " $index) $filename (contents = ${filecount} items," \
" deleted = $datetime)"
else
size="$(ls -sdk1 $name | awk '{print $1}')"
echo " $index) $filename (size = ${size}Kb, deleted = $datetime)"
fi
index=$(( $index + 1))
done
echo ""
/bin/echo -n "Which version of $1 should I restore ('0' to quit)? [1] : "
read desired
if [ ! -z "$(echo $desired | sed 's/[[:digit:]]//g')" ] ; then
echo "$0: Restore canceled by user: invalid input." >&2
exit 1
fi
if [ ${desired:=1} -ge $index ] ; then
echo "$0: Restore canceled by user: index value too big." >&2
exit 1
fi
if [ $desired -lt 1 ] ; then
echo "$0: Restore canceled by user." >&2
exit 1
fi
# sed служит для извлечения из stdout ключами -n и p строки, в которой указана искомая копия файла
restore="$(ls -td1 *"$1" | sed -n "${desired}p")"
if [ -e "$dest/$1" ] ; then
echo "\"$1\" already exists in this directory. Cannot overwrite." >&2
exit 1
fi
/bin/echo -n "Restoring file \"$1\" ..."
$move "$restore" "$dest/$1"
echo "done."
/bin/echo -n "Delete the additional copies of this file? [y] "
read answer
if [ ${answer:=y} = "y" ] ; then
$realrm -rf *"$1"
echo "Deleted."
else
echo "Additional copies retained."
fi
else
if [ -e "$dest/$1" ] ; then
echo "\"$1\" already exists in this directory. Cannot overwrite." >&2
exit 1
fi
restore="$(ls -d *"$1")"
/bin/echo -n "Restoring file \"$1\" ... "
$move "$restore" "$dest/$1"
echo "Done."
fi
exit 0
Bash-скрипты: что такое хорошо и что такое плохо
Главный совет при создании bash-скриптов — подумать прежде, чем писать. Чтобы сценарий выполнялся корректно, был удобен для дальнейшего использования или масштабирования, необходимо определиться с его структурой и логикой построения до начала работы.
Как надо:
научитесь пользоваться константами при вводе одинаковых значений или утилитой sed для удобства внесения изменений в скрипт в будущем;
дробите сложные сценарии на небольшие части и используйте функции;
задавайте рациональные имена переменным и функциям;
вносите комментарии, чтобы смысл команды не забылся, а сценарий мог обслуживать не только автор;
определитесь с регистром, типами скобок, сделайте стиль единообразным;
не используйте ресурсозатратные операции внутри циклов, например, find;
утилиты могут эмулировать функции встроенной команды bash или другой утилиты, то есть выводить один и тот же результат. В этом случае отдайте предпочтение встроенным командам или команде, содержащей меньше процессов.
И как не надо:
"var-3=19" или "time=19"
команда, для запуска которой пользователю не хватает прав доступа, не будет работать и в запущенном им сценарии;
перенос файла со сценарием, сохраненного в Windows, требует решения проблемы деления строк. В MS-DOS строка завершается двумя символами:
#!/bin/bash\r\n
#!/bin/bash\n
Удалить лишнюю -r можно с помощью редактора vim или утилит dos2unix и unix2dos.
дополнительные пробелы, свойственные языку С foo = bar
символы && и || не эмулируют команду if… then… else… fi в большинстве случаев;
искать опечатки в переменных через команду set -u (лучше использовать sell check).
Краткий обзор полезных функций для совершенствования сценариев — завершен.
Для тех, кто до конца не разобрался или хочет узнать больше полезных советов
28 июля 2022 стартует практический курс Southbridge + Слёрм «Администрирование Linux. Mega». Опыт автора программы инженера Southbridge Платона Платонова не ограничивается написанием bash-скриптов, а подкрепляется ни единым кейсом «best of the best» practice.
Вас ждут: 5 недель, 9 блоков, 12 часов теории, 48 часов практики на стендах, 80 lvl владения OC по окончанию. Углубьте свои знания работы с Linux за месяц.
Смотреть программу и занять местечко в потоке 28 июля: https://slurm.club/3yEGiAf