Урок 27. Программирование в Bash

 

Bash, наряду и с Shell, является языком программирования, а точнее языком сценариев, с помощью которого создаются небольшие программы, называемые скриптами.

Какие программы или скрипты можно создавать с помощью Bash?

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

Если пользователь может выполнить поставленную задачу, введя команды вручную, зачем тогда использовать скрипты?

С помощью них можно значительно облегчить работу и потратить на ее выполнение гораздо меньше времени. Кроме того, скрипты позволяют автоматизировать и ускорить многие действия.

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

 

Пример №1. Обновление системы

Регулярно нам приходится обновлять систему, например раз в неделю. Для этого необходимо вводить 2 команды:

На  Debian:

apt update (apt-get update)

apt upgrade (apt-get upgrade)

На Red Hat:

yum update (dnf update)

yum upgrade (dnf upgrade)

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

Как же это сделать?

Для начала создадим текстовый файл и назовем его system_updade и добавим в него вышеупомянутые команды:

Скрипт для обновления системы

В начале каждого скрипта всегда должна присутствовать запись #!/bin/bash. Она является указанием какой интерпретатор запускать при выполнении файла. В конце скрипта указываем exit. Затем сохраняем файл. 

Нужно ли указывать расширение для файла?

Необязательно. Можно оставить его без расширения либо добавить расширение .sh, что позволит визуально определить, что данный файл является скриптом.

Теперь необходимо системе указать, что файл исполняемый. Для этого меняем соответствующий атрибут файла командой chmod u+x system_update

Как теперь запустить такой файл?

Существуют 2 способа.

1-й способ

Запустите файл командой sh

sh system_update

 

2-й способ

Запустите файл с помощью ./

./system_update

Файл может находится где угодно и запускать его можно откуда угодно.

Неужели придется все время помнить где находится файл и вводить его длинное название?

Конечно же нет. У нас имеются псевдонимы и мы можем назначить файлу любой удобный псевдоним, например up:

alias up=’./system_update’

Теперь достаточно просто ввести команду up, чтобы запустить программу. Наш первый скрипт готов.

Однако можно его немного улучшить. Команда apt (apt-get) выдает слишком много информации, попробуем ее полностью убрать. Для этого воспользуемся командой перенаправления потока > либо >> и перенаправим вывод в /dev/null - это так называемая “черная дыра” в Linux, которая принимает все, но ничего не сохраняет:

Исправленный скрипт для обновления системы

Теперь скрипт не будет выводить никакой информации.

Однако создается некий дискомфорт, так как из-за отсутствия вывода мы не знаем как сработала программа. Поэтому в процессе выполнения скрипта будем выводить краткую информацию с помощью команды echo. Это стандартная команда, которая выводит в терминал заданное выражение. Выводимое сообщение всегда помещайте в двойные кавычки:

В скрипт добавлена интерактивность

Пустая команда echo создает пустую строку в консоли, то есть просто переводит курсор на одну строку ниже. Вот как теперь выглядит результат выполнения скрипта:

Вывод результата работы скрипта

 

Пример №2. Удаление старых файлов 

При работе в интернете мы часто скачиваем файлы. Обычно они сразу попадают в каталог /Загрузки или /Downloads. Некоторые файлы остаются там без использования в течении нескольких дней или недель и даже месяцев. Можно вручную удалить файлы, однако, если файлов много, то это займет некоторое время. А можно этот процесс максимально упростить и одной командой удалить все файлы за раз.

Как это сделать?

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

Для поиска старых файлов воспользуемся командой:

find  /home/student/Загрузки/ -mtime +5

Данная команда ищет файлы и каталоги, которые “не трогали” в течении последних 5 дней. Можно немного изменить команду и заставить ее искать только файлы, а не каталоги. Для этого добавим опцию type:

find  /home/student/Загрузки/  -type f  -mtime +5

Данная команда только отобразит файлы. Чтобы удалить их можно воспользоваться командой:

