This commit is contained in:
pavel
2025-05-27 13:25:12 +05:00
commit 4abcf76ee2
125 changed files with 239469 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

BIN
images/JFC.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

BIN
images/common/BigO.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
images/common/BigO_eng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
images/common/array.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/common/array_eng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/common/binary.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
images/common/graph.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
images/common/graph_eng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
images/common/heap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
images/common/heap_eng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
images/common/hex.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/common/logic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
images/common/logic_eng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
images/common/octal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images/common/queue.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

BIN
images/common/stack.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
images/common/stack_eng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
images/common/tree.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@@ -0,0 +1,67 @@
# Шпаргалка по MongoDB
<div align="right"><a href="https://github.com/cheatsnake/backend-cheats#mongodb">Вернуться на главную страницу ⬆️</a></div>
- Подготовка БД
```js
show dbs // показать список всех БД
use db_name // подключиться/создать БД с именем db_name
db // вывести имя текущей базы данных
db.createCollection("users") // создать коллекцию "notes"
show collections // показать список коллекций в текущей БД
db.dropDatabase() // удалить текущую БД
```
- Добавление элементов
```js
// Добавить один элемент
db.users.insertOne({
name: "Alex",
age: 27,
isMarried: false,
city: "NewYork"
})
// Добавить несколько элементов
db.users.insertMany([{...}, {...}])
```
- Получение элементов
```js
// Получить все элементы из коллекции
db.users.find();
// Получить элементы по указанному критерию
db.user.find({ age: 27 });
// Получить один элемент
db.users.findOne({ name: "Alex" });
// Получить отсортированный список элементов
// 1 - по возрастанию; -1 - по убыванию
db.users.find().sort({ age: 1 });
// Получить количество элементов
db.users.find().count();
// Лимит количества получаемых элементов
db.users.find().limit(10);
// Выборка с помощью операторов сравнения
db.users.find({ age: { $gt: 20 } }); // > 20
db.users.find({ age: { $gte: 20 } }); // >= 20
db.users.find({ age: { $lt: 50 } }); // < 50
db.users.find({ age: { $lte: 50 } }); // <= 50
db.users.find({ age: { $ne: 35 } }); // != 35
```
- Изменение элементов
```js
// Полное изменение элемента (первый аргумент - критерий поиска)
db.users.updateOne({name: "Alex"}, {новые_данные})
// Изменение определенных полей элемента
db.users.updateOne({name: "Alex"}, {$set: {age: 28, isMarried: true}})
// Переименовать поле у нескольких элементов
db.users.updateMany({name: "Alex"}, {&rename: {city: "town"}})
// Удаление элемента/элементов
db.users.deleteOne({name: "Alex"})
db.users.deleteMany({name: "Alex"})
```

View File

