Перейти к основному содержанию
Рецепты Linux

Main navigation

  • Основы
  • Система
  • Команды
  • Программы
  • Дистро
  • Интерфейсы
  • Устройства
  • Доки
User account menu
  • Войти

Строка навигации

  1. Главная
  2. ABS Guide
  3. Часть 4. Материал повышенной сложности
  4. Глава 22. Функции

22.1. Сложные функции и сложности с функциями

Функции могут принимать входные аргументы и возвращать код завершения.

function_name $arg1 $arg2

Доступ к входным аргументам, в функциях, производится посредством позиционных параметров, т.е. $1, $2 и так далее.

Пример 22-2. Функция с аргументами

#!/bin/bash
# Функции и аргументы

DEFAULT=default # Значение аргумента по-умолчанию.

func2 () {
 if [ -z "$1" ] # Длина аргумента #1 равна нулю?
 then
 echo "-Аргумент #1 имеет нулевую длину.-" # Или аргумент не был передан функции.
 else
 echo "-Аргумент #1: \"$1\".-"
 fi

 variable=${1-$DEFAULT} # Что делает
 echo "variable = $variable" #+ показанная подстановка параметра?
 # ---------------------------
 # Она различает отсутствующий аргумент
 #+ от "пустого" аргумента.

 if [ "$2" ]
 then
 echo "-Аргумент #2: \"$2\".-"
 fi

 return 0
}

echo

echo "Вызов функции без аргументов."
func2
echo


echo "Вызов функции с \"пустым\" аргументом."
func2 ""
echo

echo "Вызов функции с неинициализированным аргументом."
func2 "$uninitialized_param"
echo

echo "Вызов функции с одним аргументом."
func2 first
echo

echo "Вызов функции с двумя аргументами."
func2 first second
echo

echo "Вызов функции с аргументами \"\" \"second\"."
func2 "" second # Первый параметр "пустой"
echo # и второй параметр -- ASCII-строка.

exit 0
Important

Команда shift вполне применима и к аргументам функций (см. Пример 33-11).

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

Механизм косвенных ссылок на переменные (см. Пример 34-2) слишком неудобен для передачи аргументов по ссылке.

Пример 22-3. Передача косвенных ссылок в функцию

#!/bin/bash
# ind-func.sh: Передача косвенных ссылок в функцию.

echo_var ()
{
echo "$1"
}

message=Hello
Hello=Goodbye

echo_var "$message" # Hello
# А теперь передадим функции косвенную ссылку.
echo_var "${!message}" # Goodbye

echo "-------------"

# Что произойдет, если изменить содержимое переменной "Hello"?
Hello="Hello, again!"
echo_var "$message" # Hello
echo_var "${!message}" # Hello, again!

exit 0

Тут же возникает вопрос: "Возможно ли изменить значение переменной, которая была передана по ссылке?"

Пример 22-4. Изменение значения переменной, переданной в функцию по ссылке.

#!/bin/bash
# dereference.sh
# Изменение значения переменной, переданной в функцию по ссылке.
# Автор: Bruce W. Clare.

dereference ()
{
 y=\$"$1" # Имя переменной.
 echo $y # $Junk

 x=`eval "expr \"$y\" "`
 echo $1=$x
 eval "$1=\"Некий другой текст \"" # Присвоить новое значение.
}

Junk="Некий текст"
echo $Junk "до того как..." # Некий текст до того как...

dereference Junk
echo $Junk "после того как..." # Некий другой текст после того как...

exit 0

Пример 22-5. Еще один пример разыменования параметров функции, передаваемых по ссылке.

#!/bin/bash

ITERATIONS=3 # Количество вводимых значений.
icount=1

my_read () {
 # При вызове my_read varname,
 # выводит предыдущее значение в квадратных скобках,
 # затем просит ввести новое значение.

 local local_var

 echo -n "Введите говое значение переменной "
 eval 'echo -n "[$'$1'] "' # Прежнее значение.
 read local_var
 [ -n "$local_var" ] && eval $1=\$local_var

 # Последовательность "And-list": если "local_var" не пуста, то ее значение переписывается в "$1".
}

