# Шпаргалка по Bash скриптам
Вернуться на главную страницу ⬆️
**Содержание:** - [Hello world](#hello-world) - [Комментарии](#комментарии) - [Переменные](#переменные) - [Пользовательский ввод](#пользовательский-ввод) - [Передача аргументов](#передача-аргументов) - [Условия if else](#условия-if-else) - [Операторы условий](#операторы-условий) - [Логические операторы](#логические-операторы) - [Арифметические операторы](#арифметические-операторы) - [Конструкция switch case](#конструкция-switch-case) - [Массивы](#массивы) - [Цикл while](#цикл-while) - [Цикл until](#цикл-until) - [Цикл for](#цикл-for) - [Цикл select](#цикл-select) - [Break и continue в циклах](#break-и-continue-в-циклах) - [Функции](#функции) - [Локальные переменные](#локальные-переменные) - [Ключевое слово readonly](#ключевое-слово-readonly) - [Обработка сигналов](#обработка-сигналов) Скрипты Bash имеют расширение `.sh`: ``` $ touch script.sh ``` Хорошей практикой считается указывать путь до вашего терминала вначале каждого скрипта: ```sh #! /bin/bash ``` > Этот прием называется **shebang**, подробнее можно почитать [тут](https://ru.wikipedia.org/wiki/%D0%A8%D0%B5%D0%B1%D0%B0%D0%BD%D0%B3_(Unix)) Список доступных терминалов в вашей системе можно посмотреть с помощью этой команды: ``` $ cat /etc/shells ``` ## Hello world ```sh #! /bin/bash echo "Hello world" ``` Запуск скрипта: ``` $ bash script.sh ``` Скрипт можно сделать исполняемым файлом и запускать без команды `bash`: ``` $ chmod +x script.sh ``` ``` $ ./script.sh ``` ## Комментарии Однострочные комментарии: ```sh # Это просто коммент # И это тоже echo "Hello from bash" # эта команда выводит строку в консоль ``` Мультистрочные комментарии: ```sh : 'Мультистрочные комментарии очень удобны для подробного описания ваших скриптов. Успользуйте их с умом!' ``` ## Переменные ```sh MY_STRING="bash is cool" echo $MY_STRING # Вывод значения переменной ``` > Имя переменной не должно начинаться с цифры ## Пользовательский ввод Команда `read` читает пользовательский ввод и записывает его в указанную переменную: ```sh echo "Введите ваше имя:" read NAME echo "Привет $NAME!" ``` > Если переменная не указана, то команда `read` по умолчанию сохранит все данные в переменную `REPLY` Можно записывать несколько переменных. Для этого, при вводе из терминала, значения необходимо разделять пробелом: ```sh 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` позволяет создать массив в который будут записываться строки пользовательского ввода разделенные пробелом: ```sh read -a NAMES echo "Массив имён: ${NAMES[0]}, ${NAMES[1]}, ${NAMES[2]}" ``` ``` $ bash script.sh Alex Mike John Массив имён: Alex, Mike, John ``` Флаг `-p` позволяет не переносить пользовательский ввод на следующую строку. Флаг `-s` позволяет скрыть вводимые символы (как это происходит при вводе пароля). ```sh read -p "Введите ваш логин: " LOGIN read -sp "Введите ваш пароль: " PASSWD ``` ``` $ bash script.sh Введите ваш логин: bash_hacker Введите ваш пароль: ``` ## Передача аргументов Аргументы это просто значения, которые могут быть указаны при запуске скрипта. Всем переданным аргументам присваивается уникальное имя равное их порядковому номеру: ```sh echo "Аргумент 1 - $1; aргумент 1 - $2; aргумент 1 - $3." ``` ``` $ bash script.sh hello test 1337 Аргумент 1 - hello; aргумент 1 - test; aргумент 1 - 1337. ``` Нулевой аргумент всегда равен названию файла со скриптом: ```sh echo "Вы запустили файл $0" ``` ``` $ bash script.sh Вы запустили файл script.sh ``` Все аргументы можно положить в именованный массив: ```sh args=("$@") echo "Полученные аргументы: ${args[0]}, ${args[1]}, ${args[2]}." ``` ``` $ bash script.sh some values 123 Полученные аргументы: some, values, 123 ``` > `@` - это название массива по умолчанию, который хранит все аргументы (за исключением нулевого) Количество переданных аргументов (за исключением нулевого) хранится в переменной `#`: ```sh echo "Всего получено аргументов: $#" ``` ## Условия if else Условия всегда начинаются с ключевого слова `if` и заканчиваются на `fi`: ```sh echo "Введите ваш возраст:" read AGE if (($AGE >= 18)) then echo "Доступ разрешен" else echo "Доступ запрещен" fi ``` ``` $ bash script.sh Введите ваш возраст: 19 Доступ разрешен $ bash script.sh Введите ваш возраст: 16 Доступ запрещен ``` Условий может быть сколько угодно много, для этого используется конструкция `elif`, которая также как `if` может проверять условия: ```sh 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 ] | ## Логические операторы Условия с оператором "И" возвращают истину только в том случае, когда все условия истины. > Существует несколько вариантов написания условий с логическими операторами ```sh if [ $age -ge 18 ] && [ $age -le ] ``` ```sh if [ $age -ge 18 -a $age -le ] ``` ```sh if [[ $age -ge 18 && $age -le ]] ``` Условия с оператором "ИЛИ" возвращают истину в том случае, когда хотя бы одно условие истинно. ```sh if [ -r $file ] || [ -w $file ] ``` ```sh if [ -r $file -o -w $file ] ``` ```sh if [[ -r $file || -w $file ]] ``` ## Арифметические операторы ```bash 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` необходимо использовать косую черту. ## Конструкция switch case Не всегда удобно использовать конструкции if/elif для большого количества условий. Для этого лучше подойдет конструкция case: ```sh read COMMAND case $COMMAND in "/help" ) echo "Вы открыли справочное меню" ;; "/ping" ) echo "PONG" ;; "/version" ) echo "Текущая версия: 1.0.0" ;; * ) echo "Такой команды нет :(" ;; esac ``` > Случай со звездочкой * отработает лишь в том случае, если не подойдет ни одно из условий выше. ## Массивы Массивы позволяют хранить целую коллекцию данных в одной переменной. С этой переменной можно удобно и легко взаимодействовать: ```sh 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 ``` > Обратите внимание, что элементы массива разделются пробелом без запятой. Элементы массива можно добавлять/перезаписывать/удалять по ходу выполнения скрипта: ```sh 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 Цикл while повторяет выполение блока кода описанного между ключевыми словами `do` - `done` пока истино заданное условие. ```sh i=0 while (( $i < 5 )) do i=$((i + 1)) echo "Итерация номер $i" done ``` ``` $ bash script.sh Итерация номер 1 Итерация номер 2 Итерация номер 3 Итерация номер 4 Итерация номер 5 ``` Операция увеличения числа на 1 единицу называется инкриментом и для неё существует специальная запись: ```sh (( i++ )) # post increment ``` ```sh (( ++i )) # pre increment ``` Противоположная операция - декремент: ```sh (( i-- )) # post decrement ``` ```sh (( --i )) # pre decrement ``` С помощью while циклов можно построчно читать различные файлы. Существует несколько способов сделать это: ```sh echo "Чтение файла по строкам:" while read line do echo $line done < text.txt ``` ```sh echo "Чтение файла по строкам:" cat text.txt | while read line do echo $line done ``` ```sh echo "Чтение файла по строкам:" while IFS='' read -r line do echo $line done < text.txt ``` ## Цикл until Цикл until противоположен циклу while тем, что он выполняет блок кода описанный между ключевыми словами `do` - `done` тогда, когда заданное условие возвращает false: ```sh 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 Самый классический цикл: ```sh for (( i=1; i<=10; i++ )) do echo $i done ``` В новых версиях Bash существует более удобный способ записи с помощью оператора `in`: ```sh for i in {1..10} do echo $i done ``` Условие после ключевого слова `in` в общем случае выгядит так: ``` {START..END..INCREMENT} ``` > _START_ - с какого элемента начинать цикл;
> _END_ - до какого элемента продолжать цикл;
> _INCREMENT_ - на сколько увеличивать элемент после каждой итерации (по умолчанию на 1). Цикл for можно использовать для последовательного запуска набора команд: ```sh 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 ------------------------ ``` Ключевое слово `break` останавливает выполнение цикла. Ключевое слово `continue` завершает текущую итерацию цикла и переходит к следующей.
## Цикл select Крайне удобный цикл для создания меню выбора опций: ```sh select color in "Красный" "Зеленый" "Синий" "Белый" do echo "Вы выбрали $color цвет..." done ``` ``` $ bash script.sh 1) Красный 2) Зеленый 3) Синий 4) Белый #? 1 Вы выбрали Красный цвет... #? 2 Вы выбрали Зеленый цвет... #? 3 Вы выбрали Синий цвет... #? 4 Вы выбрали Белый цвет... ``` Цикл `select` очень хорошо сочетается с оператором выбора `case`. Таким образом можно очень просто создавать интерактивные консольные приложения с большим количеством разветвлений: ```sh 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 и continue в циклах Для принудительного выхода из цикла используется ключевое слово `break`: ```sh count=1 while (($count)) # всегда возвращает истину do if (($count > 10)) then break # принудительный выход несмотря на условие после while else echo $count ((count++)) fi done ``` Для того, чтобы пропустить выполнение текущей итерации в цикле и перейти к следующей - используется ключевое слово `continue`: ```sh for (( i=5; i>0; i-- )) do if ((i % 2 == 0)) then continue fi echo $i done ``` ``` $ bash script.sh 5 3 1 ``` ## Функции Функции - это именованные участки кода, которые могут переиспользоваться неограниченное количество раз: ```sh hello() { echo "Hello World!" } # вызываем функцию 3 раза: hello hello hello ``` ``` $ bash script.sh Hello World! Hello World! Hello World! ``` Функции, так же, как и сами скрипты, могут принимать аргументы. Они имеют такие же названия, но аргументы функций видны только внутри функции, в которую они были переданы: ```sh echo "$1" # аргумент переданный при запуске скрипта calc () { echo "$1 + $2 = $(($1 + $2))" } # передача двух аргументов в функцию calc calc 42 17 ``` ``` $ bash script.sh hello hello 42 + 17 = 59 ``` ## Локальные переменные Если мы объявим какую-либо переменную, а затем объявим ещё одну с таким же именем, но уже внутри функции, то у нас произойдет перезапись: ```sh VALUE="hello" test() { VALUE="linux" } test echo $VALUE ``` ``` $ bash script.sh linux ``` Чтобы предотвратить такое поведение используются ключевое слово `local` перед именем переменной, которая объявляется внутри функции: ```sh VALUE="hello" test() { local VALUE="linux" echo "Переменная внутри функции: $VALUE" } test echo "Глобальная переменная: $VALUE" ``` ``` $ bash script.sh Переменная внутри функции: linux Глобальная переменная: hello ``` ## Ключевое слово readonly По умолчанию, каждая созданная переменная в Bash в последующем может перезаписываться. Чтобы защитить переменную от изменений можно использовать ключевое слово `readonly`: ```sh readonly PI=3.14 PI=100 echo "PI = $PI" ``` ``` $ bash script.sh script.sh: строка 2: PI: переменная только для чтения PI = 3.14 ``` `readonly` можно использовать не только в момент объявления переменной, но и после: ```sh VALUE=123 VALUE=$(($VALUE * 1000)) readonly VALUE VALUE=555 echo $VALUE ``` ``` $ bash script.sh script.sh: строка 4: VALUE: переменная только для чтения 123000 ``` Тоже самое касается функций. Они так же могут быть переопределены, поэтому их можно защитить с помощью `readonly` указав при этом флаг `-f`: ```sh 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) отловить невозможно, поэтому нет смысла их указывать. ```sh 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 ```
Вернуться в начало ⬆️