Область видимости переменных и статус возврата

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

Введение

При написании скриптов нужно управлять доступом к данным и тем, как функции сообщают об успешном или неудачном выполнении.

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

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

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

В этой лабораторной работе вы изучите:

  1. Область видимости переменных: использование local для предотвращения конфликтов переменных.
  2. Статус возврата: использование кодов завершения — 0 означает успех, ненулевое значение означает ошибку — для управления ходом выполнения скрипта.

Область видимости переменных: глобальные и локальные переменные

По умолчанию переменные в Bash являются глобальными. Это означает, что переменную, определённую внутри функции, можно использовать и изменить в любой части скрипта. Из-за этого могут возникать трудноуловимые ошибки: одна часть скрипта случайно изменяет данные, необходимые другой части.

Чтобы этого избежать, используйте ключевое слово local. Переменная, объявленная с помощью local, существует только внутри функции, в которой она создана.

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

Давайте рассмотрим разницу на примере.

Создайте скрипт с именем scope.sh:

cd ~/project
nano scope.sh

Добавьте следующий код:

#!/bin/bash

## This is a Global variable
city="New York"

print_city() {
  ## This is a Local variable. It shares the name 'city', but it is a separate variable.
  local city="London"
  echo "Inside function: $city"
}

echo "Before function call: $city"

## Run the function
print_city

## Check if the Global variable was changed
echo "After function call: $city"

Сохраните файл (Ctrl+O, затем Enter) и выйдите из редактора (Ctrl+X).

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

chmod +x scope.sh
./scope.sh

Результат:

Before function call: New York
Inside function: London
After function call: New York

Обратите внимание: переменная city за пределами функции сохранила значение «New York». Функция использовала собственную local-копию переменной city со значением «London» и не изменила глобальную переменную.

Если бы мы не использовали local, функция перезаписала бы глобальную переменную, и последняя строка вывела бы «London».

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

Коды статуса возврата

Во многих языках программирования функции возвращают данные, например строку или число. В Bash команда return используется строго для передачи кодов статуса, которые обозначают успех или ошибку.

  • 0: успех.
  • 1–255: ошибка.

Не путайте return и echo.

  • echo выводит текст на экран или в переменную, если вывод сохраняется.
  • return передаёт системе скрытое числовое значение, сообщая: «операция выполнена успешно» или «операция завершилась с ошибкой».

Это фундаментальное различие в программировании для оболочки. Текстовый вывод и сообщение о статусе — два отдельных канала. Функция может выводить понятную человеку информацию с помощью echo, но значение return предназначено для управления ходом выполнения, а не для передачи произвольных данных.

Создадим функцию, которая имитирует ошибку, возвращая код 5.

Создайте файл status.sh:

nano ~/project/status.sh

Добавьте следующий код:

#!/bin/bash

check_file() {
  echo "Checking for file..."
  ## We simulate a specific error by returning Code 5
  return 5
}

check_file

Сохраните файл и выйдите из редактора.

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

chmod +x status.sh
./status.sh

Вывод:

Checking for file...

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

Сохранение кода возврата ($?)

Чтобы получить код возврата последней выполненной команды или функции, Bash предоставляет специальную переменную: $?.

После полного выполнения check_file переменная $? автоматически будет содержать значение 5.

Ключевое правило: считывайте $? сразу после выполнения команды. Как только вы выполните другую команду, даже простой echo, переменная $? получит статус этой новой команды.

Изменим status.sh, чтобы сохранить код.

Откройте файл:

nano status.sh

Замените содержимое скрипта следующим кодом:

#!/bin/bash

check_file() {
  echo "Checking for file..."
  return 5
}

check_file

## Capture the return code immediately into a variable
ret_val=$?

echo "The function returned: $ret_val"

Сохраните файл и выйдите из редактора. Запустите скрипт снова:

./status.sh

Вывод:

Checking for file...
The function returned: 5

Теперь код ошибки сохранён в переменной ret_val.

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

Обработка статуса возврата

Теперь, когда у нас есть код статуса, мы можем использовать конструкцию if для корректной обработки ошибок.

  • Если код равен 0, всё в порядке.
  • Если код равен 5, мы точно знаем, что произошло, например отсутствует файл.

Так ведут себя стандартные команды Linux. Например, grep возвращает 0, если находит совпадение, и 1, если совпадение не найдено.

Измените status.sh, добавив логику обработки результата:

nano status.sh

Добавьте условную логику:

#!/bin/bash

check_file() {
  echo "Checking for file..."
  return 5
}

check_file
ret_val=$?

## Logic based on the return code
if [ $ret_val -eq 0 ]; then
  echo "Success: File found."
elif [ $ret_val -eq 5 ]; then
  echo "Error: File missing (Code 5)."
else
  echo "Unknown Error: Code $ret_val"
fi

Сохраните файл и выйдите из редактора. Запустите скрипт:

./status.sh

Вывод:

Checking for file...
Error: File missing (Code 5).

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

Итоги

В этой лабораторной работе вы изучили два важных понятия написания скриптов:

  1. Область видимости переменных (local): использование local не позволяет переменным внутри функций перезаписывать глобальные переменные и делает поведение скрипта предсказуемым.
  2. Статус возврата (return, $?): коды завершения позволяют функциям сообщать основному скрипту об успехе или ошибке. Благодаря этому скрипт может разумно обрабатывать ошибки.

Всегда используйте local для переменных внутри функций, если только вам специально не требуется изменить глобальную переменную.