Первый Bash-скрипт: переменные, аргументы, условия и функции
Автор: Казачкин Даниил Михайлович · Обновлено
Bash-скрипт связывает команды и обрабатывает их результаты. Начнём с небольшого отчёта по файлам: он принимает пути, проверяет каждый, выводит число строк и возвращает ошибку,…
Bash-скрипт связывает команды и обрабатывает их результаты. Начнём с небольшого отчёта по файлам: он принимает пути, проверяет каждый, выводит число строк и возвращает ошибку, если встретился неподходящий вход. Пример запускается в GNU/Linux без административных прав.
Интерпретатор и способ запуска
Первая строка #!/usr/bin/env bash выбирает Bash через PATH при непосредственном запуске. Для полностью контролируемого окружения можно использовать проверенный абсолютный путь интерпретатора. bash report.sh запускает файл без execute-бита, chmod u+x report.sh и ./report.sh используют shebang. Команда sh report.sh принудительно выбирает sh; Bash-массивы или [[ ]] в таком режиме не обязаны работать.
Перед запуском чужого install.sh прочитайте его и инструкции проекта, проверьте источник и необходимые привилегии. Суффикс .sh не гарантирует безопасность и даже не задаёт интерпретатор. command not found для собственного файла часто означает отсутствие ./, а Permission denied — права, noexec или другую политику доступа.
Полный скрипт с наблюдаемым результатом
Создайте в отдельном учебном каталоге файл report.sh:
#!/usr/bin/env bash
report_file() {
local path=$1
local count
if [[ ! -f "$path" || ! -r "$path" ]]; then
printf 'cannot read regular file: %s\n' "$path" >&2
return 1
fi
if ! count=$(wc -l <"$path"); then
printf 'read failed: %s\n' "$path" >&2
return 1
fi
printf '%s: %s lines\n' "$path" "$count"
}
if (( $# == 0 )); then
printf 'usage: %s FILE...\n' "$0" >&2
exit 2
fi
result=0
for path in "$@"; do
if ! report_file "$path"; then
result=1
fi
done
exit "$result"Проверка -r не отменяет гонку: права или файл могут измениться до чтения, поэтому статус wc также обрабатывается. $# — количество аргументов, $0 — имя запуска, "$@" — каждый переданный аргумент отдельно. Это сохраняет имя two words.txt одним путём. Функция читает собственный $1; local не позволяет её временной переменной незаметно заменить переменную вызывающего кода.
printf '%s\n' one two >'two words.txt'
bash report.sh 'two words.txt'
printf 'status=%s\n' "$?"
bash report.sh 'two words.txt' missing.txt
printf 'status=%s\n' "$?"Первый запуск выводит two words.txt: 2 lines и статус 0. Второй сохраняет успешную строку, пишет диагностику отсутствующего пути в stderr и возвращает 1. Без аргументов статус 2. wc -l считает переводы строк — это оговорённый контракт отчёта, не универсальный счётчик логических записей.
Переменные, export и чтение ввода
Присваивание не содержит пробелов вокруг =: name='build report'. name+='.txt' дописывает строку, unset name удаляет переменную. Имена обычно состоят из букв, цифр и подчёркиваний и не начинаются с цифры. $(команда) подставляет stdout и удаляет завершающие переводы строк. text=$(<file.txt) удобно для небольшого текста, но Bash не хранит NUL-байты и это не побайтовая копия файла.
Shell-переменная живёт в оболочке, export NAME включает её в окружение дочерних программ. Дочерняя программа не меняет переменную родителя. Глобальная переменная видна функциям этой Bash; используйте local для внутреннего состояния. Переменные окружения не являются хранилищем секретов с автоматической защитой.
printf 'name: '
IFS= read -r name
printf 'received <%s>\n' "$name"-r сохраняет обратную косую черту, пустой IFS избегает удаления крайних пробелов. Read может вернуть неуспех при EOF; скрипт должен решить, допустимо ли это. Для потокового чтения файла пригоден цикл while IFS= read -r line || [[ -n "$line" ]]; do ...; done <file, сохраняющий последнюю незавершённую строку.
Условия, case и циклы
if команда; then проверяет её статус, а не непустоту вывода. В [[ ]] используют файловые/строковые проверки, в (( )) — арифметику. Для выбора из фиксированных вариантов удобно case:
mode=brief
case "$mode" in
brief) printf 'short report\n' ;;
full) printf 'full report\n' ;;
*) printf 'unknown mode\n' >&2 ;;
esac
for number in 1 2 3; do
printf 'item=%s\n' "$number"
doneЦикл for перебирает аргументы, while повторяет действие, пока команда-условие успешна. Не используйте for path in $(ls): пробелы и переводы строк разрушат имена. Return завершает функцию, exit — весь скрипт. Alias удобен в интерактивной Bash, но для переносимого скрипта определяйте функцию или команду явно.
Ошибки и практика
set -e не является полной обработкой ошибок: его действие зависит от контекста, включая if, &&, || и конвейеры. Скрипт отчёта проверяет важные операции явно. Добавление set -u также меняет обработку отсутствующих аргументов; проектируйте их проверки, а не вставляйте флаги вслепую.
Запустите bash -n report.sh, затем проверьте файл с пробелом, пустой файл, отсутствующий путь и отсутствие аргументов. Добавьте режим подсчёта байтов через case и отдельную функцию; статусы должны сохранить смысл. Если функция запущена через &, она работает в дочернем окружении: присваивание там не вернёт значение в родительскую переменную. Для асинхронного результата нужны явный канал и wait из следующего урока.