Урок 25. Написание простых bash-скриптов [105.2]

Рейтинг: 70.2% · 15 голосов
Полный курс LPIC-1 (экзамены 101-500 и 102-500): архитектура, загрузка, пакеты, команды и текст, ФС и права, шелл-скрипты, пользователи, сервисы, сеть, безопасность. Debian и RHEL.
Ответить
Аватара пользователя
Sergey_Sysadmin
Сообщения: 134
Зарегистрирован: 11 май 2026, 05:31

Урок 25. Написание простых bash-скриптов [105.2]

Сообщение Sergey_Sysadmin »

Оглавление курса (41)
  1. Введение в LPIC-1 и как устроен путь администратора
  2. Железо, устройства и модули ядра [101.1]
  3. Загрузка системы: от BIOS до systemd [101.2]
  4. systemd, цели и уровни выполнения [101.3]
  5. План разметки диска и swap [102.1]
  6. Загрузчик GRUB 2 [102.2]
  7. Разделяемые библиотеки [102.3]
  8. Управление пакетами в Debian: dpkg и apt [102.4]
  9. Управление пакетами RPM, DNF и Zypper [102.5]
  10. Linux как гость виртуализации [102.6]
  11. Командная строка Bash [103.1]
  12. Обработка текста фильтрами [103.2]
  13. Базовое управление файлами [103.3]
  14. Потоки, конвейеры и перенаправление [103.4]
  15. Процессы: создание, мониторинг, сигналы [103.5]
  16. Приоритеты выполнения процессов [103.6]
  17. Регулярные выражения [103.7]
  18. Редактор vi и vim [103.8]
  19. Разделы и создание файловых систем [104.1]
  20. Целостность и обслуживание ФС [104.2]
  21. Монтирование файловых систем [104.3]
  22. Урок 21. Права доступа и владение: rwx, chmod, umask и специальные биты
  23. Жёсткие и символические ссылки
  24. FHS и поиск файлов в системе [104.7]
  25. Окружение и кастомизация оболочки [105.1]
  26. Урок 25. Написание простых bash-скриптов [105.2] (вы здесь)
  27. Графика, рабочие столы и доступность
  28. Учётные записи пользователей и групп
  29. Автоматизация задач: cron, at, таймеры [107.2]
  30. Локализация и интернационализация [107.3]
  31. Системное время и синхронизация [108.1]
  32. Системное логирование [108.2]
  33. Основы почтового агента (MTA) [108.3]
  34. Печать и CUPS [108.4]
  35. Основы интернет-протоколов [109.1]
  36. Постоянная конфигурация сети [109.2]
  37. Диагностика сети [109.3]
  38. DNS на стороне клиента [109.4]
  39. Задачи администрирования безопасности [110.1]
  40. Настройка безопасности хоста [110.2]
  41. Шифрование данных: SSH и GnuPG [110.3]
Урок 25. Написание простых bash-скриптов

Любая рутина администратора рано или поздно превращается в скрипт - бэкап, проверка сервиса, разбор логов, массовое создание пользователей. Скрипт это не магия, а просто файл с командами, которые вы и так набираете руками, плюс логика: проверить условие, повторить в цикле, принять аргумент. В этом уроке разберём анатомию bash-скрипта от shebang до кода возврата, чтобы вы писали их уверенно и понимали, почему оно ведёт себя именно так.

Изображение

Как это работает

Скрипт это текстовый файл. Чтобы ядро знало, кем его запускать, в первой строке ставят shebang - два символа #! и путь к интерпретатору. Когда вы запускаете файл как программу, ядро читает эти первые байты, видит #!/bin/bash и фактически вызывает /bin/bash ваш_файл. Без shebang файл выполнит текущая оболочка, что не всегда bash - в Debian/Ubuntu /bin/sh это dash, и многие конструкции bash там просто не работают. Поэтому для bash-специфики пишите явно #!/bin/bash, а для переносимого POSIX-скрипта #!/bin/sh.

Чтобы файл можно было запустить как ./script.sh, ему нужен бит исполнения (chmod +x). Без него ядро откажет с Permission denied, хотя bash script.sh всё равно сработает - тут вы запускаете интерпретатор явно, а файл для него просто данные.

Условия в bash строятся вокруг кода возврата. Каждая команда после завершения отдаёт число 0-255: ноль значит успех, всё остальное - ошибка. Это число лежит в переменной $?. Конструкция if смотрит именно на код возврата команды, а не на текст. Команда test (она же [ ]) сама ничего не печатает - она лишь возвращает 0 или 1 в зависимости от того, истинно условие или нет. Современная замена для bash - [[ ]]: это ключевое слово оболочки, а не команда, поэтому внутри не нужно экранировать < и >, безопаснее с пустыми переменными, и оно умеет && || и сравнение по шаблону =~.

Циклы дают повтор: for идёт по списку слов, while крутится пока команда успешна, until - пока неуспешна. Аргументы, переданные скрипту, лежат в позиционных параметрах $1 $2 и так далее, $# хранит их количество, $@ - все сразу. exit завершает скрипт и задаёт его собственный код возврата, который увидит вызывающий.

Команды и примеры

Скелет скрипта и запуск:

Код: Выделить всё

#!/bin/bash
set -euo pipefail   # строгий режим: падать на ошибке, на необъявленной переменной, ловить сбой в пайпе

echo "Привет, $USER"

Код: Выделить всё

chmod +x backup.sh   # дать право на исполнение
./backup.sh          # запуск как программы (нужен shebang и +x)
bash backup.sh       # запуск явным интерпретатором (+x не нужен)
Условия - три формы. Старый test и [ ] требуют пробелов вокруг скобок и кавычек вокруг переменных:

Код: Выделить всё

if [ "$1" = "start" ]; then
    echo "запускаю"
elif [ -z "$1" ]; then
    echo "нет аргумента"
else
    echo "неизвестно: $1"
fi
То же на [[ ]] - чище и безопаснее, это рекомендуемый вариант для bash:

Код: Выделить всё

if [[ -f /etc/os-release && -r /etc/os-release ]]; then
    echo "файл есть и читается"
fi

# сравнение чисел: -eq -ne -lt -le -gt -ge
n=7
if [[ $n -gt 3 && $n -lt 10 ]]; then echo "в диапазоне"; fi

# шаблон и регэксп
host=web01
if [[ $host == web* ]]; then echo "веб-сервер"; fi
if [[ $host =~ ^web[0-9]+$ ]]; then echo "имя по маске"; fi
Частые ключи test: -f файл, -d каталог, -e существует, -r/-w/-x права, -z пустая строка, -n непустая, для строк = и !=, для чисел -eq -lt и компания.

case удобнее длинной лесенки elif - выбор по шаблону:

Код: Выделить всё

case "$1" in
    start)         echo "старт" ;;
    stop|restart)  echo "стоп или рестарт" ;;
    *.conf)        echo "конфиг" ;;
    *)             echo "по умолчанию" ;;
esac
Циклы:

Код: Выделить всё

