Возвращаемые значения функций Bash

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

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

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

Создание своей первой функции Bash

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

Создание базовой функции

Давайте создадим нашу первую функцию Bash. Откройте окно терминала и введите:

cd ~/project
mkdir -p bash_functions
cd bash_functions
create directory

Теперь давайте создадим простую функцию в новом скрипте. Создайте файл с именем first_function.sh с помощью nano:

touch first_function.sh

Добавьте следующее содержимое в файл:

#!/bin/bash

## Define a simple greeting function
say_hello() {
  echo "Hello, world!"
}

## Call the function
say_hello

## Define a function that accepts arguments
greet_person() {
  echo "Hello, $1!"
}

## Call the function with an argument
greet_person "Alice"
greet_person "Bob"

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

chmod +x first_function.sh

Теперь запустите скрипт:

./first_function.sh

Вы должны увидеть следующий вывод:

Hello, world!
Hello, Alice!
Hello, Bob!

Пояснение синтаксиса функции

В Bash существует два способа определения функций:

  1. Использование стандартного синтаксиса:
function_name() {
  ## Commands
}
  1. Использование ключевого слова function:
function function_name {
  ## Commands
}

Оба стиля работают одинаково, но первый более распространен и соответствует стандарту POSIX.

Доступ к аргументам функции

Внутри функции вы можете получить доступ к аргументам, переданным в функцию, с помощью позиционных параметров:

  • $1, $2, $3 и т.д. относятся к первому, второму, третьему аргументу и так далее
  • $0 относится к имени функции или имени скрипта
  • $# возвращает количество аргументов
  • $@ содержит все аргументы в виде отдельных строк
  • $* содержит все аргументы в виде одной строки

Давайте создадим новый файл, чтобы потренироваться с аргументами функции:

touch function_args.sh

Добавьте следующее содержимое:

#!/bin/bash

show_args() {
  echo "Function name: $0"
  echo "First argument: $1"
  echo "Second argument: $2"
  echo "Number of arguments: $#"
  echo "All arguments: $@"
}

echo "Calling function with three arguments:"
show_args apple banana cherry

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

chmod +x function_args.sh
./function_args.sh

Вы должны увидеть вывод, похожий на следующий:

Calling function with three arguments:
Function name: ./function_args.sh
First argument: apple
Second argument: banana
Number of arguments: 3
All arguments: apple banana cherry

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

Понимание возвращаемых кодов функций

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

Базовые коды возврата

В Bash:

  • Код возврата 0 означает успешное выполнение
  • Любое ненулевое значение (от 1 до 255) означает ошибку или аномальную ситуацию

Давайте создадим скрипт, чтобы продемонстрировать это:

cd ~/project/bash_functions
touch return_codes.sh

Добавьте следующее содержимое:

#!/bin/bash

## Function that always succeeds
succeed() {
  echo "This function succeeds"
  return 0
}

## Function that always fails
fail() {
  echo "This function fails"
  return 1
}

## Call the functions and check their return codes
succeed
echo "Return code of succeed: $?"

fail
echo "Return code of fail: $?"

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

chmod +x return_codes.sh
./return_codes.sh

Вы должны увидеть следующее:

This function succeeds
Return code of succeed: 0
This function fails
Return code of fail: 1

Захват кодов возврата

Специальная переменная $? содержит код возврата последней выполненной команды или функции. Это значение имеет важное значение для условного выполнения и обработки ошибок.

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

touch check_file.sh

Добавьте следующее содержимое:

#!/bin/bash

## Function to check if a file exists
file_exists() {
  local filename="$1"

  if [ -f "$filename" ]; then
    echo "File $filename exists"
    return 0
  else
    echo "File $filename does not exist"
    return 1
  fi
}

## Test the function with files that exist and don't exist
file_exists "return_codes.sh"
if [ $? -eq 0 ]; then
  echo "Great! The file was found."
else
  echo "Too bad. The file was not found."
fi

echo ""

file_exists "non_existent_file.txt"
if [ $? -eq 0 ]; then
  echo "Great! The file was found."
else
  echo "Too bad. The file was not found."
fi

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

chmod +x check_file.sh
./check_file.sh

Вы должны увидеть вывод, похожий на следующий:

File return_codes.sh exists
Great! The file was found.

File non_existent_file.txt does not exist
Too bad. The file was not found.

Использование кодов возврата в условных выражениях

Код возврата можно использовать напрямую в условных выражениях с помощью операторов && (логическое И) и || (логическое ИЛИ):

touch conditional_return.sh

Добавьте следующее содержимое:

#!/bin/bash

check_number() {
  local num=$1

  if [ $num -gt 10 ]; then
    return 0 ## Success if number is greater than 10
  else
    return 1 ## Failure if number is not greater than 10
  fi
}

## Using conditional operators with return codes
check_number 15 && echo "Number is greater than 10"
check_number 5 || echo "Number is not greater than 10"

## This line runs only if check_number succeeds
check_number 20 && {
  echo "Number is greater than 10"
  echo "Performing additional operations..."
}

## This line runs only if check_number fails
check_number 3 || {
  echo "Number is not greater than 10"
  echo "Taking alternative actions..."
}

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

chmod +x conditional_return.sh
./conditional_return.sh

Вывод должен быть следующим:

Number is greater than 10
Number is not greater than 10
Number is greater than 10
Performing additional operations...
Number is not greater than 10
Taking alternative actions...

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

Работа с пользовательскими возвращаемыми значениями

Хотя коды возврата полезны для указания успешного или неудачного выполнения, они ограничены числами от 0 до 255. Чтобы возвращать реальные данные из функций, нам нужно использовать другие методы.

Метод 1: Использование echo для возврата значений

Самым распространенным способом возврата реальных значений из функций является использование echo или других команд вывода, а затем захват этого вывода.

Давайте создадим скрипт, чтобы продемонстрировать этот метод:

cd ~/project/bash_functions
touch return_values.sh

Добавьте следующее содержимое:

#!/bin/bash

## Function that returns a value using echo
get_username() {
  echo "labex"
}

## Function that returns a calculated value
add_numbers() {
  local sum=$(($1 + $2))
  echo $sum
}

## Capture the returned values
username=$(get_username)
echo "The username is: $username"

result=$(add_numbers 5 7)
echo "The sum of 5 and 7 is: $result"

## You can also use the returned value directly
echo "Calculating again: $(add_numbers 10 20)"

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

chmod +x return_values.sh
./return_values.sh

Вы должны увидеть следующее:

The username is: labex
The sum of 5 and 7 is: 12
Calculating again: 30

Метод 2: Использование глобальных переменных

Другой подход - это изменение глобальных переменных внутри функции:

touch global_return.sh

Добавьте следующее содержимое:

#!/bin/bash

## Declare global variables
FULL_NAME=""
USER_AGE=0

## Function that sets global variables
set_user_info() {
  FULL_NAME="$1 $2"
  USER_AGE=$3

  ## Return success
  return 0
}

## Call the function
set_user_info "John" "Doe" 30

## Use the global variables that were set by the function
echo "Full name: $FULL_NAME"
echo "Age: $USER_AGE"

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

chmod +x global_return.sh
./global_return.sh

Вывод:

Full name: John Doe
Age: 30

Метод 3: Возврат нескольких значений

Давайте рассмотрим, как вернуть несколько значений из функции:

touch multiple_returns.sh

Добавьте следующее содержимое:

#!/bin/bash

## Function that returns multiple values separated by a delimiter
get_system_info() {
  local hostname=$(hostname)
  local kernel=$(uname -r)
  local uptime=$(uptime -p)

  ## Return multiple values separated by semicolons
  echo "$hostname;$kernel;$uptime"
}

## Capture the output and split it
system_info=$(get_system_info)

## Split the values using IFS (Internal Field Separator)
IFS=';' read -r host kernel up <<< "$system_info"

## Display the values
echo "Hostname: $host"
echo "Kernel version: $kernel"
echo "Uptime: $up"

## Alternative method using an array
get_user_details() {
  local details=("John Doe" "[email protected]" "Developer")
  printf "%s\n" "${details[@]}"
}

## Capture the output into an array
mapfile -t user_details < <(get_user_details)

echo ""
echo "User information:"
echo "Name: ${user_details[0]}"
echo "Email: ${user_details[1]}"
echo "Role: ${user_details[2]}"

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

chmod +x multiple_returns.sh
./multiple_returns.sh

Вывод должен показать информацию о вашей системе, за которой следуют данные о пользователе:

Hostname: ubuntu
Kernel version: 5.15.0-1033-azure
Uptime: up 2 hours, 15 minutes

User information:
Name: John Doe
Email: [email protected]
Role: Developer

Фактическое имя хоста, версия ядра и время работы системы будут различаться в зависимости от вашей системы.

Эти методы демонстрируют различные способы возврата значений из функций помимо простых кодов возврата. Каждый подход имеет свои преимущества в зависимости от ваших конкретных потребностей.

Практическое применение функций в скрипте

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

Создание утилиты для управления файлами

Давайте создадим полноценный скрипт, который выполняет различные операции с файлами:

cd ~/project/bash_functions
touch file_manager.sh

Добавьте следующее содержимое:

#!/bin/bash

## Function to check if a file exists
## Returns 0 if file exists, 1 if it doesn't
file_exists() {
  if [ -f "$1" ]; then
    return 0
  else
    return 1
  fi
}

## Function to get file size in bytes
## Returns the size via echo
get_file_size() {
  if file_exists "$1"; then
    ## Use stat to get file size in bytes
    local size=$(stat -c %s "$1")
    echo "$size"
  else
    echo "0"
  fi
}

## Function to count lines in a file
## Returns line count via echo
count_lines() {
  if file_exists "$1"; then
    local lines=$(wc -l < "$1")
    echo "$lines"
  else
    echo "0"
  fi
}

## Function to get file information
## Returns multiple values using a delimiter
get_file_info() {
  local filename="$1"

  if file_exists "$filename"; then
    local size=$(get_file_size "$filename")
    local lines=$(count_lines "$filename")
    local modified=$(stat -c %y "$filename")
    local permissions=$(stat -c %A "$filename")

    ## Return all info with semicolons as delimiters
    echo "$size;$lines;$modified;$permissions"
  else
    echo "0;0;N/A;N/A"
  fi
}

## Function to create a test file
create_test_file() {
  local filename="$1"
  local lines="$2"

  ## Create or overwrite the file
  > "$filename"

  ## Add the specified number of lines
  for ((i = 1; i <= lines; i++)); do
    echo "This is line $i of the test file." >> "$filename"
  done

  ## Return success if file was created
  if file_exists "$filename"; then
    return 0
  else
    return 1
  fi
}

## Main script execution starts here
echo "File Management Utility"
echo "----------------------"

## Create a test file
TEST_FILE="sample.txt"
echo "Creating test file with 10 lines..."
if create_test_file "$TEST_FILE" 10; then
  echo "File created successfully."
else
  echo "Failed to create file."
  exit 1
fi

## Check if file exists
echo ""
echo "Checking if file exists..."
if file_exists "$TEST_FILE"; then
  echo "File '$TEST_FILE' exists."
else
  echo "File '$TEST_FILE' does not exist."
fi

## Get file size
echo ""
echo "Getting file size..."
size=$(get_file_size "$TEST_FILE")
echo "File size: $size bytes"

## Count lines
echo ""
echo "Counting lines in file..."
lines=$(count_lines "$TEST_FILE")
echo "Line count: $lines"

## Get all file information
echo ""
echo "Getting complete file information..."
file_info=$(get_file_info "$TEST_FILE")

## Split the returned values
IFS=';' read -r size lines modified permissions <<< "$file_info"

echo "File: $TEST_FILE"
echo "Size: $size bytes"
echo "Lines: $lines"
echo "Last modified: $modified"
echo "Permissions: $permissions"

echo ""
echo "File content preview:"
head -n 3 "$TEST_FILE"
echo "..."

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

chmod +x file_manager.sh
./file_manager.sh

Вы должны увидеть вывод, похожий на следующий:

File Management Utility
----------------------
Creating test file with 10 lines...
File created successfully.

Checking if file exists...
File 'sample.txt' exists.

Getting file size...
File size: 300 bytes

Counting lines in file...
Line count: 10

Getting complete file information...
File: sample.txt
Size: 300 bytes
Lines: 10
Last modified: 2023-11-04 12:34:56.789012345 +0000
Permissions: -rwxrwxr-x

File content preview:
This is line 1 of the test file.
This is line 2 of the test file.
This is line 3 of the test file.
...

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

Анализ скрипта

