Основы написания скриптов оболочки

ShellBeginner
Практиковаться сейчас

Введение

Скрипт оболочки хранит команды в файле, чтобы задачу можно было выполнять повторно и предсказуемо. Надёжные скрипты для начинающих явно указывают интерпретатор, заключают подстановки переменных в кавычки, проверяют входные данные перед выполнением действий и возвращают код завершения, понятный другим командам.

В этой лабораторной работе вы будете постепенно создавать небольшие Bash-скрипты. Вы запустите скрипты через Bash и как исполняемые файлы, используете переменные и позиционные аргументы, прочитаете интерактивный ввод, организуете ветвление с помощью if и проверок файлов, выберете коды завершения, выполните итерацию с помощью for и объедините эти приёмы в простом анализаторе журналов.

Создание и запуск первого скрипта

На этом этапе вы создадите Bash-скрипт, запустите его через интерпретатор и сделаете его непосредственно исполняемым.

Перейдите в рабочий каталог лаборатории:

cd /home/labex/project/shell-lab

Создайте hello.sh с помощью here-документа. Первая строка — это шебанг, указывающий Bash в качестве интерпретатора при непосредственном запуске файла:

cat > hello.sh <<'EOF'
#!/bin/bash

echo "Hello from a Bash script"
pwd
EOF

Перед запуском просмотрите содержимое файла:

cat hello.sh

Явно передайте файл Bash. Это сработает даже до добавления разрешения на выполнение:

bash hello.sh

Добавьте разрешение на выполнение и проверьте режим доступа:

chmod u+x hello.sh
ls -l hello.sh

Запустите файл из текущего каталога. ./ указывает путь; по умолчанию оболочка обычно не ищет команды в текущем каталоге автоматически:

./hello.sh

Использование переменных и безопасных кавычек

На этом этапе вы присвоите значения переменным, подставите их и увидите, почему двойные кавычки защищают значения, содержащие пробелы.

Создайте скрипт с двумя переменными:

cat > variables.sh <<'EOF'
#!/bin/bash

course="Linux for Noobs"
workspace="/home/labex/project/shell-lab"

echo "Course: $course"
echo "Workspace: $workspace"
printf 'Quoted argument count: %s\n' "$#"
printf 'First argument: %s\n' "${1:-not provided}"
EOF

В присваивании переменной в оболочке вокруг = не должно быть пробелов. Двойные кавычки позволяют раскрыть $course, сохраняя Linux for Noobs как единое значение.

Запустите скрипт с одним аргументом, содержащим пробелы:

bash variables.sh "beginner learner"

Специальная переменная $# содержит количество позиционных аргументов, а $1 — первый аргумент. ${1:-not provided} подставляет запасное значение, если аргумент отсутствует.

Сохраните результат для просмотра:

bash variables.sh "beginner learner" > variable-output.txt
cat variable-output.txt

Проверка позиционных аргументов

На этом этапе вы потребуете наличие аргумента, выведете подсказку по использованию и вернёте разные коды завершения для ошибки и успешного выполнения.

Создайте greet-argument.sh:

cat > greet-argument.sh <<'EOF'
#!/bin/bash

if [ "$#" -ne 1 ]; then
  echo "Usage: $0 NAME" >&2
  exit 2
fi

name=$1
echo "Hello, $name"
exit 0
EOF

Тестовая конструкция [ ... ] проверяет, не равно ли количество аргументов единице. >&2 отправляет текст с инструкцией в стандартный поток ошибок, а exit 2 сообщает о некорректном использовании скрипта.

Запустите скрипт без аргумента, а затем сразу выведите $? — код завершения предыдущей команды:

bash greet-argument.sh
echo "missing argument status: $?"

Ожидаемый код — 2. Теперь запустите скрипт правильно:

bash greet-argument.sh "Ada Lovelace"
echo "valid argument status: $?"

Сохраните приветствие, полученное при успешном запуске:

bash greet-argument.sh "Ada Lovelace" > argument-greeting.txt

Чтение интерактивного ввода

На этом этапе вы запросите у пользователя данные, безопасно прочитаете одну строку и обработаете пустой ввод.

Создайте ask-name.sh:

cat > ask-name.sh <<'EOF'
#!/bin/bash

read -r -p "Enter your name: " name

if [ -z "$name" ]; then
  echo "A name is required" >&2
  exit 1
fi

echo "Welcome, $name"
EOF

read -r читает строку, не обрабатывая обратные косые черты особым образом. -p выводит приглашение, а -z проверяет, является ли значение в кавычках пустым.

Запустите скрипт:

bash ask-name.sh

В ответ на приглашение введите:

Grace Hopper

