Написание скриптов на языке Bash #
Андрей Михайлович Васильев, 2022
Версии презентации
Исполняемые файлы в Linux #
Всего можно выделить 2 типа исполняемых файла:
- Бинарные приложения, предназначенные для работы на текущем процессоре
- Интерпретируемые приложения, включая скриптовые языки и интерпетируемые языки
Для запуска первых приложений достаточно только ОС, для работы вторых нужен установленный интерпретатор или соответствующая исполнительная машина
Типичный способ запуска таких приложений выглядит следующим образом:
# интерпретатор файл-приложения
python app.py
ruby app.rb
bash script.sh
Автоматический выбор интерпретатора #
Приложение, реализованное на языке Python, обычно знает какой интерпретатор ему нужен, а вот его пользователь может не знать
В UNIX-мире есть механизм, позволяющий указать интерпретатор для запуска файла
- Текстовому файлу должны быть выданы права на выполнение
- В начале текстового файла может быть указана информация, по которой загрузчик программ, автоматически выберет интерпретатор
Shebang #
Строка для выбора интерпретатора называется Shebang:
#!интерпретатор [аргументы]
В случае, если в начале файла указана данная строка, то при его запуске загрузчик постарается найти интерпретатор и запустит интерпретатор с указанным файлом
Варианты Shebang #
#!/bin/sh
— запуск интерпретатора Bourne Shell или совместимого с ним.#!/bin/bash
— запуск файла с помощью интерпретатора Bash.#!/usr/bin/python3
— запуск файла с помощью системного интерпретатора Python 3#!/usr/bin/env ruby
— запуск файла с помощью интерпретатора Ruby, поиск которого надо выполнять в PATH пользователя, вызывающего приложение#!/bin/false
— запретить выполнение файла через запуск, нужно например для файлов, предназначеных только для подключения к другим исполняемым скриптам
Путь к интерпретатору должен быть абсолютным, т.к. данный механизм по умолчанию не использует подсистему поиска по PATH. Если необходима функциональность по поиску интерпретатора, тогда следует использовать /usr/bin/env
Пример добавления информации к скрипту #
Рассмотрим простейшее приложение на языке Bash:
echo 'Hello, world!'
Запишем его в файл /home/user/script.sh
и дадим права на исполнение
$ cd /home/user
$ chmod +x script.sh
$ ./script.sh
Для запуска скрипта необходимо:
- либо указать полный путь к исполнямому файлу в качестве команды
- либо поместить файл в один из каталогов PATH
Выбор языка для написания скриптов #
Исполняемые скрипты для выполнения административных задач в Linux можно писать на множестве языков: Python, Ruby, Perl, Bash и так далее
При выборе языка программирования необходимо учитывать множество факторов:
- Сколько времени нужно будет поддерживать скрипт?
- Кто будет поддерживать скрипт?
- Насколько сложную задачу необходимо решать?
- Сложность задачи состоит в последовательности вызова программ или в обработке данных?
- Скрипт должен поддерживать работу на одной или нескольких платформах?
Использование Ruby или Python #
Если задача требует сложных вычислений и её необходимо поддерживать достаточно долго, то рекомендуется использовать более продвинутые языки программирования
Использование Ruby #
- Для настройки систем есть специализированные проекты Chef и Puppet
- Для выполнения действий над файлами можно использовать библиотеку FileUtils
- Для запуска внешних процессов есть мощная библиотека open3
- Для выполнения действий по сети есть библиотеки net-ssh и net-scp
Использование Python #
- Для настройки систем есть специализированные проекты Ansible и Salt
- Для выполнения действий над файлами можно использовать библиотеку shutil
- Для запуска внешних процессов есть библиотека Subprocess
- Для выполнения действий по SSH есть библиотека paramiko
Запуск внешних программ в Bash #
С базовым синтаксисом Bash мы уже знакомы
#!/bin/bash
touch file1
mkdir trash
mv file1 trash
rm -rf trash
mkdir trash
echo "Файл удален!"
Bash выполняет каждую строку как отдельную команду, которую вводили в терминале
Документация #
Наиболее полная книжка по Bash называется Advanced Bash Scripting Guide
Известные особенности обработки строк в Bash #
- Расширение путей:
*
,?
,[[:upper:]]
- Расширение скобок:
{abc, def}
- Арифметическое расширение:
$((5 + 10))
- Расширение переменных:
${VAR}
- Замена команд:
$(ls)
Все эти элементы можно и нужно использовать при написании скриптов
Описание переменных #
Для объявления переменной используется синтаксис:
name=[value]
Для получения доступа к значению переменной используйте $name
- Пробелы вокруг знака равенства недопустимы
- Если не указать значение, то переменная будет содержать нулевую строку
- Декларирование переменных может происходить в рамках команд
alias
,declare
,typeset
,export
,readonly
,local
- Для отмены значений переменной необходимо использовать
unset name
- В названии переменной можно использовать латинские символы, подчёркивания
- В качестве значения может выступать любое строковое выражение, к которому будут применены все расширения
Описание переменных. Пример #
#!/bin/bash
info='Some data goes here'
echo "Значение info: $info" # Просмотр переменной
files=$(ls) # Сохранение результата вызова ls
echo "$files"
$ ls
abc test.sh
$ ./test.sh
Some data goes here
abc
test.sh
Со знака #
начинаются комментарии
Циклы #
Для обхода набора значений удобно воспользоваться циклами
for index in 1 2 3 4 5 6 7 8 9 10; do
echo "Индекс: $index"
done
for ((index=0; index < 10; index++)) do
echo "Число: $index"
done
Обход вывода приложения #
Циклы можно использовать для обработки вывода из приложения
echo 'Файлы, начинающиеся с A'
for file in A*; do
echo "$file"
done
echo 'Проход по PID процессов'
for pid in $(ps -eo pid); do
echo "${pid}"
done
Условный оператор if
#
Благодаря наличию данного оператора можно говорить, что Bash является языком программирования. Важно понимать, что этот язык — узкоспециализированный, ориентированный на обработку строк и запуск внешних задач, поэтому разрабатывать на нём сложные приложения не стоит
Синтаксис:
if list; then list;
[ elif list; then list; ] ...
[ else list; ]
fi
Если последняя команда из списка в условии вернёт 0, тогда будут выполнены команды в then
-списке, в противном случае будет протестированы команды из elif
Возвращаемое значение #
Стандартное определение функции main
на языке Си:
int main(int argc, char* argv[], char** envp) {...}
int argc
— количество аргументовchar* argv[]
— массив с аргументамиchar** envp
— массив с переменными окружения в форматеКЛЮЧ=ЗНАЧЕНИЕ
int main
, метод возвращает целое значение, обозначающее результат выполнения- 0 — программа выполнила все действия корректно
- !=0 — во время работы программы возникли ошибки, некорректное завершение
Проверка возвращаемого значения в Bash #
В BASH переменная ?
позволяет узнать статус выполнения последней команды
$ ls
$ echo $?
0
$ ls /aoeb &> /dev/null
$ echo $?
2
Приложение test
#
Приложение позволяет проверить некоторые выражения и вернуть 1 или 0
test ВЫРАЖЕНИЕ
[ ВЫРАЖЕНИЕ ]
Написанное выражение подвергается обработке со стороны BASH как обычная строка, а после передаётся для выполнения в приложение test
Данное приложение предназначено для использования в if-выражении:
# Testing that file exists
if [ -f /tmp/data ]; then
echo "Файл /tmp/data существует!"
fi
Встроенная команда Bash [[
#
Данная команда является расширением приложения test
и исправляет ряд его ограничений. Рекомендуется к использованию вместо приложения test
- Внутри команды
[[ ]]
не происходит расширения строк - Можно использовать
&&
,||
внутри выражения - Легче группировать выражения с помощью
(...)
Команду [[
следует использовать для сравнения строк и проверки файлов
Комбинирование проверок в test
#
Несколько проверок можно объединить с помощью &&
, ||
на уровне Bash:
[ -f /tmp/data ] || { [ -f /tmp/info ] && [ -f /tmp/more ] }
Операторы &&
и ||
работают на уровне результатов работы приложения
Строковые проверки команд test
и [[
#
Строковые проверки отличаются
[[ |
test | Пример |
---|---|---|
> |
\> |
[[ a > b ]] ложь, a идёт раньше b |
< |
\< |
[[ az < za ]] правда, a идёт раньше z |
= , == |
= |
[[ a = a ]] правда, a равно a |
!= |
!= |
[[ a != b ]] правда, a не равно b |
= , == |
нет | [[ name = n* ]] правда, name начинается с n |
=~ |
нет | [[ home =~ ^h+ ]] правда, home соответствует выражению |
-z |
-z |
[[ -z $info ]] правда, если строка в $info нулевой длины |
-n |
-n |
[[ -n $data ]] правда, если строка в $data ненулевой длины |
Числовые проверки команд test
и [[
#
Числовые проверки между test
и [[
не отличаются
Проверка | Пример |
---|---|
-gt |
[[ 5 -gt 10 ]] ложь, 5 меньше 10 |
-lt |
[[ 8 -lt 9 ]] правда, 8 меньше 9 |
-eq |
[[ 5 -eq 3 ]] ложь, 5 не равно 3 |
-ne |
[[ 5 -ne 3 ]] правда, 5 не равно 3 |
-ge |
[[ 3 -ge 3 ]] правда, 3 больше или равно 3 |
-le |
[[ 3 -le 8]] правда, 3 меньше или равно 8 |
Файловые проверки команд test
и [[
#
Проверка | Пример |
---|---|
-a |
[[ -a /tmp/data ]] правда, если файл существует |
-d |
[[ -d /var/log ]] правда, если директория существует |
-e |
[[ -e /run/info.pid ]] правда, если файл существует |
-f |
[[ -f /tmp/test ]] правда, если существует и является файлом |
-r |
[[ -r ~/info ]] правда, если файл доступен на чтение |
-w |
[[ -w ~/result ]] правда, если файл доступен на запись |
-x |
[[ -x ~/bin/run ]] правда, если файл можно выполнять |
Циклы while
и until
#
Помимо итеративного for в Bash также есть бесконечные циклы while и utlit:
while list-1; do list-2; done
until list-1; do list-2; done
- Цикл
while
будет выполнять свои действия пока условия вlist-1
верны - Цикл
until
будет выполнять свои действия пока условия вlist-1
неверны
Выйти из цикла можно с помощью break [n]
Если передать число, тогда выход будет произведён из такого количества циклов
Для перехода к следующей итерации можно воспользоваться continue [n]
Если передать число, то продолжение будет на соответствующем уровне вложенности
Пример цикла while
#
#!/bin/bash
echo
while [ "$var1" != "end" ]
do
echo "Input variable #1 (end to exit) "
read var1 # Not 'read $var1' (why?).
echo "variable #1 = $var1" # Need quotes because of "#"
echo
done
exit 0
Типы переменных в Bash #
Bash поддерживает следующие типы переменных:
- Локальные переменные, которые видны внутри области видимости (файла, функции и т.д.)
- Переменные окружения, которые видны детским процессам
- Позиционные переменные
Переменные окружения #
Для декларирования переменной окружения используется команда export
:
export [name=[word]] ...
Позиционные переменные #
В переменные $0
, $1
, $2
записываются аргументы скрипта
$0
содержит название скрипта$1
содержит первый аргумент и т.д.- После 9 к аргументам необходимо обращаться
${10}
$*
и$@
позволяют обратиться сразу ко всем переменным$#
позволяет получить количество переданных аргументов
Базовое использование позиционных переменных #
MINPARAMS=10
if [ $# -lt "$MINPARAMS" ]; then
echo
echo "This script needs at least $MINPARAMS arguments!"
fi
if [ -n "$1" ] # Tested variable is quoted.
then
echo "Parameter #1 is $1" # Need quotes to escape #
fi
if [ -n "${10}" ] # Parameters > $9 must be enclosed
then
echo "Parameter #10 is ${10}"
fi
Специальная обработка аргументов #
Команда shift
производит “сдвиг” позиционных аргументов влево
$1 <--- $2, $2 <--- $3, $3 <--- $4, ...
Значение переменной $0 не изменяется
С помощью этой команды можно обойти все аргументы:
until [ -z "$1" ] # Until all parameters used up . . .
do
echo -n "$1 "
shift
done
shift может сдвигать сразу на несколько позиций
Проверка качества скриптов #
Язык Bash имеет множество отличий в семантике работы от привычных языков программирования, поэтому надо быть осторожным с переносом своих привычек на язык Bash
Для проверки Bash-кода на наличие проблем рекомендуется использовать статический анализатор кода ShellCheck
Для установки его в GNU/Debian необходимо от имени сперпользователя
apt install shellcheck
Пример использования ShellCheck #
#!/bin/bash
until [ -z $1 ] # Until all parameters used up . . .
do
echo -n "$1 "
shift
done
$ shellcheck test.sh
In test.sh line 3:
until [ -z $1 ] # Until all parameters used up . . .
^-- SC2086: Double quote to prevent globbing and word splitting.
Did you mean:
until [ -z "$1" ] # Until all parameters used up . . .
For more information:
https://www.shellcheck.net/wiki/SC2086 -- Double quote to prevent globbing ...