Как вернуть значение из bash функции?
Как известно, в bash
все функции и команды возвращают лишь код возврата
(ноль - успешное завершение, не-ноль - ошибка).
Для того, чтобы вернуть нечто отличное от числа,
нужно использовать один из следующих приёмов:
- установка глобальной переменной
- использование подстановки (чтение вывода команды)
- передача косвенной ссылки в функцию
Рассмотрим подробнее, как же использовать данные подходы.
Установка глобальной переменной
Что может быть проще, чем загадить глобальную область видимости в функции, и затем считать из неё данные в основной программе?
function makedirty()
{
myresult='oh, sic...!'
}
makedirty
echo $myresult # oh, sic...!
Оно и понятно, ведь все переменные в bash
по-умолчанию - глобальные.
+ Элементарно реализуется
+ Возможность вернуть несколько значений
- В больших программах может привести к трудноуловимым ошибкам
- т.к. кто-то случайно где-то может затереть Вашу переменную
Иcпользование подстановки, aka $(function)
Довольно распространённый механизм. Вы уже, наверняка, сами не зная того, неоднократно им пользовались. Идея в том, что мы сохраняем в переменную всё, что выводит некая команда (функция -- тоже команда). Ну и конечно, что бы без необходимости не засорять глобальную область видимости, рекомендуется использовать локальные переменные.
function makecoffe()
{
local myresult='done done done!'
echo "$myresult"
}
result=$(makecoffe) # или result=`myfunc`
echo $result # done done done!
+ Не засоряется глобальная область видимости
- Можно вернуть лишь 1 значение (или несколько разделённых неким разделителем)
Передача косвенной ссылки (Indirect References) в функцию
Самый интересный, функциональный и неуклюжий механизм :)
Это проще 1 раз увидеть, чем 100 раз услышать:
#!/bin/bash
# return-val-func-3.sh: Передача косвенной ссылки в функцию.
echo_var ()
{
echo "$1"
}
message=Hello
Hello=Goodbye
echo_var "$message" # Hello
# Теперь давайте передадим в функцию косвенную ссылку.
# Т.е. дважды разыменуем переменную message
echo_var "${!message}" # Goodbye
echo "-------------"
# И что же произойдёт, если мы поменяем содержимое переменной "Hello" ?
Hello="Hello, again!"
echo_var "$message" # Hello
echo_var "${!message}" # Hello, again!
exit 0
А теперь по-честному и по порядку
В отличии от некоторых других языков программирования, в bash
сценариях параметры в функцию обычно передаются по значению.
Если имена переменных (которые фактически являются указателями)
будут переданы в функцию в качестве параметров, то они будут обработаны,
как строковые литералы.
Функции обрабатывают свои аргументы, как литералы.
Переменные - это способ представления данных в различных языках программирования. Переменная не более чем метка - имя, назначенное области или группе областей памяти компьютера, где содержатся данные.
Имя переменной - это "контейнер" для хранения значения данных. Обращение к значению (получение значения) переменной называется подстановкой переменной.
Отмечу, что надо различать имя переменной и ее значение. Если variable1 - имя переменной, то $variable1 - обращение к значению, которое она содержит.
Имя переменной - в действительности, ссылка/указатель на область(ти) памяти, где фактические данные связаны со значением, которое они хранят.
bash$ variable1=23
bash$ echo variable1
variable1
bash$ echo $variable1
23
Как сказано выше, после подстановки переменной (referencing a variable, $var
),
мы получаем значение, на которую указывает переменная.
А как насчёт значения, на которое укзывает само значение? $$var
?
Вы же помните, что с переменными мы работаем просто как с литералами? :)
Ну вот мы и пробуем получить значение, на которое указывает переменная,
имя которой тоже является значением другой переменной.
Немного запутанно, но, надеюсь, уловили.
Настоящая нотация выглядит как \$$var
(или, c версии bash 2
, как ${!variable}
)
и обычно используется вместе с eval
.
Данный механизм и называется косвенная ссылка (indirect reference).
Пример 1. Для понимания
#!/bin/bash
# dereference.sh
# Разыменовывание параметра переданного в функцию.
# Сценарий от Bruce W. Clare.
dereference ()
{
y=\$"$1" # Сохранили имя переменной
echo $y # $Junk == echo \$$y
x=`eval "expr \"$y\" "` # $x == Junk
echo $1=$x
eval "$1=\"Некоторый Другой Текст \"" # Присваиваем новое значение.
}
Junk="Некоторый Текст"
echo $Junk "до" # Некоторый Текст до
dereference Junk
echo $Junk "после" # Некоторый Другой Текст после
exit 0
Пример 2. Практичный
function myfunc()
{
local __resultvar=$1
local myresult='some value'
eval $__resultvar="'$myresult'"
}
myfunc result
echo $result
Так как имя переменной само хранится в переменной,
мы не можем установить его напрямую. Для этого используют eval
.
Если в кратце, eval
сообщает bash
-интерпретатору, что необходимо
дважды проинтерпретировать строку. В первый раз он установит result='some value'
,
а во второй - искомую переменную (которую передали как аргумент).
При сохранении имени переменной, которую передали как аргумент командной строки, убедитесь, что сохраняете её локально и в переменную с отличным (по возможности) именем, нежели использовалась в командной строке. Если этого не сделать, и имя переменной, переданной как параметр командной строки, совпадёт с именем локальной переменной, в которую Вы сохраняете значение, то ничего не будет работать :D
Пример 3. Не работающий!
function myfunc()
{
local result=$1
local myresult='some value'
eval $result="'$myresult'"
}
myfunc result
echo $result
Почему? Ну... в тот момент когда eval
совершает вторую подстановку в
result='some value'
, result
- уже локальная переменная функции, поэтому
мы установим её ещё разок, а не искомую переменную.
Для большей гибкости, можно комбинировать разные подходы:
Пример 4. Универсальный
function myfunc()
{
local __resultvar=$1
local myresult='some value'
if [[ "$__resultvar" ]]; then
eval $__resultvar="'$myresult'"
else
echo "$myresult"
fi
}
myfunc result
echo $result # some value
result2=$(myfunc)
echo $result2 # some value
Т.е. если в функцию не передано ни одного параметра, значение просто выводится в stdout
.
Использованные источники:
- Linux Journal: Returning Values from Bash Functions
- Advanced Bash Scripting по-русски 4. Введение в переменные и параметры, 9. Indirect References, 23. Функции