Вы должны увидеть Welcome, Grace Hopper. Чтобы получить воспроизводимый сохранённый результат, передайте этому же скрипту одну строку ввода через канал:

printf 'Grace Hopper\n' | bash ask-name.sh > interactive-greeting.txt
cat interactive-greeting.txt

Ветвление с проверками файлов

На этом этапе вы используете if, elif и else вместе с проверками файлов, чтобы определить тип пути.

Создайте classify-path.sh:

cat > classify-path.sh <<'EOF'
#!/bin/bash

path=${1:-}

if [ -z "$path" ]; then
  echo "Usage: $0 PATH" >&2
  exit 2
elif [ -d "$path" ]; then
  echo "directory: $path"
elif [ -f "$path" ]; then
  echo "file: $path"
else
  echo "missing: $path" >&2
  exit 1
fi

exit 0
EOF

-d проверяет, является ли путь каталогом, а -f — обычным файлом. Порядок проверок важен: некорректное использование обрабатывается до определения типа пути.

Проверьте все три возможных результата:

bash classify-path.sh logs
bash classify-path.sh notes.txt
bash classify-path.sh missing.txt
echo "missing path status: $?"

Сохраните успешные результаты в один файл. && запускает вторую команду только в том случае, если первая завершилась успешно:

bash classify-path.sh logs > path-results.txt && bash classify-path.sh notes.txt >> path-results.txt
cat path-results.txt

Обработка нескольких файлов с помощью цикла For

На этом этапе вы пройдёте по файлам журналов и подсчитаете количество совпадающих записей в каждом из них.

Создайте count-errors.sh:

cat > count-errors.sh <<'EOF'
#!/bin/bash

log_dir=${1:-logs}

if [ ! -d "$log_dir" ]; then
  echo "Log directory not found: $log_dir" >&2
  exit 1
fi

for log_file in "$log_dir"/*.log; do
  error_count=$(grep -c '^ERROR ' "$log_file" || true)
  printf '%s errors=%s\n' "$(basename "$log_file")" "$error_count"
done
EOF

На каждой итерации цикл присваивает log_file один из найденных путей. Кавычки вокруг "$log_file" защищают пути, содержащие пробелы, а basename удаляет префикс каталога. grep -c подсчитывает совпадающие строки; || true не позволяет отсутствию совпадений прервать более крупный строгий сценарий выполнения.

Запустите цикл и сохраните его вывод:

bash count-errors.sh logs | tee error-counts.txt

Вы должны увидеть один результат для каждого файла .log.

Объединение проверок, циклов и кодов завершения

На этом этапе вы создадите универсальный скрипт, который проверяет каталог, обобщает данные из журналов и явно сообщает об успехе или ошибке.

Создайте summarize-logs.sh:

cat > summarize-logs.sh <<'EOF'
#!/bin/bash

input_dir=${1:-}
output_file=${2:-}

if [ -z "$input_dir" ] || [ -z "$output_file" ]; then
  echo "Usage: $0 INPUT_DIR OUTPUT_FILE" >&2
  exit 2
fi

if [ ! -d "$input_dir" ]; then
  echo "Input directory not found: $input_dir" >&2
  exit 1
fi

: > "$output_file"

for log_file in "$input_dir"/*.log; do
  lines=$(wc -l < "$log_file")
  errors=$(grep -c '^ERROR ' "$log_file" || true)
  printf '%s lines=%s errors=%s\n' "$(basename "$log_file")" "$lines" "$errors" >> "$output_file"
done

echo "Wrote summary to $output_file"
exit 0
EOF

Команда : > "$output_file" безопасно создаёт файл назначения или очищает его перед началом цикла. Запустите скрипт, указав оба обязательных аргумента:

chmod u+x summarize-logs.sh
./summarize-logs.sh logs log-summary.txt
echo "summary status: $?"
cat log-summary.txt

Проверьте ветку с ошибкой, указав несуществующий каталог:

./summarize-logs.sh missing log-summary.txt
echo "missing directory status: $?"

Код завершения при успехе должен быть 0, а при отсутствии каталога — 1.

Итоги

Вы создали и запустили Bash-скрипты, использовали шебанги и разрешения доступа, выполняли подстановку переменных в кавычках, читали позиционные и интерактивные данные и возвращали информативные коды завершения. Кроме того, вы организовали ветвление с помощью if/elif/else, проверяли файлы и каталоги, обрабатывали журналы в цикле for и объединили эти приёмы в универсальном анализаторе.

Эти базовые навыки помогают создавать автоматизацию, которую проще читать, безопаснее запускать повторно и удобнее проверять другим командам или планировщикам заданий.