@@ -0,0 +1,146 @@
# Шпаргалка по SQL
<div align="right"><a href="https://github.com/cheatsnake/backend-cheats#%D1%80%D0%B5%D0%BB%D1%8F%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%B0%D1%8F-%D0%B1%D0%B0%D0%B7%D0%B0-%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85">Вернуться на главную страницу ⬆️</a></div>
- Создание новой БД
```sql
CREATE DATABASE db_name;
```
- Создание новой таблицы
```sql
CREATE TABLE users (
id SERIAL PRIMARY KEY, # Уникальный id
firstName VARCHAR(100), # Строка
lastName VARCHAR(100), # Строка
age INT, # Число
gender VARCHAR(10), # Строка
isMarried BOOLEAN # true/false
);
```
- Основные типы данных
> - INT (целые числа от -2^32 до +2^32)
> - FLOAT / DOUBLE / DECIMAL (дробные числа)
> - CHAR / VARCHAR / TEXT (строки)
> - DATA / DATETIME / TIME (дата и время)
> - ENUM (перечисления - списки допустимых значений)
> - [И другие](https://sql-language.ru/osnova-sql/tipy-dannykh-sql.html)
- Добавление данных в таблицу
```sql
INSERT INTO users(
firstName, lastName, age, gender, isMarried
) VALUES (
'Alex', 'Manson' 25, 'male', false
);
```
- Выборка данных из таблицы
```sql
# SELECT
## Получить всю таблицу users
SELECT * FROM users;
## Получить только столбцы firstName и age из таблицы users
SELECT firstName, age FROM users;
# LIMIT
## Получить первых 20 записей таблицы users
SELECT * FROM users LIMIT 20;
# DISTINCT
## Получить только уникальные значения из столбца firstName
SELECT DISTINCT(firstName) FROM users;
# WHERE
## Записи, где столбец gender = 'male'
SELECT * FROM users WHERE gender = 'male';
## AND, OR
SELECT * FROM users WHERE age = 25 AND isMarried = falsel
SELECT * FROM users WHERE age = 20 OR age = 50;
# BETWEEN
## Записи, где значения столбца age находятся в промежутке от 20 до 30
SELECT * FROM users WHERE age BETWEEN 20 AND 30;
#NULL
## Записи, где столбец lastName не пуст
SELECT * FROM users WHERE lastName IS NOT NULL;
```
- Поиск данных по шаблону
```sql
# IN, LIKE, NOT LIKE
## % - подстановочный знак, который указывает на любое кол-во символов
## _ - подстановочный знак, который указывает на один символ
## Записи, где firsName равен 'John', 'Mike' или 'Kane'
SELECT * FROM users WHERE firstName IN ('John', 'Mike', 'Kane');
## Записи, где firsName начинается c буквы 'A'
SELECT * FROM users WHERE firstName LIKE 'A%';
## Записи, где первая буква в firstName равна 'A', 'B' или 'C'
SELECT * FROM users WHERE firstName LIKE '[ABC]%';
## Записи, где вторая буква в firsName не равна 'o'
SELECT * FROM users WHERE firstName NOT LIKE '_o%';
```
- Сортировка и фильтрация данных таблиц
```sql
# ORDER BY
## ASC - по возрастанию (по умолчанию)
## DESC - по убыванию
SELECT * FROM users ORDER BY firstName ASC;
SELECT * FROM users ORDER BY age DESC;
SELECT * FROM users ORDER BY lastName DESC, isMarried ASC;
# HAVING
## Фильтрация результатов группировки
```
- Использование псевдонимов
```sql
# AS
SELECT firstName AS name FROM users WHERE name = "Alex";
```
- Изменение таблиц
```sql
# ALTER TABLE
## Добавить новую колонку city к таблицe users
ALTER TABLE users ADD COLUMN city VARCHAR(50);
## Удалить колонку isMarried из таблицы users
ALTER TABLE users DROP COLUMN isMarried;
## Переименовать колонку firstName в fName в таблицe users
ALTER TABLE users RENAME COLUMN firstName TO fName;
## Переименовать таблицу users в consumers
ALTER TABLE users RENAME TO consumers;
```
- Изменение данных в таблице
```sql
# UPDATE
## Изменить в таблицe users записать с id = 1
UPDATE users SET firstName = 'Kale', age = 33 WHERE id = 1;
## Изменить записи, где gender = 'female'
UPDATE users SET city = 'Paris' WHERE gender = 'famale';
```
- Удаление данных из таблицы
```sql
# DELETE
# Удалить запись в таблице users, где id = 2
DELETE FROM users WHERE id = 2;
# Удалить все записи в таблице users, где gender = 'male'
DELETE FROM users WHERE gender = 'male';
```
- [Агрегатные функции](https://codetown.ru/sql/agregatnye-funkcii/)
> Используются для обобщения/подсчёта данных.
```sql
# COUNT
## Возвращает количество элементов в таблице users
SELECT COUNT(*) FROM users;
## Возвращает количество не повторяющихся значений столбца firstName
SELECT COUNT(DISTINCT(firstName)) FROM users;
# MAX, MIN
SELECT MAX(age) FROM users;
SELECT MIN(age) FROM users;
# SUM
# Сумма всех значений столбца age
SELECT SUM(age) FROM users;
# AVG
## Среднее значение столбца age
SELECT AVG(age) FROM users;
```

BIN
images/exception.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

View File

@@ -0,0 +1,813 @@
# Шпаргалка по 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>

BIN
images/linux/chmod.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

BIN
images/linux/chmod_eng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

BIN
images/linux/cron.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

BIN
images/linux/cron_eng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

BIN
images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

BIN
images/optimization/cdn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
images/os/os-layer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
images/os/os-layer_eng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
images/os/process.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
images/os/process_eng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1,357 @@
# Шпаргалка по регулярным выражениям
<div align="right"><a href="https://github.com/cheatsnake/backend-cheats#%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D1%8B-%D1%8F%D0%B7%D1%8B%D0%BA%D0%B0">Вернуться на главную страницу ⬆️</a></div>
**Содержание:**
- [Базовое применение](#базовое-применение)
- [Флаги](#флаги)
- [Основной синтаксис](#основной-синтаксис)
- [Любой символ .](#любой-символ-)
- [Перечень []](#перечень-)
- [Исключающий перечень [^]](#исключающий-перечень-)
- [Диапазон [-]](#диапазон--)
- [Повторения *](#повторения-)
- [Повторения +](#повторения--1)
- [Необязательный символ ?](#необязательный-символ-)
- [Количество повторений {}](#количество-повторений-)
- [Диапазон повторений {,}](#диапазон-повторений-)
- [Группировка ()](#группировка-)
- [Логическое ИЛИ |](#логическое-или-)
- [Экранирование](#экранирование-)
- [Поиск в начале строки ^](#поиск-в-начале-строки-)
- [Поиск в конце строки $](#поиск-в-конце-строки-)
- [Классы символов](#классы-символов)
- [Позиционные проверки](#позиционные-проверки)
[Регулярные выражения](https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D0%B5_%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F) – это мощный инструмент для поиска и замены слов в тексте. Само регулярное выражение представляет из себя обычную строку составленную по определенным правилами. В общем виде она представляет из себя две косые черты `/ /`, где после первой черты идёт специальный паттерн для поиска, а после второй – набор флагов.
Для тренировки использования регулярных выражений можно воспользоваться сайтом [Regex101.com](https://regex101.com/).
Также можно воспользоваться возможностями для работы с регулярными выражениями в одном из языков программирования. Вот примеры для [Python](https://pythonru.com/primery/primery-primeneniya-regulyarnyh-vyrazheniy-v-python), [JavaScript](https://developer.mozilla.org/ru/docs/Web/JavaScript/Guide/Regular_Expressions), [Go](https://tproger.ru/articles/puteshestvie-v-golang-regexp/), [Kotlin](https://java-blog.ru/kotlin/vvedenie-v-regulyarnye-vyrazheniya-kotlin), [C#](https://metanit.com/sharp/tutorial/7.4.php) и [так далее](https://www.google.com/search?q=%D0%BA%D0%B0%D0%BA+%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C+%D1%80%D0%B5%D0%B3%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D0%B5+%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F+%D0%B2+%5B%D0%B2%D0%B0%D1%88+%D1%8F%D0%B7%D1%8B%D0%BA+%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%5D%5D)
## Базовое применение
Возьмем для примера любой текст. Представим, что в этом тексте нам нужно найти все слова `London`. Это самый простой случай использования регулярных выражения, нам всего лишь необходимо вписать нужное слово между косыми чертами:
<p align="center"><img src="https://i.ibb.co/cCsd7ss/Pasted-image-20221016151349.png" alt="Regex example"/></p>
## Флаги
Флаги влияют на результат поиска. Их всего 5 штук:
- `i` – позволяет игнорировать регистры букв (нет разницы между *A* и *a*).
- `g` – позволяет искать все совпадения в тексте, без него – только первое.
- `m` – включение многострочного режима (влияет только на поведение `^` и `$`).
- `s` – текст трактуется как одна строка, в этом случае метасимволу `.` (точка) соответствует любой одиночный символ, включая символ новой строки.
- `u` – unicode-трактовка. Выражение может содержать специальные паттерны, характерные для юникода.
## Основной синтаксис
### Любой символ `.`
На месте точки может быть любой символ. Количеством точек можно определять длину слов.
```js
/t..k/g
```
> take look team <ins>took</ins> hike track <ins>teak</ins> time
### Перечень `[]`
Позволяют указать определенный перечень символов.
```js
/t[aoi]k/g
```
> tek <ins>tok</ins> tdk <ins>tak</ins> <ins>tik</ins> tuk tyk took taoik
### Исключающий перечень `[^]`
Позволяет исключить определенный набор символ из поиска, используется вместе с квадратными скобками.
```js
/ba[^td]/g
```
> <ins>ban</ins> <ins>bag</ins> bat <ins>bas</ins> bad
### Диапазон `[-]`
Указывает диапазон с первого по последний символ (включительно) в алфавитном порядке.
```js
/[a-d]../g
```
> ost hst <ins>ast</ins> fst <ins>cst</ins> <ins>bst</ins>
Аналогично работает с цифрами:
```js
/201[5-9]/g
```
> 2010 2012 <ins>2015</ins> <ins>2017</ins> <ins>2019</ins> 2022
### Повторения `*`
Звездочка после символа указывает, что данный символ может отсутствовать, либо совпадать один или более раз.
```js
/wo*w/g
```
> <ins>wow</ins> waw wiw <ins>woooow</ins> wawe <ins>ww</ins> <ins>woow</ins>
### Повторения `+`
Плюс после символа указывает, что данный символ должен присутствовать один или более раз.
```js
/go+gle/g
```
> <ins>google</ins> ggle <ins>gogle</ins> gugle g00gle <ins>goooogle</ins>
### Необязательный символ `?`
Вопросительный знак после символа указывает, что данный символ является не обязательным (может либо отсутствовать, либо встрачаться только один раз).
```js
/bou?nd/g
```
> <ins>bond</ins> <ins>bound</ins> bouuund boynd
### Количество повторений `{}`
Чтобы указать точное количеcтво повторений, необходимо после символа записать фигурные скобки с нужным числом.
```js
/bo{3}m/g
```
> boom bom <ins>booom</ins> bm boooom
### Диапазон повторений `{,}`
Чтобы указать диапазон повторений, необходимо после символа записать фигурный скобки с нужным диапазоном, разделенным запятой.
```js
/lo{2,4}k/g
```
> lok <ins>look</ins> lk <ins>loook</ins> <ins>looook</ins> loooooook
Верхнюю границу можно опускать. Например, запись `a{3,}` говорит о том, что символ _a_ должен встречаться не менее трёх раз.
### Группировка `()`
Скобки позволяют сгруппировать любую последовательность символов, чтобы в дальнейшем обращается к ним используя выражение `\число`, где число - порядковый номер сгруппированной последовательности.
```js
/(la)-\1{2}-\1{3}/g // Группируем выражение "la" и затем, обращаемся к нему через "\1"
```
> la-laaa-<ins>la-lala-lalala</ins>-lalala-la-la-la
```js
/(la)-\1-(laa)-\2/g
```
> laa-la-laa-<ins>la-la-laa-laa</ins>-lalal
Чтобы игнорировать сохранение группы используется конструкция `(?:)`.
```js
/(?:abc)-(test),\1,\1/g // В данном случае группа "abc" не будет сохранена, поэтому первый индекс указывает на "test".
```
> abc,test-<ins>abc-test,test,test</ins>-abc-test
Группам можно задавать любые имена. Для этого используется конструкция - `(?P<Name>...)`, где Name - название, ... - любая последовательность символов. Для обращения к именованным группам используется конструкция - `(?P=Name)`.
```js
/(?P<seven>7{3})-(?P=seven){2}-(?P=seven)/g
```
> 7777-77-7777777-<ins>777-777777-777</ins>-777-7-7-7-7777-7
Если у Вас возникли трудности с пониманием группировки, советую [посмотреть данное видео](https://youtu.be/W9CffcsYpAU).
### Логическое ИЛИ `|`
Вертикальная черта позволяет указывать альтернативные варианты для поиска. Это чем-то похоже на использование квадратных скобок `[abc]`, но только вертикальная черта может работать с целыми словами и выражениями, а не только с отдельными символами.
```js
/yes|no/g
```
> <ins>yes</ins>,maybe,<ins>no</ins>,idk,ok
### Экранирование `\`
Для того, чтобы использовать в поиске специальные символы `{} [] / \ + *. $ ^ |?`, необходимо поставить впереди знак косой черты `\`.
```js
/\.|\?/g // Поиск точек "." или знаков вопроса "?"
```
> What now<ins>?</ins> What next<ins>?</ins> Times up<ins>.</ins> Wake up<ins>.</ins>
### Поиск в начале строки `^`
Символ каретки в регулярном выражении говорит о том, что поиск производится только по началу строк.
```js
/^[0-9]*/gm // Поиск чисел которые находятся в начале строки
```
> <ins>1</ins>. Apples x10 <br>
> <ins>2</ins>. Cookies x5 <br>
> <ins>3</ins>. Eggs x7
### Поиск в конце строки `$`
Символ доллара в регулярном выражении говорит о том, что поиск производится только по концу строк.
```js
/com|net/gm
```
> google.<ins>com</ins> <br>
> nodejs.org <br>
> sourceforge.<ins>net</ins>
### Классы символов
Для более удобного поиска целого класса символов существуют встроенные обозначения.
#### Любой словесный символ `\w`
Обе записи ниже эквивалентны.
```js
/[a-zA-Z0-9_]/g
```
```js
/\w/g
```
> <ins>some</ins> <ins>random</ins> <ins>words</ins> <ins>for</ins> <ins>example</ins>
#### Любой не словесный символ `\W`
```js
/[^a-zA-Z0-9_]/g
```
```js
/\W/g
```
> developer_2022<ins>@</ins>gmail<ins>.</ins>com
#### Любая цифра `\d`
```js
/[0-9]/g
```
```js
/\d/g
```
> developer_<ins>2022</ins>@gmail.com
#### Любой символ кроме цифр `\D`
```js
/[^0-9]/g
```
```js
/\D/g
```
> <ins>developer_</ins>2022<ins>@gmail.com</ins>
#### Пробел `\s`
К пробелам также относятся различные символы переноса строк.
```js
/[\r\n\t\f\v ]/g
```
```js
/\s/g
```
#### Любой символ кроме пробела `\S`
```js
/[^\r\n\t\f\v ]/g
```
```js
/\S/g
```
### Позиционные проверки
Для того, чтобы найти фразу, которая должна находится до или после другой фразы используются позиционные проверки (lookarounds).
#### Опережающие проверки `(?=)` `(?!)`
Чтобы найти выражение Х после которого стоит выражение Y, используется конструкция `X(?=Y)`.
```js
/\d+(?=€)/g
```
> 200$ <ins>750</ins>€ 100$ <ins>330</ins>€ 550$
Чтобы найти выражение Х после которого НЕ стоит выражение Y, используется конструкция `X(?!Y)`.
```js
/\d{4,}(?!€)/g
```
> This car was costed about 7000€ in <ins>2015</ins>
#### Ретроспективные проверки `(?<=)` `(?<!)`
Чтобы найти выражение X перед которым стоит выражение Y, используется конструкция `(?<=Y)X`.
```js
/(?<=:)\d+/g
```
> {"id":<ins>4</ins>,"value":<ins>123</ins>,name:"test"}
Чтобы найти выражение X перед которым НЕ стоит выражение Y, используется конструкция `(?<!Y)X`.
```js
/(?<!\$)\d+/g
```
> $5 $6 $7 <ins>2019</ins> <ins>2009</ins> <ins>1999</ins>
## Практика
Потратьте немного времени на закрепление изученного материала. Напишите простую библиотеку на Вашем любимом языке программирования, которая будет выполнять валидации (проверки) заданных строк. Например, на соответствие электронной почты или номера телефона. Напишите валидатор для паролей, чтобы он соответствовал заданным требованиям по длине, наличию специальных символов, заглавных букв или цифр. Это будет вдвойне полезным занятием, поскольку в будущем вы сможете использовать эту библиотеку в Ваших приложениях.
Дополнительно по запросу в Google – _regex practice_, можно найти много интересных заданий на тему регулянрых выражений.
## Дополнительные материалы
1. 📄 [**Awesome Regex** – GitHub](https://github.com/aloisdg/awesome-regex)
1. 📺 [**Регулярные выражения в SEO: где и как применяются** – YouTube](https://youtu.be/ZxWfQW32dIE)
1. 📘 [**Регулярные выражения. Сборник рецептов** – Ян Гойвертс, Стивен Левитан, 2010](https://www.litmir.me/data/Book/0/156000/156298/Goiverts_Yan_Regulyarnye_vyracheniya._Sbornik_receptov_%5BPodrobnye_resheniya_na_vosmi_yazykah_programmirova_Litmir.net_bid156298_original.pdf)

BIN
images/security/hashing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB