backend-cheats/files/linux/bash-scripts-cheatsheet.md
2022-09-08 21:23:36 +03:00

813 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Шпаргалка по Bash скриптам
<div align="right"><a href="https://github.com/cheatsnake/backend-cheats#%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D1%8B-bash">Вернуться на главную страницу ⬆️</a></div>
**Содержание:**
- [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` <br>
> Так же не забывайте отделять условия пробелами внутри фигурных скобок -> `[ 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_ - с какого элемента начинать цикл; <br>
> _END_ - до какого элемента продолжать цикл; <br>
> _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` завершает текущую итерацию цикла и переходит к следующей. <br>
## Цикл 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 <КОМАНДА> <СИГНАЛ>
```
> Под сигналом можно использовать его название (колонка _Сигнал_ в таблице), либо его код (колонка _Код_ в таблице). Можно указывать несколько сигналов разделяя их названия или коды пробелом. <br>
> **Исключения:** сигналы 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
```
<div align="right"><a href="https://github.com/cheatsnake/backend-cheats/blob/master/files/linux/bash-scripts-cheatsheet.md#%D1%88%D0%BF%D0%B0%D1%80%D0%B3%D0%B0%D0%BB%D0%BA%D0%B0-%D0%BF%D0%BE-bash-%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%B0%D0%BC">Вернуться в начало ⬆️</a></div>