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

Функции

Часто используемые, повторяющиеся блоки имеет смысл выделять в отдельные функции, чтобы при необходимости их запускать, передавая параметры.

Определение функции выглядит следующим образом:

<имя_функции>() {
  <команды>
  return <число>
}

function <имя_функции>() {
  <команды>
  return <число>
}

Первый вариант ближе к синтаксису языка С и считается более переносимым, во втором варианте круглые скобки можно не указывать. Также может отсутствовать оператор return, если функция не возвращает значения.

Самый простой пример скрипта, содержащего объявление и вызов функции будет выглядеть так:

#!/bin/bash

f() {
  echo Test
}

f

Мы объявили функцию f, которая выводит слово Test, и затем вызвали её:

test@osboxes:~$ ./script6.sh
Test

Так же, как и скрипт, функция может принимать параметры и использовать их, ссылаясь по номеру ($1, $2, …, $N). Вызов функции с параметрами в скрипте осуществляется так:

<имя функции> <параметр1> <параметр2>… <параметрN>

Функция может возвращать результат своего выполнения (код завершения) в виде числового значения в диапазоне от 0 до 255. Принято считать, что если функция возвращает 0, то она выполнилась успешно, во всех остальных случаях значение содержит код ошибки. Чтобы получить код завершения функции в скрипте, необходимо обратиться к переменной $?. Добавив параметры и возвращаемое значение, получим следующий скрипт:

#!/bin/bash

summ() {
  re='^[0-9]+$'
  if ! [[ $1 =~ $re ]] ; then
    return 1
  elif ! [[ $2 =~ $re ]] ; then
    return 2
  else
    s=$(($1 + $2))
    return 0
  fi
}

summ $1 $2

case $? in
 0) echo "The sum is: $s" ;;
 1) echo "var1 is not a nubmer" ;;
 2) echo "var2 is not a nubmer" ;;
 *) echo "Unknown error" ;;
esac

Здесь мы создали функцию summ, которая принимает 2 параметра и с помощью регулярного выражения ^[0-9]+$ проверяет, является ли каждый из переданных параметров числом. В случае, если первый параметр не число, то код завершения функции будет 1, если второй параметр не число, то код завершения функции будет 2. Во всех остальных случаях функция вычисляет сумму переданных параметров, сохраняя результат в глобальной переменной s.

Скрипт вызывает функцию, передавая ей на вход параметры, которые были переданы ему самому при вызове. Далее проверяется код завершения функции и выдается соответствующая ошибка, если код не равен 0, иначе выдается сумма, сохраненная в переменной s. Протестируем скрипт:

test@osboxes.org:~$ ./script7.sh abc 123
var1 is not a nubmer
test@osboxes.org:~$ ./script7.sh 234 def
var2 is not a nubmer
test@osboxes.org:~$ ./script7.sh 10 15
The sum is: 25

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

Для того, чтобы объявить переменную локальной, используется слово local, например local loc_var=123. Важно отметить, все что переменные, объявляемые в теле функции, считаются необъявленными до тех пор, пока эта функция не будет вызвана.

Объединим все воедино, создав на основе рассмотренных ранее структур следующий скрипт:

#!/bin/bash

clearFiles() {
  rm *.dat
  if [ $? -eq 0 ]
  then
    echo Files deleted
  fi
}

genFiles() {
  for (( i=1; i<=$1; i++ ))
  do
    head -c ${i}M </dev/urandom >myfile${i}mb.dat
  done
  ls -l *.dat
}

delFiles() {
for f in *.dat
  do
    size=$(( $(stat -c %s $f) /1024/1024 ))
    if [ $size -gt $1 ]
    then
      rm $f
      echo Deleted file $f
    fi
  done
  ls -l *.dat
}

showWeather() {
  curl -s "https://weather-broker-cdn.api.bbci.co.uk/en/observation/rss/$1" | grep "<desc" | sed -r 's/<description>//g; s/<\/description>//g'
}

menu() {
  clear
  echo 1 - Delete all .dat files
  echo 2 - Generate .dat files
  echo 3 - Delete big .dat files
  echo 4 - List all files
  echo 5 - Planet info
  echo 6 - Show weather
  echo "x/q - Exit"
  echo -n "Choose action: "
  read -n 1 key
  echo
}

while true
do
  case "$key" in
    "x" | "q" | "X" | "Q") break ;;
    "1")
      clearFiles
      read -n 1
    ;;
    "2")
      echo -n "File count: "
      read count
      genFiles $count
      read -n 1
    ;;
    "3")
      echo -n "Delete file greater than (mb): "
      read maxsize
      delFiles $maxsize
      read -n 1
    ;;
    "4")
      ls -la
      read -n 1
    ;;
    "5")
      ./script4.sh
      read -n 1
    ;;
    "6")
      echo -n "Enter city code: " # 524901 498817 5391959
      read citycode
      showWeather $citycode
      read -n 1
    ;;
  esac
  menu
done

В данном скрипте мы объявили 5 функций:

  • clearFiles

  • genFiles

  • delFiles

  • showWeather

  • menu

