10 (Доп.) Язык исполняемой оболочки bash
Отчет по форме не оформляется. Необходимо предоставить скрипты, выполняющие упражнения, тестовые входные данные. Обязательно продемонстрировать работу скриптов преподавателю.
10.1 Дополнительная теория
Во время долгой рутинной работы: неважно, компьютерной или любой другой - возникает непреодолимое желание автоматизировать её. Автоматизировать свою работу за компьютером позволяет широкий функционал языка сценариев исполняемой оболочки. С егопомощью мы можем заменить какую-то сложную последовательность действий одной операцией. bash (Bourne again shell) является одной из исполняемых оболочек, самой известной. Оболочка может быть разной в зависимости от дистрибутива или операционной системы. Помимо самого терминала (shell) и bash, также существуют оболочки ksh (Korn shell), csh (C shell), zsh (Z shell). Все они немного отличаются между собой, но во много очень сильно похожи. Мы рассматриваем bash как наиболее популярную.
В самом простом варианте сценарий (скрипт) исполняемой оболочки является обычным текстовым файлом с расширением .sh, в котором записаны те же самые команды из командной строки, которые вы запускали до это вручную. Создадим с вами простой скрипт.
Создайте файл first_example.sh с помощью редактора vim. Напишите в него команду, которая выводит на экран текущее время, затем с новой строки команду, выводящую текущую рабочую директорию. Сохраните файл.
date
pwdЧтобы проверить работу нашего скрипта, нужно выдать файлу права на запуск. Вспомните как это сделать?
chmod u+x first_example.sh
# Строки, начинающиеся с решетки это комменатарии. Они не выполняются и носят пояснительную функцию
# Более короткая форма записи команды ниже: ./first_example.sh
bash first_example.shЕсли вы увидели на выводе терминала дату и текущую директорию, вы большие молодцы и написали свой первый скрипт на bash! Если что-то пошло не так, вернитесь на шаг назад и подумайте над сообщениями об ошибках. В случае затруднения обратитесь к преподавателю.
Хорошей практикой считается прописывать для каждого скрипта чем его запускать. Для этого первая строка в файле должна начинаться с шебанга1 (shebang) - последовательности из двух символов: решетки и восклицательного знака. После него в этой же строке, без пробела, должен быть прописан путь к интерпретатору.
Как узнать, где лежит интерпретатор?
which bash
Второй момент связан с тем, что bash скрипт не останавливает своё выполнение, если в каком-то месте возникла ошибка. Для избежания этого второй строкой прописывают команду set,которая имеет несколько опций. При таких настройках некоторые распространенные ошибки приведут к немедленному сбою скрипта, явному и громкому. В противном случае вы можете получить скрытые ошибки, которые будут обнаружены только тогда, когда они взорвут ваш продакшн.
set -eЭта опция предписывает bash немедленно завершить работу, если любая команда имеет ненулевой статус выхода. Вы бы не хотели устанавливать это для своей командной строки, но в скрипте это очень полезно. Во всех широко используемых языках программирования общего назначения - необработанная ошибка времени выполнения
будь то выброшенное исключение в Java, ошибка сегментации в C или синтаксическая ошибка в Python — выполнение программы немедленно останавливается; последующие строки не выполняются. По умолчанию bash этого не делает. Это поведение по умолчанию — именно то, что вам нужно, если вы используете bash в командной строке. Вы не хотите, чтобы опечатка вывела вас из системы! Но в скрипте вы хотите наоборот. Если одна строка в скрипте не срабатывает, но последняя строка срабатывает, весь скрипт имеет успешный код выхода. Это позволяет очень легко пропустить ошибку. Опять же, то, что вы хотите при использовании bash в качестве командной строки и при использовании его в скриптах, здесь не совпадает. Быть нетерпимым к ошибкам гораздо лучше в скриптах, и это то, что вам дает set -e.
set -x- Включает режим оболочки, в котором все выполненные команды выводятся на терминал. В вашем случае это явно используется для отладки, что является типичным вариантом использования set -x: вывод каждой команды по мере ее выполнения может помочь вам визуализировать поток управления скрипта, если он не функционирует так, как ожидалось.
set -uВлияет на переменные. Если установлена ссылка на любую переменную, которую вы ранее не определили — за исключением
$*и$@— является ошибкой и приводит к немедленному завершению программы. Такие языки, как Python, C, Java и другие, ведут себя одинаково, по разным причинам. Одна из них — чтобы опечатки не создавали новые переменные без вашего ведома. Например:#!/bin/bash firstName="Aaron" fullName="$firstname Maxwell" echo "$fullName"Остановитесь на минутку и посмотрите. Видите ошибку? В правой части третьей строки написано “firstname” строчными буквами, а не “firstName” в верблюжьем регистре. Без опции -u это будет молчаливая ошибка. Но с опцией -u скрипт завершается на этой строке с кодом выхода 1, выводя сообщение “firstname: unbound variable” в stderr.
Это то, чего вы хотите: чтобы сбой произошел явно и немедленно, а не создавал скрытые ошибки, которые могут быть обнаружены слишком поздно.
set -o pipefailЭта настройка предотвращает маскировку ошибок в конвейере. Если какая-либо команда в конвейере завершается неудачей, этот код возврата будет использоваться как код возврата всего конвейера. По умолчанию код возврата конвейера — это код последней команды, даже если она выполнена успешно. Представьте себе поиск отсортированного списка совпадающих строк в файле:
set -o pipefail grep some-string /non/existent/file | sort # grep: /non/existent/file: No such file or directory echo $? # 0Здесь grep имеет код выхода 2, записывает сообщение об ошибке в stderr и пустую строку в stdout.
Затем эта пустая строка передается через сортировку, которая успешно принимает ее как допустимые входные данные и возвращает код состояния 0.
Это хорошо для командной строки, но плохо для сценария оболочки: вы почти наверняка захотите, чтобы сценарий сразу же завершился с ненулевым кодом выхода… например так:
set -o pipefail grep some-string /non/existent/file | sort # grep: /non/existent/file: No such file or directory echo $? # 2
Программы возвращают числовый код, который сигнализирует о наличии или отсутствии какого-либо сбоя. Перечень распространенных кодов приведен ниже.
| Код | Причина |
|---|---|
| 0 | Успех |
| 1-255 | Провал |
| 1 | Общая ошибка |
| 2 | Неправильное использование команды |
| 13 | Доступ запрещён |
| 126 | Команда найдена, но неисполняема |
| 127 | Команда не найдена |
Хорошей практикой считается начинать свой скрипт с этих двух инструкций.
10.1.1 Переменные и типы данных
Чтобы определить переменную, необходимо задать ей имя и присвоить значение. Для того, чтобы обратиться к переменной, необходимо использовать знак доллара.
#| code-fold: show
#| eval: true
#NB! Никаких пробелов!
country="Russia"
echo $country
В скриптах Bash действуют следующие соглашения об именовании переменных:
Имена переменных должны начинаться с буквы или символа подчеркивания (
_).Имена переменных могут содержать буквы, цифры и символы подчеркивания (
_).Имена переменных чувствительны к регистру.
Имена переменных не должны содержать пробелов и специальных символов.
Используйте описательные имена, отражающие назначение переменной.
Избегайте использования зарезервированных ключевых слов, таких как
if,then,else,fiи т. д., в качестве имен переменных.
Допустимые имена
#| code-fold: show
name
count
_var
myVar
MY_VAR
Недопустимые имена
#| code-fold: show
2ndvar
my var
my-var
Bash довольно скуп на типы данных. Для него существуют только числа с фиксированной запятой, строки, массивы и словари. Интерпретатор сам определяет тип переменной, но его можно задать явно.
#| code-fold: show
# Целое число
declare -i myvar
# Массив
declare -a myvar
# Словарь
declare -A myvar
# Также есть дополнительные полезные опции, см man declare
Для целых чисел, bash может проводить все арифметические и логические операции. Полный список здесь.
#| code-fold: show
#| eval: true
var1=5
var2=10
var3=$var1+$var2
echo $var3
10.1.2 Специальные переменные
В этой таблице перечислены некоторые специальные переменные.
| Обращение к переменной | Функция |
|---|---|
| $# | Сколько аргументов командной строки было передано |
| $@ | Все переданные аргументы командной строки |
| $? | Выходной статус последнего запущенного процесса |
| $$ | Идентификатор процесса запущенного скрипта |
| $USER | Имя пользователя |
| $HOSTNAME | Имя машины |
| $SECONDS | Количество секунд, которое выполняется скрипт |
| $RANDOM | Случайное число |
| $LINENO | Номер тукщей строки в скрипте |
| $PWD | Текущая рабочая директория |
Кроме того, можно обратиться непосредственно к конкретному аргументу командной строки по его номеру от $1 до $9. $0 - имя скрипта.
Модифицируйте пример выше с сложением двух чисел, чтобы он работал с аргументами командной строки.
var1=$1
var2=$2
var3=$var1+$var2
echo $var3
10.1.3 Условные операторы
10.1.3.1 Оператор if-else
Синтаксис
if [[ condition ]]; then
statement1
elif [[ condition ]]; then
statement2
else
default statement
fibash не использует привычные операторы сравнения для чисел! Вместо них он использует специальные обозначения. Приведенные в таблице ниже
| Оператор | Значение |
|---|---|
| -lt | Меньше чем |
| -le | Меньше или равно |
| -gt | Больше чем |
| -ge | Больше или равно |
| -eq | Равно |
| -ne | Не равно |
Для сравнения строк используются также свои операторы.
| Выражение | Значение |
|---|---|
| string1 = string2 | Оператор равенства возвращает значение true, если операнды равны. Используйте |
| string1 == string2 | Оператор равенства возвращает значение true, если операнды равны. Используйте |
| string1 != string2 | Оператор неравенства возвращает значение true, если операнды не равны. |
| string1 =~ regex | Оператор регулярного выражения возвращает значение true, если левый операнд соответствует расширенному регулярному выражению справа. |
| string1 > string | Оператор «больше» возвращает значение true, если левый операнд больше правого, отсортированного в лексикографическом (алфавитном) порядке. |
| string1 < string | Оператор «меньше» возвращает значение true, если левый операнд больше правого, отсортированного в лексикографическом (алфавитном) порядке. |
| -z string | Строка нулевой длины |
| -n string | Строка ненулевой длины |
#| code-fold: show
#| eval: true
programmist="I use R!"
# Типичное поведение неумных фанатов Python.
# Сравниваемые переменныелучшезаключать в кавычки, чтобы были уверены, что сравниваете строки.
if [[ "$programmist" = "I love Python!" ]]; then
echo "You are good!"
else
echo "R is shit! Use Python!"
fi
Напишите скрипт, принимающий на вход число и проверяющий его знак.
number=$1
if [[ $number -gt 0 ]]; then
echo "Positive"
elif [[ $number -lt 0 ]]; then
echo "Negative"
else
echo "Zero"
fi
10.1.3.2 Оператор case
Синтаксис
case EXPRESSION in
PATTERN_1)
STATEMENTS
;;
PATTERN_2)
STATEMENTS
;;
PATTERN_N)
STATEMENTS
;;
*)
STATEMENTS
;;
esacОператор case необходим для выбора из ограниченного спектра вариантов. Часто используют в bash для парсинга именованных аругментов командной строки совместно с циклом while. Пример будет ниже.
10.1.4 Циклы
10.1.4.1 Цикл с предусловием
Синтаксис
while condition
do
statement1
statement2
doneПример: парсинг аргументов командной строки
while getopts u:a:f: flag
do
case "${flag}" in
u)
username=${OPTARG}
;;
a)
age=${OPTARG}
;;
f)
fullname=${OPTARG}
;;
esac
done
echo "Username: $username";
echo "Age: $age";
echo "Full Name: $fullname";
10.1.4.2 Цикл с постусловием
Чем отличаются цикл с предусловием и постусловием?
Синтаксис
until [CONDITION]
do
[COMMANDS]
doneСледующий скрипт может быть полезен, когда ваш хост git находится в состоянии простоя, и вместо того, чтобы вручную вводить git pullнесколько раз, пока хост не будет в сети, вы можете запустить скрипт один раз. Он будет пытаться вытащить репозиторий, пока это не будет успешно.
until git pull &> /dev/null
do
echo "Waiting for the git host ..."
sleep 1
done
echo -e "\nThe git repository is pulled."
10.1.4.3 Параметрический цикл
Цикл for имеет две формы записи. Первая форма используется для перебора массивов, вторая - для повторения операций заданное число раз
for name in words
do
commands
donefor (( i=0 ; i<=10 ; i++ ))
do
commands
done10.1.5 Массивы
array=( 1,2,3,4,5)
for i in array[@]
do
echo $i
done
10.1.6 Чтение файла
while IFS= read -r line; do
echo "Text read from file: $line"
done < my_filename.txt
Также полезны sed и awk (особенно для таблиц).
10.2 Задание
10.2.1 Упражнение 1: Простой скрипт для вывода информации
Напишите скрипт, который:
Выводит текущую дату и время.
Выводит имя пользователя.
Выводит текущую директорию.
Пример вывода:
Текущая дата и время: 2023-10-05 14:30:00
Имя пользователя: user
Текущая директория: /home/user
10.2.2 Упражнение 2: Скрипт для работы с файлами
Напишите скрипт, который:
Создает директорию с именем
test_dir.Внутри этой директории создает 5 файлов с именами
file1.txt,file2.txt, …,file5.txt.Записывает в каждый файл строку “Это файл номер N”, где N — номер файла.
Выводит содержимое всех файлов в терминал.
10.2.3 Упражнение 3: Скрипт для поиска файлов
Напишите скрипт, который:
Принимает на вход два аргумента: директорию и расширение файла (например,
.txt).Ищет все файлы с указанным расширением в указанной директории и её поддиректориях.
Выводит список найденных файлов с их полными путями.
Пример использования:
./find_files.sh /home/user .txt
10.2.4 Упражнение 4: Скрипт для анализа системы
Напишите скрипт, который:
Выводит информацию о системе: имя хоста, версия ядра, объем оперативной памяти, объем свободного места на диске.
Сохраняет эту информацию в файл
system_info.txt.Если файл уже существует, скрипт должен спросить пользователя, перезаписать ли его.
10.2.5 Упражнение 5: Скрипт для работы с пользователями
Напишите скрипт, который:
Принимает на вход имя пользователя.
Проверяет, существует ли пользователь в системе.
Если пользователь существует, выводит его домашнюю директорию и список процессов, запущенных от его имени.
Если пользователь не существует, предлагает создать его.
10.2.6 Упражнение 6: Скрипт для резервного копирования
Напишите скрипт, который:
Принимает на вход директорию, которую нужно скопировать, и директорию, куда нужно сохранить резервную копию.
Создает архив с именем
backup_YYYY-MM-DD.tar.gz, гдеYYYY-MM-DD— текущая дата.Проверяет, существует ли уже архив с таким именем, и если да, предлагает пользователю перезаписать его или создать новый с другим именем.
10.2.7 Упражнение 7: Скрипт для мониторинга процессов
Напишите скрипт, который:
Запускается в фоновом режиме.
Каждые 5 секунд проверяет, запущен ли процесс с именем, указанным в аргументе.
Если процесс не запущен, скрипт выводит сообщение и завершает работу.
Пример использования:
./monitor_process.sh firefox
10.2.8 Упражнение 8: Скрипт для работы с текстовыми файлами
Напишите скрипт, который:
Принимает на вход текстовый файл.
Подсчитывает количество строк, слов и символов в файле.
Выводит эту информацию в терминал.
Пример использования:
./text_stats.sh document.txt
10.2.9 Упражнение 9: Скрипт для работы с сетью
Напишите скрипт, который:
Принимает на вход IP-адрес.
Проверяет доступность этого IP-адреса с помощью команды
ping.Если IP-адрес доступен, выводит сообщение “IP доступен”.
Если IP-адрес недоступен, выводит сообщение “IP недоступен”.
Пример использования:
./check_ip.sh 192.168.1.1
10.2.10 Упражнение 10: Циклы и пайплайны
Напишите скрипт, который:
Используя цикл
for, выводит числа от 1 до 10.Затем с помощью пайплайна (
|) передаст эти числа командеgrep, чтобы отфильтровать только четные числа.Выведет результат в терминал.
Пример вывода:
2 4 6 8 10
10.2.11 Упражнение 11: Поиск с помощью grep
Напишите скрипт, который:
Принимает на вход директорию и строку для поиска.
Рекурсивно ищет все файлы в указанной директории, содержащие эту строку.
Выводит имена файлов и строки, в которых найдено совпадение.
Пример использования:
./search_text.sh /home/user "hello world"
10.2.12 Упражнение 12: Замена текста с помощью sed
Напишите скрипт, который:
Принимает на вход текстовый файл и две строки:
старая_строкаиновая_строка.Использует команду
sedдля замены всех вхожденийстарой_строкинановую_строкув файле.Сохраняет изменения в том же файле.
Пример использования:
./replace_text.sh file.txt "old_text" "new_text"
10.2.13 Упражнение 13: Комбинация grep, sed и awk
Напишите скрипт, который:
Принимает на вход текстовый файл.
Использует
grepдля поиска строк, содержащих слово “error”.Использует
sedдля удаления всех цифр из найденных строк.Использует
awkдля вывода только первых 5 символов каждой строки.
Пример использования:
./process_errors.sh logfile.txt
10.2.14 Упражнение 14: Цикл с условием
Напишите скрипт, который:
Используя цикл
while, читает строки из файла построчно.Если строка содержит слово “warning”, выводит её в терминал.
Если строка содержит слово “error”, завершает выполнение скрипта с сообщением “Обнаружена ошибка!”.
Пример использования:
./check_log.sh logfile.txt
10.2.15 Упражнение 15: Пайплайны и awk
Напишите скрипт, который:
Использует команду
dfдля получения информации о дисковом пространстве.Передает вывод через пайплайн в
awk, чтобы отфильтровать только те разделы, где использование превышает некоторый порог.Выводит список таких разделов.
Пример вывода:
/dev/sda1 85% /dev/sdb2 90%
10.2.16 Упражнение 16: Анализ CSV с помощью awk
Напишите скрипт, который:
Принимает на вход CSV-файл с данными (например,
data.csv).Использует
awkдля подсчета суммы значений в определенной колонке (например, второй колонке).Выводит результат.
Пример CSV:
Name,Amount
Alice,100
Bob,200
Charlie,300
Пример использования:
./sum_column.sh data.csv 2
Пример вывода:
Сумма: 600
10.3 Вопросы для защиты
Выполнение скрпитов в bash
Переменные и типы данных в bash
Условия и циклы в bash
Статусы выхода. Сигналы
Специальные переменные, позиционные аргументы
непереводимый термин↩︎