find  /home/student/Загрузки/  -type f  -mtime +5 -exec rm -f {} \;

И теперь создадим наш скрипт:

Скрипт по удалению старых файлов

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

Попробуем сделать сделать следующее:

    1. Сначала отобразим весь список файлов. 
    2. Затем программа запросит действия пользователя, то есть спросит: “Удалить файлы или нет?”. 
    3. В зависимости от ответа пользователя программа удалит файлы или оставит их нетронутыми.

 Реализуем первое действие:

Отображение списка старых файлов в скрипте

Здесь все понятно. Теперь реализуем второе действие. Здесь мы воспользуемся командой read для получения ответа от пользователя, введенного с клавиатуры:

Чтение команды пользователя

Пользователю необходимо ввести требуемый запрос (Да/Нет) и нажать Enter. Введенное значение сохраняется в переменной command, которую мы будем использовать дальше в программе.

Для реализации 3-го действия нам необходимо проанализировать введенную с клавиатуры фразу и выполнить соответствующее действие. Для этого воспользуемся конструкцией проверки условия:

if условие

then действие_при_выполнении_условия

else действие_если_усовие_не_выполняется

fi

 Вот как выглядит данная конструкция:

В скрипт добавлено условие if-else

Мы сравниваем значение переменной command с символами “Да” и если условие выполняется, то есть пользователь ввел “Да”, то файлы удаляются. В противном случае ничего не делать.

В конструкции if-else условие помещается в двойные квадратные скобки, хотя можно обойтись и одинарными квадратными скобками или вообще без них. Но лучше взять за привычку использовать двойные квадратные скобки.

После операторов then и else я использовал отступы. Они не обязательны, но я специально их сделал, чтобы легче было читать код.

Что означает выражение $command”?

Знак $ перед переменной указывает интерпретатору, что нужно работать со значением переменной, а не с самой переменной. При этом переменную со знаком $ всегда необходимо заключать в двойные кавычки.

То есть, если command = “Нет”, то условие “$command” = “Да” аналогично  условию “Нет” = “Да”. Здесь мы сравниваем значение переменной.

Однако условие command = “Да” аналогично “command” = “Да”. Поэтому при манипуляции со значением переменной всегда используйте знак $.

При работе с переменными избегайте использование пробелов:

command = “Да” - неправильно из-за пробелов до и после знака “=”

command=”Да”  - правильно, так как нет никаких пробелов.

Это касается случаев, когда вы присваиваете переменной какое-либо значение. В условиях if-else необходимо соблюдать интервал, то есть используйте пробел.

Вот как работает наш скрипт:

Демонстрация работы скрипта по удалению файлов

Вроде все работает. Попробуем снова запустить скрипт:

Повторное выполнение скрипта

Выглядит  не очень, так как старых файлов нет, а программа ведет себя так, как будто они есть.

Поэтому введем проверку на наличие старых файлов. Если они присутствуют, то выполним описанные шаги, если нет, то выведем сообщение, что файлов нет.

Для этого нам снова понадобится конструкция if-else и команда find. Мы запустим команду find и если она вернет пустой результат, то прекратим выполнение программы. Если вернет список файлов, то выполним операцию по удалению:

Проверка наличия файлов для удаления

Для упрощения результат выполнения команды find я присвоил переменной result.

Запомните, что когда хотите присвоить результат выполнения любой команды переменной, то необходимо заключить ее в конструкцию $(команда).

Теперь проверим содержит ли наша переменная какое-нибудь значение или нет. Заметьте, что знаки >, < используются с пробелами. null в условии означает пустоту (не 0).

Чтобы вывести весь список найденных файлов мы воспользовались командой echo “$result”. Заметьте, что мы использовали знак $, потому что нас интересует значение переменной.

Если выполнить echo result, то команда просто отобразит слово “result”, то есть она воспримет ее как обычный текст, а не переменную.