echo

while [ "$icount" -le "$ITERATIONS" ]
do
 my_read var
 echo "Значение #$icount = $var"
 let "icount += 1"
 echo
done


# Спасибо Stephane Chazelas за этот поучительный пример.

exit 0

Exit и Return

код завершения

Функции возвращают значение в виде кода завершения. Код завершения может быть задан явно, с помощью команды return, в противном случае будет возвращен код завершения последней команды в функции (0 -- в случае успеха, иначе -- ненулевой код ошибки). Код завершения в сценарии может быть получен через переменную $?.

return

Завершает исполнение функции. Команда return[51] может иметь необязательный аргумент типа integer, который возвращается в вызывающий сценарий как "код завершения" функции, это значение так же записывается в переменную $?.

Пример 22-6. Наибольшее из двух чисел

#!/bin/bash
# max.sh: Наибольшее из двух целых чисел.

E_PARAM_ERR=-198 # Если функции передано меньше двух параметров.
EQUAL=-199 # Возвращаемое значение, если числа равны.

max2 () # Возвращает наибольшее из двух чисел.
{ # Внимание: сравниваемые числа должны быть меньше 257.
if [ -z "$2" ]
then
 return $E_PARAM_ERR
fi

if [ "$1" -eq "$2" ]
then
 return $EQUAL
else
 if [ "$1" -gt "$2" ]
 then
 return $1
 else
 return $2
 fi
fi
}

max2 33 34
return_val=$?

if [ "$return_val" -eq $E_PARAM_ERR ]
then
 echo "Функции должно быть передано два аргумента."
elif [ "$return_val" -eq $EQUAL ]
 then
 echo "Числа равны."
else
 echo "Наибольшее из двух чисел: $return_val."
fi


exit 0

# Упражнение:
# ---------------
# Сделайте этот сценарий интерактивным,
#+ т.е. заставьте сценарий запрашивать числа для сравнения у пользователя (два числа).
Tip

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

count_lines_in_etc_passwd()
{
 [[ -r /etc/passwd ]] && REPLY=$(echo $(wc -l  /etc/passwd))
 # Если файл /etc/passwd доступен на чтение, то в переменную REPLY заносится число строк.
 # Возвращаются как количество строк, так и код завершения.
 # Команда 'echo' может показаться ненужной, но . . .
 #+ она предотвращает вывод лишних пробелов.
}

if count_lines_in_etc_passwd
then
 echo "В файле /etc/passwd найдено $REPLY строк."
else
 echo "Невозможно подсчитать число строк в файле /etc/passwd."
fi

# Спасибо S.C.

Пример 22-7. Преобразование чисел в римскую форму записи

#!/bin/bash

# Преобразование чисел из арабской формы записи в римскую
# Диапазон: 0 - 200

# Расширение диапазона представляемых чисел и улучшение сценария
# оставляю вам, в качестве упражнения.

# Порядок использования: roman number-to-convert

LIMIT=200
E_ARG_ERR=65
E_OUT_OF_RANGE=66

if [ -z "$1" ]
then
 echo "Порядок использования: `basename $0` number-to-convert"
 exit $E_ARG_ERR
fi

num=$1
if [ "$num" -gt $LIMIT ]
then
 echo "Выход за границы диапазона!"
 exit $E_OUT_OF_RANGE
fi

to_roman () # Функция должна быть объявлена до того как она будет вызвана.
{
number=$1
factor=$2
rchar=$3
let "remainder = number - factor"
while [ "$remainder" -ge 0 ]
do
 echo -n $rchar
 let "number -= factor"
 let "remainder = number - factor"
done

return $number
 # Упражнение:
 # --------
 # Объясните -- как работает функция.
 # Подсказка: деление последовательным вычитанием.
}


to_roman $num 100 C
num=$?
to_roman $num 90 LXXXX
num=$?
to_roman $num 50 L
num=$?
to_roman $num 40 XL
num=$?
to_roman $num 10 X
num=$?
to_roman $num 9 IX
num=$?
to_roman $num 5 V
num=$?
to_roman $num 4 IV
num=$?
to_roman $num 1 I

echo

exit 0

См. также Пример 10-28.

Important

Наибольшее положительное целое число, которое может вернуть функция -- 255. Команда return очень тесно связана с понятием код завершения, что объясняет это специфическое ограничение. К счастью существуют различные способы преодоления этого ограничения.

Пример 22-8. Проверка возможности возврата функциями больших значений

#!/bin/bash
# return-test.sh

# Наибольшее целое число, которое может вернуть функция, не может превышать 256.

return_test () # Просто возвращает то, что ей передали.
{
 return $1
}

return_test 27 # o.k.
echo $? # Возвращено число 27.

return_test 255 # o.k.
echo $? # Возвращено число 255.

return_test 257 # Ошибка!
echo $? # Возвращено число 1.

return_test -151896 # Как бы то ни было, но для больших отрицательных чисел проходит!
echo $? # Возвращено число -151896.

exit 0

Самый простой способ вернуть из функции большое положительное число -- это присвоить "возвращаемое значение" глобальной переменной.

Return_Val= # Глобальная переменная, которая хранит значение, возвращаемое функцией.

alt_return_test ()
{
 fvar=$1
 Return_Val=$fvar
 return # Возвратить 0 (успешное завершение).
}

alt_return_test 1
echo $? # 0
echo "Функция вернула число $Return_Val" # 1

alt_return_test 255
echo "Функция вернула число $Return_Val" # 255

alt_return_test 257
echo "Функция вернула число $Return_Val" # 257

alt_return_test 25701
echo "Функция вернула число $Return_Val" #25701

Еще более элегантный способ заключается в передаче возвращаемого значания команде echo, для вывода на stdout, которое затем снимается со стандартного вывода конструкцией подстановки команд. См. обсуждение этого приема в Section 33.7.

Пример 22-9. Сравнение двух больших целых чисел

#!/bin/bash
# max2.sh: Наибольшее из двух БОЛЬШИХ целых чисел.

# Это модификация предыдущего примера "max.sh",
# которая позволяет выполнять сравнение больших целых чисел.

EQUAL=0 # Если числа равны.
MAXRETVAL=255 # Максимально возможное положительное число, которое может вернуть функция.
E_PARAM_ERR=-99999 # Код ошибки в параметрах.
E_NPARAM_ERR=99999 # "Нормализованный" код ошибки в параметрах.

max2 () # Возвращает наибольшее из двух больших целых чисел.
{
if [ -z "$2" ]
then
 return $E_PARAM_ERR
fi

if [ "$1" -eq "$2" ]
then
 return $EQUAL
else
 if [ "$1" -gt "$2" ]
 then
 retval=$1
 else
 retval=$2
 fi
fi

# -------------------------------------------------------------- #
# Следующие строки позволяют "обойти" ограничение
if [ "$retval" -gt "$MAXRETVAL" ] # Если больше предельного значения,
then # то
 let "retval = (( 0 - $retval ))" # изменение знака числа.
 # (( 0 - $VALUE )) изменяет знак числа.
fi
# Функции имеют возможность возвращать большие *отрицательные* числа.
# -------------------------------------------------------------- #

return $retval
}

max2 33001 33997
return_val=$?

# -------------------------------------------------------------------------- #
if [ "$return_val" -lt 0 ] # Если число отрицательное,
then # то
 let "return_val = (( 0 - $return_val ))" # опять изменить его знак.
fi # "Абсолютное значение" переменной $return_val.
# -------------------------------------------------------------------------- #
 
 
if [ "$return_val" -eq "$E_NPARAM_ERR" ]
then # Признак ошибки в параметрах, при выходе из функции так же поменял знак.
 echo "Ошибка: Недостаточно аргументов."
elif [ "$return_val" -eq "$EQUAL" ]
 then
 echo "Числа равны."
else
 echo "Наиболшее число: $return_val."
fi
	 
exit 0

См. также Пример A-8.

Упражнение: Используя только что полученные знания, добавьте в предыдущий пример, преобразования чисел в римскую форму записи, возможность обрабатывать большие числа.

Перенаправление

Перенаправление ввода для функций

Функции -- суть есть блок кода, а это означает, что устройство stdin для функций может быть переопределено (перенаправление stdin) (как в Пример 3-1).

Пример 22-10. Настоящее имя пользователя

#!/bin/bash

# По имени пользователя получить его "настоящее имя" из /etc/passwd.

ARGCOUNT=1 # Ожидается один аргумент.
E_WRONGARGS=65

file=/etc/passwd
pattern=$1

if [ $# -ne "$ARGCOUNT" ]
then
 echo "Порядок использования: `basename $0` USERNAME"
 exit $E_WRONGARGS
fi

file_excerpt () # Производит поиск в файле по заданному шаблону, выводит требуемую часть строки.
{
while read line
do
 echo "$line" | grep $1 | awk -F":" '{ print $5 }' # Указывет awk использовать ":" как разделитель полей.
done
} $file # Подменить stdin для функции.

file_excerpt $pattern

# Да, этот сценарий можно уменьшить до
# grep PATTERN /etc/passwd | awk -F":" '{ print $5 }'
# или
# awk -F: '/PATTERN/ {print $5}'
# или
# awk -F: '($1 == "username") { print $5 }'
# Однако, это было бы не так поучительно.

exit 0

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

# Вместо:
Function ()
{
 ...
 }  file

# Попробуйте так:
Function ()
{
 {
 ...
 }  file
}

# Похожий вариант,

Function () # Тоже работает.
{
 {
 echo $*
 } | tr a b
}

Function () # Этот вариант не работает.
{
 echo $*
} | tr a b # Наличие вложенного блока кода -- обязательное условие.


# Спасибо S.C.

Notes

[51]

Команда return -- это встроенная команда Bash.

Перекрёстные ссылки книги для 22.1. Сложные функции и сложности с функциями

  • Глава 22. Функции
  • Вверх
  • 22.2. Локальные переменные

Book navigation

  • Содержание
  • Часть 1. Введение
  • Часть 2. Основы
  • Часть 3. Углубленный материал
  • Часть 4. Материал повышенной сложности
    • Глава 18. Регулярные выражения
    • Глава 19. Подоболочки, или Subshells
    • Глава 20. Ограниченный режим командной оболочки
    • Глава 21. Подстановка процессов
    • Глава 22. Функции
      • 22.1. Сложные функции и сложности с функциями
      • 22.2. Локальные переменные
      • 22.3. Рекурсия без локальных переменных
    • Глава 23. Псевдонимы
    • Глава 24. Списки команд
    • Глава 25. Массивы
    • Глава 26. Файлы
    • Глава 27. /dev и /proc
    • Глава 28. /dev/zero и /dev/null
    • Глава 29. Отладка сценариев
    • Глава 30. Необязательные параметры (ключи)
    • Глава 31. Широко распространенные ошибки
    • Глава 32. Стиль программирования
    • Глава 33. Разное
    • Глава 34. Bash, версия 2
  • Глава 35. Замечания и дополнения
  • Библиография
  • Приложение A. Дополнительные примеры сценариев
  • Приложение B. Справочная информация
  • Приложение C. Маленький учебник по Sed и Awk
  • Приложение D. Коды завершения, имеющие предопределенный смысл
  • Приложение E. Подробное введение в операции ввода-вывода и перенаправление ввода-вывода
  • Приложение F. Системные каталоги
  • Приложение G. Локализация
  • Приложение H. История команд
  • Приложение I. Пример файла .bashrc
  • Приложение J. Преобразование пакетных (*.bat) файлов DOS в сценарии командной оболочки
  • Приложение K. Упражнения
  • Приложение L. Хронология
  • Приложение M. Авторские права

Последние материалы

  • Приложение Zoom
    3 weeks ago
  • Команда restore
    1 month ago
  • Файл sudoers
    1 month 1 week ago
  • Утилита visudo
    1 month 1 week ago
  • Файловый менеджер Thunar
    1 month 2 weeks ago
RSS feed

Secondary menu

  • О проекте

© 2008–2025 Олег Меньшенин mensh@yandex.ru