Далее реализован бесконечный цикл с помощью оператора while с условием true, в который вложен оператор выбора в зависимости от нажатой клавиши, а также вызов функции menu для отображения списка доступных действий. Данный скрипт в интерактивном режиме позволяет выполнить следующие действия:

  • Удалить все файлы .dat в текущей директории

  • Создать указанное количество файлов

  • Удалить файлы больше определенного размера

  • Вывести список всех файлов текущей директории

  • Запустить скрипт, выдающий информацию о планетах

  • Отобразить погоду по коду указанного города

Не будем подробно разбирать все строки кода, скажем только, что в скрипте демонстрируется вызов другого скрипта, получение информации из интернет, и её парсинг (выделение нужной информации), команда break для выхода из цикла и ряд других возможностей. Предлагаю желающим самостоятельно протестировать скрипт, посмотреть, какие могут быть ошибки при его работе и при вводе различных значений, какие проверки можно добавить, и что можно улучшить. Приведем результаты тестирования:

test@osboxes.org:~$ ./script8.sh
1 - Delete all .dat files
2 - Generate .dat files
3 - Delete big .dat files
4 - List all files
5 - Planet info
6 - Show weather
x/q - Exit
Choose action: 4
total 40
drwxr-xr-x 2 test test 4096 Feb 16 15:56 .
drwxr-xr-x 6 root root 4096 Feb 16 15:54 ..
-rw------- 1 test test   42 Feb 16 15:55 .bash_history
-rw-r--r-- 1 test test  220 Feb 16 15:54 .bash_logout
-rw-r--r-- 1 test test 3771 Feb 16 15:54 .bashrc
-rw-r--r-- 1 test test  807 Feb 16 15:54 .profile
-rw-r--r-- 1 test test 1654 Feb 16 12:40 input.xml
-rwxr-xr-x 1 test test  281 Feb 16 14:02 script4.sh
-rwxr-xr-x 1 test test  328 Feb 16 13:40 script7.sh
-rwxr-xr-x 1 test test 1410 Feb 16 15:24 script8.sh
1 - Delete all .dat files
2 - Generate .dat files
3 - Delete big .dat files
4 - List all files
5 - Planet info
6 - Show weather
x/q - Exit
Choose action: 2
File count: 8
-rw-rw-r-- 1 test test 1048576 Feb 16 16:00 myfile1mb.dat
-rw-rw-r-- 1 test test 2097152 Feb 16 16:00 myfile2mb.dat
-rw-rw-r-- 1 test test 3145728 Feb 16 16:00 myfile3mb.dat
-rw-rw-r-- 1 test test 4194304 Feb 16 16:00 myfile4mb.dat
-rw-rw-r-- 1 test test 5242880 Feb 16 16:00 myfile5mb.dat
-rw-rw-r-- 1 test test 6291456 Feb 16 16:00 myfile6mb.dat
-rw-rw-r-- 1 test test 7340032 Feb 16 16:00 myfile7mb.dat
-rw-rw-r-- 1 test test 8388608 Feb 16 16:00 myfile8mb.dat
1 - Delete all .dat files
2 - Generate .dat files
3 - Delete big .dat files
4 - List all files
5 - Planet info
6 - Show weather
x/q - Exit
Choose action: 3
Delete file greater than (mb): 5
Deleted file myfile6mb.dat
Deleted file myfile7mb.dat
Deleted file myfile8mb.dat
-rw-rw-r-- 1 test test 1048576 Feb 16 16:00 myfile1mb.dat
-rw-rw-r-- 1 test test 2097152 Feb 16 16:00 myfile2mb.dat
-rw-rw-r-- 1 test test 3145728 Feb 16 16:00 myfile3mb.dat
-rw-rw-r-- 1 test test 4194304 Feb 16 16:00 myfile4mb.dat
-rw-rw-r-- 1 test test 5242880 Feb 16 16:00 myfile5mb.dat
1 - Delete all .dat files
2 - Generate .dat files
3 - Delete big .dat files
4 - List all files
5 - Planet info
6 - Show weather
x/q - Exit
Choose action: 1
Files deleted
1 - Delete all .dat files
2 - Generate .dat files
3 - Delete big .dat files
4 - List all files
5 - Planet info
6 - Show weather
x/q - Exit
Choose action: 5
Enter the name of planet: Mars
The Mars has two satellite(s).
1 - Delete all .dat files
2 - Generate .dat files
3 - Delete big .dat files
4 - List all files
5 - Planet info
6 - Show weather
x/q - Exit
Choose action: 6
Enter city code: 524901
    Latest observations for Moscow from BBC Weather, including weather, temperature and wind information
      Temperature: -11°C (11°F), Wind Direction: Northerly, Wind Speed: 0mph, Humidity: 84%, Pressure: 1018mb, , Visibility: Moderate

Примечание: для тестирования работы с данными из Интернет (пункт 6 в меню выбора скрипта) может потребоваться установка curl, это можно сделать командой sudo apt install curl.

Планировщик заданий cron

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