Кроме того, мы воспользовались двойными кавычками. Можно, конечно, обойтись и без них в данной команде, однако весь список файлов будет выведен в одну строку. Если воспользуемся кавычками, то список файлов будет выведен в столбик. В любом случае обе команды будут правильными:

echo $result - вывод в строку

echo “$result” - вывод в столбик

В скрипте были использованы 2 конструкции if-else, причем одна конструкция вложена в другую. Если выяснится, что в системе имеются старые файлы, то все действия будут выполняться во вложенной конструкции if-else до тех пор, пока файлы не будут удалены либо мы отклоним запрос удаления.

Попробуем его выполнить:

Теперь скрипт работает правильно

Программа работает правильно.

Зачем мы использовали переменную result? Ведь можно обойтись и без нее?

полный код скрипта

Да, можно и без нее, но тогда придется использовать длинную команду find еще в 2-х местах. Оба варианта правильные и каждый использует тот вариант, который удобен.

Хочу обратить ваше внимание на команду rm -f $result.

В качестве аргумента, то есть пути к файлу, я указал переменную. Таким образом я воспользовался подстановкой переменной в качестве аргумента команды. Однако здесь есть один нюанс.  При подстановке переменную всегда заключают в двойные кавычки, то есть должно было быть так rm -f “$result”. И это правильно. Но так как переменная result может содержать в себе сразу несколько путей к файлам, то вышеуказанная запись вызовет ошибку в интерпретаторе.

Чтобы исключить эту ошибку и заставить команду rm выполнить свое действие мы опускаем кавычки.

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

Скрипт работает, однако есть одно НО.

При вводе ответа с клавиатуры программа сравнивает наш ответ со словом “Да”. Но что если мы ввели “да” вместо “Да”? Или что если русская раскладка клавиатуры не установлена? К тому же переключение с английской раскладки на русскую не очень удобное дело.

Поэтому нам необходимо учитывать и этот вариант. Мы же создаем скрипт для упрощения наших рутинных задач.

Модифицируем условие знаком ||, которое означает ИЛИ из булевы алгебры.

Напишем условие и поясним на примере:

Добавлено новое условие

Допустим мы ввели “y”. 

Сначала программа сравнивает переменную с “Да”, если условие не выполняется, то идем дальше и сравниваем с “да”. Если и здесь неудача, то сравниваем с “Y”. Если условие ложно, то переходим к последнему условию и оно оказывается истинным, то есть выполняется. Следовательно работа программы переходит в следующий блок  для удаления файлов.

 

Пример №3. Избирательное обновление программ

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

Представим, что все программы установлены из репозитариев. Тогда процесс обновления аналогичен установке, то есть достаточно выполнить такую команду:

apt install название_программы

или

dnf install название_программы

Возникает вопрос, если все так легко, то зачем нам создавать скрипт?

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

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

Для примера возьмем 3 программы: GIMP - фоторедактор, похожий на Photoshop, VLC - медиа плеер, Firefox - интернет браузер. Вот как будет выглядеть работа скрипта:

Демонстрация работы скрипта по обновлению программ

После запуска программа спросит какую программу обновить. Нам достаточно выбрать номер из списка и нажать Enter.

Реализовать такой скрипт несложно - нам уже знакомы команда пользовательского ввода и конструкции сравнения:

Код скрипта по обновлению программ

Однако при множественном выборе и сравнении конструкция if-else неудобна и можно легко запутаться. Поэтому в таких ситуациях лучше применять конструкцию case - esac, которая намного удобнее.

Вот как будет выглядеть вышеописанная конструкция if-else, но уже с помощью оператора case:

Условия if-else заменены на switch-case

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

В общем виде конструкция case - esac выглядит так:

case “$переменная” in

возможное_значение_переменной ) список команд

;;

возможное_значение_переменной ) список команд

;;

esac

Такой скрипт можно запускать раз в неделю, ни о чем не заботясь.