Наша утилита для управления файлами демонстрирует несколько ключевых концепций:

  1. Коды возврата - Функции file_exists() и create_test_file() возвращают 0 при успешном выполнении и 1 при ошибке.
  2. Возврат значений с помощью echo - Функции get_file_size() и count_lines() возвращают числовые значения с помощью echo.
  3. Возврат нескольких значений - Функция get_file_info() возвращает несколько значений, используя разделитель.
  4. Композиция функций - Некоторые функции вызывают другие функции, демонстрируя, как создавать сложную функциональность.
  5. Условное выполнение - Скрипт использует операторы if с кодами возврата для управления потоком программы.

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

Обработка ошибок и рекомендации по использованию функций

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

Создание скрипта с обработкой ошибок

Давайте создадим новый скрипт, который демонстрирует надежную обработку ошибок:

cd ~/project/bash_functions
touch error_handling.sh

Добавьте следующее содержимое:

#!/bin/bash

## Enable error handling
set -e ## Exit immediately if a command exits with non-zero status

## Define a function to log messages
log_message() {
  local level="$1"
  local message="$2"
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message"
}

## Function to validate a number is positive
validate_positive() {
  local num="$1"
  local name="$2"

  ## Check if the argument is a number
  if ! [[ "$num" =~ ^[0-9]+$ ]]; then
    log_message "ERROR" "$name must be a number"
    return 1
  fi

  ## Check if the number is positive
  if [ "$num" -le 0 ]; then
    log_message "ERROR" "$name must be positive"
    return 2
  fi

  return 0
}

## Function that divides two numbers
divide() {
  local numerator="$1"
  local denominator="$2"

  ## Validate inputs
  validate_positive "$numerator" "Numerator" || return $?
  validate_positive "$denominator" "Denominator" || return $?

  ## Check for division by zero
  if [ "$denominator" -eq 0 ]; then
    log_message "ERROR" "Division by zero is not allowed"
    return 3
  fi

  ## Perform division
  local result=$(echo "scale=2; $numerator / $denominator" | bc)
  echo "$result"
  return 0
}

## Function to safely get user input
get_number() {
  local prompt="$1"
  local input

  while true; do
    read -p "$prompt: " input

    if validate_positive "$input" "Input"; then
      echo "$input"
      return 0
    else
      log_message "WARN" "Invalid input. Please try again."
    fi
  done
}

## Disable automatic exit on error for the main script
set +e

## Main script logic
log_message "INFO" "Starting division calculator"

## Test with valid values
result=$(divide 10 2)
exit_code=$?

if [ $exit_code -eq 0 ]; then
  log_message "INFO" "10 / 2 = $result"
else
  log_message "ERROR" "Division failed with code $exit_code"
fi

## Test with invalid values
echo ""
log_message "INFO" "Testing with invalid values"
divide 0 5
log_message "INFO" "Exit code: $?"

divide 10 0
log_message "INFO" "Exit code: $?"

divide abc 5
log_message "INFO" "Exit code: $?"

## Interactive mode
echo ""
log_message "INFO" "Interactive mode"
echo "Let's perform a division. Enter positive numbers."

## Get user input safely
num1=$(get_number "Enter first number")
num2=$(get_number "Enter second number")

## Perform division
result=$(divide "$num1" "$num2")
exit_code=$?

if [ $exit_code -eq 0 ]; then
  log_message "INFO" "$num1 / $num2 = $result"
else
  log_message "ERROR" "Division failed with code $exit_code"
fi

log_message "INFO" "Calculator finished"

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

chmod +x error_handling.sh
./error_handling.sh

Вы увидите вывод, похожий на следующий, и будете приглашены ввести числа:

[2023-11-04 13:45:23] [INFO] Starting division calculator
[2023-11-04 13:45:23] [INFO] 10 / 2 = 5.00

[2023-11-04 13:45:23] [INFO] Testing with invalid values
[2023-11-04 13:45:23] [ERROR] Numerator must be positive
[2023-11-04 13:45:23] [INFO] Exit code: 2
[2023-11-04 13:45:23] [ERROR] Division by zero is not allowed
[2023-11-04 13:45:23] [INFO] Exit code: 3
[2023-11-04 13:45:23] [ERROR] Numerator must be a number
[2023-11-04 13:45:23] [INFO] Exit code: 1

[2023-11-04 13:45:23] [INFO] Interactive mode
Let's perform a division. Enter positive numbers.
Enter first number:

Введите число, например 20. Затем вас попросят ввести второе число:

Enter second number:

Введите еще одно число, например 4, и вы должны увидеть:

[2023-11-04 13:45:30] [INFO] 20 / 4 = 5.00
[2023-11-04 13:45:30] [INFO] Calculator finished

Рекомендации по использованию функций в Bash

На основе наших примеров, вот несколько рекомендаций по использованию функций в Bash:

  1. Добавляйте описательные комментарии - Документируйте, что делает каждая функция, какие у нее параметры и возвращаемые значения.
  2. Используйте осмысленные имена функций - Выбирайте имена, которые ясно указывают на назначение функции.
  3. Проверяйте входные параметры - Проверяйте входные данные, чтобы предотвратить ошибки.
  4. Используйте локальные переменные - Предотвращайте конфликты имен переменных с помощью ключевого слова local.
  5. Возвращайте соответствующие коды завершения - Используйте стандартные коды возврата (0 для успешного выполнения, ненулевые значения для ошибок).
  6. Реализуйте правильную обработку ошибок - Логируйте ошибки и обрабатывайте их корректно.
  7. Сосредотачивайтесь на одной задаче в каждой функции - Каждая функция должна хорошо выполнять одну задачу.
  8. Используйте композицию функций - Создавайте сложную функциональность, комбинируя более простые функции.
  9. Документируйте возвращаемые значения - Ясно укажите, как возвращаются значения (через echo, код возврата и т.д.).
  10. Тестируйте крайние случаи - Убедитесь, что функции корректно обрабатывают необычные входные данные.

Следуя этим рекомендациям, вы можете создать более надежные, поддерживаемые и повторно используемые функции в Bash.

Создание библиотеки функций

В качестве последнего упражнения давайте создадим повторно используемую библиотеку функций:

touch math_functions.lib

Добавьте следующее содержимое:

#!/bin/bash
## math_functions.lib - A library of mathematical functions

## Add two numbers
add() {
  echo $(($1 + $2))
}

## Subtract second number from first
subtract() {
  echo $(($1 - $2))
}

## Multiply two numbers
multiply() {
  echo $(($1 * $2))
}

## Divide first number by second (with decimal precision)
divide() {
  if [ "$2" -eq 0 ]; then
    return 1
  fi
  echo "scale=2; $1 / $2" | bc
  return 0
}

## Calculate power: first number raised to second number
power() {
  echo $(($1 ** $2))
}

## Check if a number is even
is_even() {
  if (($1 % 2 == 0)); then
    return 0
  else
    return 1
  fi
}

## Check if a number is odd
is_odd() {
  if is_even "$1"; then
    return 1
  else
    return 0
  fi
}

Теперь создайте скрипт, который использует эту библиотеку:

touch use_library.sh

Добавьте следующее содержимое:

#!/bin/bash

## Source the math functions library
source math_functions.lib

## Display a header
echo "Math Functions Demo"
echo "------------------"

## Test the functions
echo "Addition: 5 + 3 = $(add 5 3)"
echo "Subtraction: 10 - 4 = $(subtract 10 4)"
echo "Multiplication: 6 * 7 = $(multiply 6 7)"

## Test division with error handling
div_result=$(divide 20 5)
if [ $? -eq 0 ]; then
  echo "Division: 20 / 5 = $div_result"
else
  echo "Division error: Cannot divide by zero"
fi

## Test division by zero
div_result=$(divide 20 0)
if [ $? -eq 0 ]; then
  echo "Division: 20 / 0 = $div_result"
else
  echo "Division error: Cannot divide by zero"
fi

echo "Power: 2 ^ 8 = $(power 2 8)"

## Test the even/odd functions
echo ""
echo "Number properties:"
for num in 1 2 3 4 5; do
  echo -n "Number $num is "

  if is_even $num; then
    echo "even"
  else
    echo "odd"
  fi
done

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

chmod +x use_library.sh
./use_library.sh

Вы должны увидеть:

Math Functions Demo
------------------
Addition: 5 + 3 = 8
Subtraction: 10 - 4 = 6
Multiplication: 6 * 7 = 42
Division: 20 / 5 = 4.00
Division error: Cannot divide by zero
Power: 2 ^ 8 = 256

Number properties:
Number 1 is odd
Number 2 is even
Number 3 is odd
Number 4 is even
Number 5 is odd

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

Резюме

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

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

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