Просмотр заданий пользователя выполняется командой crontab –l. Для редактирования и создания новых задания используется команда crontab –e. Строки для запуска команд планировщика в файле конфигурации cron имеют следующий формат:

m h dom mon dow command parameters

Где m – минута, h – час, dom – день месяца, mon – месяц, dow – день недели, command – команда, parameters – список параметров. Наглядно этот формат можно представить так:

Например, для того, чтобы в 10 и 30 минут каждого часа каждый день месяца весь год по будням запускать команду, нужно указать следующее:

10,30 * * * 1-5 command parameter1 parameter2

Более простой пример, каждые 15 минут выполнять команду:

*/15 * * * * command

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

#!/bin/bash
USER=`whoami`
BACKUP_DIR=/tmp/backup_${USER}
BACKUP_FILE=${USER}_$(date +%Y%m%d%M%H%S).tgz

mkdir -p $BACKUP_DIR
cd /
tar -zcf $BACKUP_DIR/$BACKUP_FILE home/$USER

Поставим скрипт на выполнение каждый день в 22:00, выполнив команду crontab -eи добавив с помощью открывшегося редактора строку:

00 22 * * * ./backup_home.sh

Проверить, что задача добавлена в планировщик, можно командой crontab -l:

test@osboxes.org:~$ crontab -l
00 22 * * * ./backup_home.sh

В результате каждый день в 22:00 будет создаваться резервная копия домашней директории пользователя (в приведенном примере для демонстрации запуск скрипта выполняется каждую минуту):

test@osboxes.org:~$ cd /tmp/backup_test/
test@osboxes:/tmp/backup_test$ ll
total 80
drwxrwxr-x  2 test test 4096 Feb 16 16:38 ./
drwxrwxrwt 17 root root 4096 Feb 16 16:30 ../
-rw-rw-r--  1 test test 4431 Feb 16 16:30 test_20210216301601.tgz
-rw-rw-r--  1 test test 4431 Feb 16 16:31 test_20210216311601.tgz
-rw-rw-r--  1 test test 4431 Feb 16 16:32 test_20210216321601.tgz
-rw-rw-r--  1 test test 4431 Feb 16 16:33 test_20210216331601.tgz
-rw-rw-r--  1 test test 4431 Feb 16 16:34 test_20210216341601.tgz
test@osboxes:/tmp/backup_test$

Нужно отметить, что директория /tmp в примере использована исключительно для тестов, т.к. она предназначена для хранения временных файлов, и использовать её для хранения резервных копий нельзя. Правильное место размещения бэкапов может подсказать системный администратор.

Список полезных команд

Список встроенных команд интерпретатора: help
Помощь по команде: <команда> --help
Мануал по команде: man <команда>
Версия команды: <команда> --version
Список доступных оболочек: cat /etc/shells
Список пользователей и их оболочек: cat /etc/passwd
Текущая директория: pwd
Список файлов текущей директории: ls -la
Текущий пользователь: id
Переменные среды: set
Версия ОС: cat /etc/os-release
Версия ядра: uname -a
Получить привилегии суперпользователя: sudo su -
Установка программы в Debian: apt install mc
Посмотреть утилизацию(загрузку): top
Свободное место: df -h
Сколько занимает директория: du -ks /var/log
Конфигурация сетевых интерфейсов: ifconfig -a
Объем оперативной памяти: free -m
Информация о блочных устройствах(дисках): lsblk
Информация о процессорах: cat /proc/cpuinfo
Список установленных пакетов: apt list --installed
Список и статус сервисов: service --status-all
Перезапуск сервиса: service apache2 restart
Скачать файл: wget https://www.gnu.org/graphics/gplv3-with-text-136x68.png
Получить веб-страницу по URL: curl https://www.google.com
Показать задания планировщика: crontab -l
Редактировать задания планировщика: crontab -e
Вывести новые сообщения в системном логе: tail -f /var/log/syslog
Подсчитать количество строк в выводе команды: <команда> | wc -l
Изменить права доступа к файлу (разрешить выполнение всем): chmod a+x <файл>
Список процессов: ps -ef
Проверить, запущен ли процесс: ps -ef | grep <процесс>
Перейти в предыдущий каталог: cd -
Завершить процесс (сигнал kill): kill -9
Удаление файла: rm <имя файла>
Удаление директории: rm -rf <имя директории>
Редактировать файл: nano <имя_файла>
Топ 10 процессов по использованию памяти: ps aux | awk '{print $6/1024 " MB\t\t" $11}' | sort -nr | head

Полезные ссылки

Руководство по bash: GNU Bash manual
Расширенное руководство по Bash: Advanced Bash-Scripting Guide
Статья на Википедии: Bash
Описание команд и утилит оболочки bash: SS64
Часто задаваемые вопросы о Debian GNU/Linux: Debian FAQ

Заключение

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

На этом пока все, надеюсь, было интересно!
Какие другие полезные команды вы знаете и используете в работе?
Какие интересные конструкции приходилось использовать?
Какие задачи решали?
Всем удачного скриптинга, делитесь мнениями в комментариях!