for f in /var/log/*.log; do
    echo "обрабатываю $f"
done

for i in $(seq 1 5); do echo "шаг $i"; done   # seq печатает 1 2 3 4 5
for i in {1..5}; do echo "шаг $i"; done        # то же без внешней команды

n=0
while [[ $n -lt 3 ]]; do
    echo "while $n"; ((n++))
done

until ping -c1 -W1 8.8.8.8 &>/dev/null; do
    echo "сети нет, жду..."; sleep 2
done
Чтение построчно - именно так читают файлы, а не через for:

Код: Выделить всё

while IFS= read -r line; do
    echo ">> $line"
done < /etc/hostname
Аргументы, подстановка команд, коды возврата:

Код: Выделить всё

#!/bin/bash
echo "имя скрипта: $0"
echo "первый аргумент: $1"
echo "всего аргументов: $#"
echo "все аргументы: $@"

today=$(date +%F)          # подстановка: вывод команды попадает в переменную
echo "сегодня $today"

if ! systemctl is-active --quiet nginx; then
    echo "nginx не работает" >&2   # ошибки - в stderr
    exit 1                          # свой код возврата
fi
echo "проверка пройдена"
exit 0

Код: Выделить всё

./check.sh nginx
echo $?    # код возврата последней команды: 0 успех, не 0 ошибка
Чтение ввода от пользователя и отладка:

Код: Выделить всё

read -rp "Удалить файлы? [y/N] " ans
[[ $ans == [yY] ]] && echo "удаляю"

# базовая отладка: видеть каждую команду после раскрытия
set -x
cp file.txt /tmp/
set +x   # выключить трассировку
Менеджеры пакетов различаются, и скрипты это учитывают. Определять семейство удобно так:

Код: Выделить всё

if command -v apt >/dev/null; then
    apt install -y htop          # Debian 13 / Ubuntu 24.04
elif command -v dnf >/dev/null; then
    dnf install -y htop          # RHEL 10 / Fedora 41+ (dnf5)
fi
Частые грабли
  • Пробелы вокруг = в test обязательны. [ "$x"="y" ] всегда истинно, потому что это проверка одной непустой строки, а не сравнение. Нужно [ "$x" = "y" ].
  • Непроставленные кавычки. Если $f пустая или содержит пробел, [ -f $f ] ломается с ошибкой синтаксиса. Либо кавычьте, либо используйте [[ ]], где это не страшно.
  • = в test это строки, -eq это числа. [ "08" = "8" ] ложно, [ "08" -eq "8" ] истинно. Путаница даёт тихие баги.
  • for line in $(cat file) рвёт строки по пробелам, а не по переводам строки. Читайте файлы через while read.
  • Забыли chmod +x - ./script.sh даёт Permission denied. Либо chmod, либо запуск через bash script.sh.
  • #!/bin/sh не равно bash. В Debian/Ubuntu sh это dash, и [[ ]], массивы, ((...)) там не работают. Для bash-фич явно ставьте #!/bin/bash.
  • set -e не панацея: команда в if или с || не считается фатальной, и пайп без pipefail прячет ошибку левой части.
  • read без -r съедает обратные слэши. Почти всегда нужен read -r.
Мини-лаба
  • Создайте файл svccheck.sh, первой строкой #!/bin/bash, второй set -euo pipefail.
  • Сделайте его исполняемым через chmod +x и запустите без аргументов - добейтесь, чтобы он печатал в stderr подсказку об использовании и выходил с exit 2, если $# равно нулю.
  • Принимайте имя сервиса как $1 и через systemctl is-active --quiet проверяйте его состояние, ветвление сделайте на if/else.
  • Через case по $1 обработайте особый случай: если передали слово all - в цикле for пройдите по списку sshd cron и проверьте каждый.
  • Добавьте подстановку команд: в вывод включите дату через $(date +%T) и имя хоста через $(hostname).
  • Включите set -x в начале, прогоните, посмотрите трассировку, затем уберите и сравните вывод.
  • Проверьте echo $? после успешного и после неуспешного запуска - убедитесь, что коды разные.
Контрольные вопросы
  • Что делает ядро, встретив строку #!/bin/bash, и почему скрипт без shebang может вести себя по-разному в разных системах?
  • Чем [[ ]] отличается от [ ] и от команды test? Назовите минимум два практических преимущества [[ ]].
  • В чём разница между $@, $# и $0? Что вернёт $? и какие значения считаются успехом?
  • Почему чтение файла построчно делают через while IFS= read -r, а не через for line in $(cat file)?
  • Когда выбрать case вместо цепочки if/elif, и как в case задать несколько шаблонов на одну ветку?
  • Что делает set -x и чем он отличается от set -e и set -o pipefail?
👍1 ❤️3 🔥2 😄 🤔1
Аватара пользователя
mcpers
Сообщения: 1
Зарегистрирован: 24 май 2026, 05:29

Re: Урок 25. Написание простых bash-скриптов [105.2]

Сообщение mcpers »

А зачем set -euo pipefail если можно просто if везде проверять? Я думал -e и так на всё ловит ошибки
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
josse
Сообщения: 1
Зарегистрирован: 23 май 2026, 07:11

Re: Урок 25. Написание простых bash-скриптов [105.2]

Сообщение josse »

Поймал баг ровно как у вас: for f in $(cat list) разъезжался когда в путях были пробелы. Переписал на while read -r line и всё встало на место, спасибо
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Окружение и кастомизация оболочки [105.1]
Следующая глава →
Графика, рабочие столы и доступность

Все главы курса «LPIC-1: администратор Linux (101 + 102)»

Поделиться темой: ✈ Telegram VK

Вернуться в «LPIC-1: администратор Linux»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость