Bash скрипты (сценарии) – это наборы тех же самых команд, которые можно вводить с клавиатуры, но собранные в единый файл и объединённые некоей общей целью. Такой подход позволяет автоматизировать множество рутинных задач, например, сборку проектов или установку новых программ. Bash прост в освоении и использовании, гибок и так или иначе присутствует в абсолютном большинстве дистрибутивов Linux.
Скрипты Bash имеют расширение .sh
:
$ touch script.sh
Хорошей практикой считается указывать путь до вашего терминала вначале каждого скрипта:
#! /bin/bash
Этот прием называется shebang, подробнее можно почитать здесь.
Список доступных терминалов в вашей системе можно посмотреть с помощью этой команды:
$ cat /etc/shells
- Bash скрипты на примерах
- Содержание
- Hello world
- Комментарии
- Переменные
- Пользовательский ввод
- Передача аргументов
- Условия if else
- Операторы условий
- Логические операторы
- Арифметические операторы
- Конструкция switch case
- Массивы
- Цикл while
- Цикл until
- Цикл for
- Цикл select
- Break и continue в циклах
- Функции
- Локальные переменные
- Ключевое слово readonly
- Обработка сигналов
- Отладка скриптов
- Дополнительные материалы
#! /bin/bash
echo "Hello world"
Запуск скрипта:
$ bash script.sh
Скрипт можно сделать исполняемым файлом и запускать без команды bash
:
$ chmod +x script.sh
$ ./script.sh
Однострочные комментарии:
# Это просто коммент
# И это тоже
echo "Hello from bash" # эта команда выводит строку в консоль
Мультистрочные комментарии:
: 'Мультистрочные комментарии очень удобны
для подробного описания ваших скриптов.
Успользуйте их с умом!'
MY_STRING="bash is cool"
echo $MY_STRING # Вывод значения переменной
Имя переменной не должно начинаться с цифры
Команда read
читает пользовательский ввод и записывает его в указанную переменную:
echo "Введите ваше имя:"
read NAME
echo "Привет $NAME!"
Если переменная не указана, то команда
read
по умолчанию сохранит все данные в переменнуюREPLY
Можно записывать несколько переменных. Для этого, при вводе из терминала, значения необходимо разделять пробелом:
read V1 V2 V3
echo "1 переменная: $V1"
echo "2 переменная: $V2"
echo "3 переменная: $V3"
$ bash script.sh
$ hello world some other text
1 переменная: hello
2 переменная: world
3 переменная: some other text
Флаг -a
позволяет создать массив в который будут записываться строки пользовательского ввода разделенные пробелом:
read -a NAMES
echo "Массив имён: ${NAMES[0]}, ${NAMES[1]}, ${NAMES[2]}"
$ bash script.sh
Alex Mike John
Массив имён: Alex, Mike, John
Флаг -p
позволяет не переносить пользовательский ввод на следующую строку.
Флаг -s
позволяет скрыть вводимые символы (как это происходит при вводе пароля).
read -p "Введите ваш логин: " LOGIN
read -sp "Введите ваш пароль: " PASSWD
$ bash script.sh
Введите ваш логин: bash_hacker
Введите ваш пароль:
Аргументы это просто значения, которые могут быть указаны при запуске скрипта.
Всем переданным аргументам присваивается уникальное имя равное их порядковому номеру:
echo "Аргумент 1 - $1; aргумент 1 - $2; aргумент 1 - $3."
$ bash script.sh hello test 1337
Аргумент 1 - hello; aргумент 1 - test; aргумент 1 - 1337.
Нулевой аргумент всегда равен названию файла со скриптом:
echo "Вы запустили файл $0"
$ bash script.sh
Вы запустили файл script.sh
Все аргументы можно положить в именованный массив:
args=("$@")
echo "Полученные аргументы: ${args[0]}, ${args[1]}, ${args[2]}."
$ bash script.sh some values 123
Полученные аргументы: some, values, 123
@
- это название массива по умолчанию, который хранит все аргументы (за исключением нулевого)
Количество переданных аргументов (за исключением нулевого) хранится в переменной #
:
echo "Всего получено аргументов: $#"
Условия всегда начинаются с ключевого слова if
и заканчиваются на fi
:
echo "Введите ваш возраст:"
read AGE
if (($AGE >= 18))
then
echo "Доступ разрешен"
else
echo "Доступ запрещен"
fi
$ bash script.sh
Введите ваш возраст:
19
Доступ разрешен
$ bash script.sh
Введите ваш возраст:
16
Доступ запрещен
Условий может быть сколько угодно много, для этого используется конструкция elif
, которая также как if
может проверять условия:
read COMMAND
if [ $COMMAND = "help" ]
then
echo "Доступные команды:"
echo "ping - вернет строку PONG"
echo "version - вернет номер версии программы"
elif [ $COMMAND = "ping" ]
then
echo "PONG"
elif [ $COMMAND = "version" ]
then
echo "v1.0.0"
else
echo "Команда не определена. Воспользуйтесь командой 'help' для справки"
fi
Обратите внимание, что после конструкций
if
иelif
всегда следует строчка с ключевым словомthen
Так же не забывайте отделять условия пробелами внутри фигурных скобок ->[ condition ]
Для цифр и строк могут использоваться разные операторы сравнения. Полные их списки с примерами приведены в таблицах ниже.
Обратите внимания, что разные операторы используются с определенными скобками
Оператор | Описание | Пример |
---|---|---|
-eq | равняется ли | if [ $age -eq 18 ] |
-ne | не равняется | if [ $age -ne 18 ] |
-gt | больше чем | if [ $age -gt 18 ] |
-ge | больше чем или равно | if [ $age -ge 18 ] |
-lt | меньше чем | if [ $age -lt 18 ] |
-le | меньше чем или равно | if [ $age -le 18 ] |
> | больше чем | if (($age > 18)) |
< | меньше чем | if (($age < 18)) |
=> | больше чем или равно | if (($age => 18)) |
<= | меньше чем или равно | if (($age <= 18)) |
Оператор | Описание | Пример |
---|---|---|
= | проверка на равенство | if [ $str = "hello" ] |
== | проверка на равенство | if [ $str == "hello" ] |
!= | проверка на НЕ равенство | if [ $str != "hello" ] |
< | сравнение меньше чем по ASCII коду символов | if [[$str < "hello"]] |
> | сравнение больше чем по ASCII коду символов | if [[$str > "hello"]] |
-z | проверка пустая ли строка | if [ -z $str ] |
-n | проверка есть ли в строке хоть один символ | if [ -n $str ] |
Так же существуют операторы для проверки различных условий над файлами.
Оператор | Описание | Пример |
---|---|---|
-e | проверяет, существует ли файл | if [ -e $file ] |
-s | проверяет, пустой ли файл | if [ -s $file ] |
-f | проверяет, является ли файл обычным файлом, а не каталогом или специальным файлом | if [ -f $file ] |
-d | проверяет, является ли файл каталогом | if [ -d $file ] |
-r | проверяет, доступен ли файл для чтения | if [ -r $file ] |
-w | проверяет, доступен ли файл для записи | if [ -w $file ] |
-x | проверяет, является ли файл исполняемым | if [ -x $file ] |
Условия с оператором "И" возвращают истину только в том случае, когда все условия истины.
Существует несколько вариантов написания условий с логическими операторами
if [ $age -ge 18 ] && [ $age -le ]
if [ $age -ge 18 -a $age -le ]
if [[ $age -ge 18 && $age -le ]]
Условия с оператором "ИЛИ" возвращают истину в том случае, когда хотя бы одно условие истинно.
if [ -r $file ] || [ -w $file ]
if [ -r $file -o -w $file ]
if [[ -r $file || -w $file ]]
num1=10
num2=5
# Сложение
echo $((num1 + num2)) # 15
echo $(expr $num1 + $num2) # 15
# Вычитание
echo $((num1 - num2)) # 5
echo $(expr $num1 - $num2) # 5
# Умножение
echo $((num1 * num2)) # 50
echo $(expr $num1 \* $num2) # 50
# Деление
echo $((num1 / num2)) # 2
echo $(expr $num1 / $num2) # 2
# Остаток от деления
echo $((num1 % num2)) # 0
echo $(expr $num1 % $num2) # 0
Обратите внимание, что при использовании умножения с ключевым словом
expr
необходимо использовать косую черту.
Не всегда удобно использовать конструкции if/elif для большого количества условий. Для этого лучше подойдет конструкция case:
read COMMAND
case $COMMAND in
"/help" )
echo "Вы открыли справочное меню" ;;
"/ping" )
echo "PONG" ;;
"/version" )
echo "Текущая версия: 1.0.0" ;;
* )
echo "Такой команды нет :(" ;;
esac
Случай со звездочкой * отработает лишь в том случае, если не подойдет ни одно из условий выше.
Массивы позволяют хранить целую коллекцию данных в одной переменной. С этой переменной можно удобно и легко взаимодействовать:
array=('aaa' 'bbb' 'ccc' 'ddd')
echo "Элементы массива: ${array[@]}"
echo "Первый элемент массива: ${array[0]}"
echo "Индексы элементов массива: ${!array[@]}"
array_length=${#array[@]}
echo "Длинна массива: ${array_length}"
echo "Последний элемент массива: ${array[$((array_length - 1))]}"
$ bash script.sh
Элементы массива: aaa bbb ccc ddd
Первый элемент массива: aaa
Индексы элементов массива: 0 1 2 3
Длинна массива: 4
Последний элемент массива: ddd
Обратите внимание, что элементы массива разделяются пробелом без запятой.
Элементы массива можно добавлять/перезаписывать/удалять по ходу выполнения скрипта:
array=('a' 'b' 'c')
array[3]='d'
echo ${array[@]} # a b c d
array[0]='x'
echo ${array[@]} # x b c d
array[0]='x'
echo ${array[@]} # x b c d
unset array[2]
echo ${array[@]} # x b d
Цикл while повторяет выполнение блока кода описанного между ключевыми словами do
- done
пока истинно заданное условие.
i=0
while (( $i < 5 ))
do
i=$((i + 1))
echo "Итерация номер $i"
done
$ bash script.sh
Итерация номер 1
Итерация номер 2
Итерация номер 3
Итерация номер 4
Итерация номер 5
Операция увеличения числа на 1 единицу называется инкрементом и для неё существует специальная запись:
(( i++ )) # post increment
(( ++i )) # pre increment
Противоположная операция - декремент:
(( i-- )) # post decrement
(( --i )) # pre decrement
С помощью while циклов можно построчно читать различные файлы. Существует несколько способов сделать это:
echo "Чтение файла по строкам:"
while read line
do
echo $line
done < text.txt
echo "Чтение файла по строкам:"
cat text.txt | while read line
do
echo $line
done
echo "Чтение файла по строкам:"
while IFS='' read -r line
do
echo $line
done < text.txt
Цикл until противоположен циклу while тем, что он выполняет блок кода описанный между ключевыми словами do
- done
тогда, когда заданное условие возвращает false:
i=5
until (( $i == 0 )) # будет выполняться пока i не станет равным 0
do
echo "Значение переменной i = $i"
(( i-- ))
done
$ bash script.sh
Значение переменной i = 5
Значение переменной i = 4
Значение переменной i = 3
Значение переменной i = 2
Значение переменной i = 1
Самый классический цикл:
for (( i=1; i<=10; i++ ))
do
echo $i
done
В новых версиях Bash существует более удобный способ записи с помощью оператора in
:
for i in {1..10}
do
echo $i
done
Условие после ключевого слова in
в общем случае выглядит так:
{START..END..INCREMENT}
START - с какого элемента начинать цикл;
END - до какого элемента продолжать цикл;
INCREMENT - на сколько увеличивать элемент после каждой итерации (по умолчанию на 1).
Цикл for можно использовать для последовательного запуска набора команд:
for command in ls pwd date # Список команд для запуска
do
echo "---Запуск команды $command---"
$command
echo "------------------------"
done
$ bash script.sh
---Запуск команды ls---
script.sh text.txt
------------------------
---Запуск команды pwd---
/home/user/bash
------------------------
---Запуск команды date---
Сб 03 сен 2022 10:35:57 +03
------------------------
Крайне удобный цикл для создания меню выбора опций:
select color in "Красный" "Зеленый" "Синий" "Белый"
do
echo "Вы выбрали $color цвет..."
done
$ bash script.sh
1) Красный
2) Зеленый
3) Синий
4) Белый
#? 1
Вы выбрали Красный цвет...
#? 2
Вы выбрали Зеленый цвет...
#? 3
Вы выбрали Синий цвет...
#? 4
Вы выбрали Белый цвет...
Цикл select
очень хорошо сочетается с оператором выбора case
. Таким образом можно очень просто создавать интерактивные консольные приложения с большим количеством разветвлений:
echo "---Добро пожаловать в меню---"
select cmd in "Запуск" "Настройки" "О программе" "Выход"
do
case $cmd in
"Запуск")
echo "Программа запущена"
echo "Введите число:"
read input
echo "$input в квадрате = $(( input * input ))" ;;
"Настройки")
echo "Настройки программы" ;;
"О программе")
echo "Версия 1.0.0" ;;
"Выход")
echo "Выход из программы..."
break ;;
esac
done
Для принудительного выхода из цикла используется ключевое слово break
:
count=1
while (($count)) # всегда возвращает истину
do
if (($count > 10))
then
break # принудительный выход несмотря на условие после while
else
echo $count
((count++))
fi
done
Для того, чтобы пропустить выполнение текущей итерации в цикле и перейти к следующей - используется ключевое слово continue
:
for (( i=5; i>0; i-- ))
do
if ((i % 2 == 0))
then
continue
fi
echo $i
done
$ bash script.sh
5
3
1
Функции - это именованные участки кода, которые могут переиспользоваться неограниченное количество раз:
hello() {
echo "Hello World!"
}
# вызываем функцию 3 раза:
hello
hello
hello
$ bash script.sh
Hello World!
Hello World!
Hello World!
Функции, так же, как и сами скрипты, могут принимать аргументы. Они имеют такие же названия, но аргументы функций видны только внутри функции, в которую они были переданы:
echo "$1" # аргумент переданный при запуске скрипта
calc () {
echo "$1 + $2 = $(($1 + $2))"
}
# передача двух аргументов в функцию calc
calc 42 17
$ bash script.sh hello
hello
42 + 17 = 59
Если мы объявим какую-либо переменную, а затем объявим ещё одну с таким же именем, но уже внутри функции, то у нас произойдет перезапись:
VALUE="hello"
test() {
VALUE="linux"
}
test
echo $VALUE
$ bash script.sh
linux
Чтобы предотвратить такое поведение используются ключевое слово local
перед именем переменной, которая объявляется внутри функции:
VALUE="hello"
test() {
local VALUE="linux"
echo "Переменная внутри функции: $VALUE"
}
test
echo "Глобальная переменная: $VALUE"
$ bash script.sh
Переменная внутри функции: linux
Глобальная переменная: hello
По умолчанию, каждая созданная переменная в Bash в последующем может перезаписываться. Чтобы защитить переменную от изменений можно использовать ключевое слово readonly
:
readonly PI=3.14
PI=100
echo "PI = $PI"
$ bash script.sh
script.sh: строка 2: PI: переменная только для чтения
PI = 3.14
readonly
можно использовать не только в момент объявления переменной, но и после:
VALUE=123
VALUE=$(($VALUE * 1000))
readonly VALUE
VALUE=555
echo $VALUE
$ bash script.sh
script.sh: строка 4: VALUE: переменная только для чтения
123000
Тоже самое касается функций. Они так же могут быть переопределены, поэтому их можно защитить с помощью readonly
указав при этом флаг -f
:
test() {
echo "This is test function"
}
readonly -f test
test() {
echo "Hello World!"
}
test
$ bash script.sh
script.sh: строка 9: test: значение функции можно только считать
This is test function
Во время выполнения скриптов, могут происходить неожиданные действия. Например, пользователь может прервать выполнения скрипта с помощь комбинации Ctrl + C
, либо может случайно закрыть терминал или в самом скрипте может случится какая-либо ошибка и так далее...
В POSIX-системах существуют специальные сигналы - уведомления процесса о каком-либо событии. Их список определен в таблице ниже:
Сигнал | Код | Действие | Описание |
---|---|---|---|
SIGHUP | 1 | Завершение | Закрытие терминала |
SIGINT | 2 | Завершение | Сигнал прерывания (Ctrl-C) с терминала |
SIGQUIT | 3 | Завершение с дампом памяти | Сигнал «Quit» с терминала (Ctrl-) |
SIGILL | 4 | Завершение с дампом памяти | Недопустимая инструкция процессора |
SIGABRT | 6 | Завершение с дампом памяти | Сигнал, посылаемый функцией abort() |
SIGFPE | 8 | Завершение с дампом памяти | Ошибочная арифметическая операция |
SIGKILL | 9 | Завершение | Процесс уничтожен (kill signal) |
SIGSEGV | 11 | Завершение с дампом памяти | Нарушение при обращении в память |
SIGPIPE | 13 | Завершение | Запись в разорванное соединение (пайп, сокет) |
SIGALRM | 14 | Завершение | Сигнал истечения времени, заданного alarm() |
SIGTERM | 15 | Завершение | Сигнал завершения (сигнал по умолчанию для утилиты kill) |
SIGUSR1 | 30/10/16 | Завершение | Пользовательский сигнал № 1 |
SIGUSR2 | 31/12/17 | Завершение | Пользовательский сигнал № 2 |
SIGCHLD | 20/17/18 | Игнорируется | Дочерний процесс завершен или остановлен |
SIGCONT | 19/18/25 | Продолжить выполнение | Продолжить выполнение ранее остановленного процесса |
SIGSTOP | 17/19/23 | Остановка процесса | Остановка выполнения процесса |
SIGTSTP | 18/20/24 | Остановка процесса | Сигнал остановки с терминала (Ctrl-Z) |
SIGTTIN | 21/21/26 | Остановка процесса | Попытка чтения с терминала фоновым процессом |
SIGTTOU | 22/22/27 | Остановка процесса | Попытка записи на терминал фоновым процессом |
В Bash есть ключевое слово trap
с помощью которого можно отлавливать различные сигналы и предусматривать выполнение определенных команд:
trap <КОМАНДА> <СИГНАЛ>
Под сигналом можно использовать его название (колонка Сигнал в таблице), либо его код (колонка Код в таблице). Можно указывать несколько сигналов разделяя их названия или коды пробелом.
Исключения: сигналы SIGKILL (9) и SIGSTOP (17/19/23) отловить невозможно, поэтому нет смысла их указывать.
trap "echo Выполнение программы прервано...; exit" SIGINT
for i in {1..10}
do
sleep 1
echo $i
done
$ bash script.sh
1
2
3
4
^CВыполнение программы прервано...
Запуск скрипта с параметром -x
покажет его поэтапное выполнение, что будет полезно при отладке и поиске ошибок:
$ bash -x script.sh