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

305

Я работаю со скриптом bash и хочу выполнить функцию для вывода возвращаемого значения:

function fun1(){
  return 34
}
function fun2(){
  local res=$(fun1)
  echo $res
}

Когда я выполняю fun2, он не печатает «34». Почему это так?

Миндиа
источник
8
returnв вашем случае это по сути то же самое, exit codeчто и диапазон от 0 - 255. Используйте echoкак предложено @septi. Коды выхода могут быть записаны с $?.
devnull
1
В этом случае гораздо проще использовать echo в fun1. Это идея unix-программирования: echo отправляет результаты в стандартный вывод, который затем может быть повторно использован другими функциями с res = $ (fun1) - или напрямую передан другим функциям:function a() { echo 34; } function b() { while read data; do echo $data ; done ;} a | b
Arne Babenhauserheide
Правильный способ сделать это состоит в том, чтобы поместить в функцию материал верхнего уровня и использовать локальное с динамическим правилом видимости bash. Я создам ответ, чтобы продемонстрировать, что это не широко известная функция, но полностью поддерживаемая.
Оливер
Смотрите также: stackoverflow.com/a/8743103/12887
Джонатан Тран

Ответы:

374

Несмотря на то, что bash имеет returnоператор, единственное, что вы можете указать с его помощью, это собственный exitстатус функции (значение между 0и 255, 0 означает «успех»). Так returnчто не то, что вы хотите.

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

Вот пример:

function fun1(){
  echo 34
}

function fun2(){
  local res=$(fun1)
  echo $res
}

Другой способ получить возвращаемое значение (если вы просто хотите вернуть целое число 0-255) - это $?.

function fun1(){
  return 34
}

function fun2(){
  fun1
  local res=$?
  echo $res
}

Кроме того, обратите внимание, что вы можете использовать возвращаемое значение, чтобы использовать логическую логику, которая fun1 || fun2будет запускаться, только fun2если fun1возвращает 0значение. Возвращаемым значением по умолчанию является выходное значение последнего оператора, выполненного в функции.

tamasgal
источник
2
Вам нужно выполнить, fun1а затем возвращаемое значение сохраняется в $?. Хотя я бы не рекомендовал это делать ...
Тамасгал
9
Почему бы не использовать $??
Питикос
147
Нет, мне нужно чертово возвращаемое значение . К черту эхо.
Томаш Зато - Восстановить Монику
7
@Blauhirn в этой среде, с этой ||конструкцией, код выхода 0 считается успешным и, следовательно, "верным". Ненулевое значение является ошибкой и, следовательно, ложным. Думайте fun1 || fun2как сокращение для «если fun1 возвращает успех или fun2 возвращает успех», а не как оператор самих фактических возвращаемых значений.
davidA
6
Что раздражает, так это то, что функция, которая должна предоставлять данные, также не может выводить другие данные в stdout, потому что вызывающая программа, использующая $ (), тоже получит это и запутается или должна будет проанализировать вывод. Глобальные переменные не очень хороши, потому что это просто вопрос времени, когда вы используете один и тот же глобальный var в двух местах, которые оказываются вложенными, и данные могут быть потеряны. Должны быть отдельные каналы для печати данных и отправки данных обратно.
Оливер
68

$(...)захватывает текст, отправленный на стандартный вывод командой, содержащейся в. returnне выводит на стандартный вывод. $?содержит код результата последней команды.

fun1 (){
  return 34
}

fun2 (){
  fun1
  local res=$?
  echo $res
}
Игнасио Васкес-Абрамс
источник
6
Да returnиспользуется для настройки, $?которая является exit status. В приведенном выше примере, fun1«s exit statusбудет 34. Также обратите внимание, что $(...)также захватывает stderr в дополнение к stdout из указанной команды.
swoop81
59

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

Команды оболочки связаны трубами (или потоками), а не фундаментальными или определяемыми пользователем типами данных, как в «реальных» языках программирования. Нет такой вещи, как возвращаемое значение для команды, возможно, в основном потому, что нет реального способа объявить это. Это может произойти на странице руководства или на --helpвыходе команды, но оба они доступны только для чтения человеком и, следовательно, записываются на ветер.

Когда команда хочет получить ввод, она читает ее из своего потока ввода или списка аргументов. В обоих случаях текстовые строки должны быть проанализированы.

Когда команда хочет что-то вернуть, она должна echoпередать это в свой выходной поток. Другой часто практикуемый способ - хранить возвращаемое значение в выделенных глобальных переменных. Запись в выходной поток является более четкой и гибкой, поскольку она также может принимать двоичные данные. Например, вы можете легко вернуть BLOB:

encrypt() {
    gpg -c -o- $1 # encrypt data in filename to stdout (asks for a passphrase)
}

encrypt public.dat > private.dat # write function result to file

Как написали другие в этом потоке, вызывающая сторона также может использовать подстановку команд $()для захвата вывода.

Параллельно функция «вернет» код выхода gpg(GnuPG). Думайте о коде выхода как о бонусе, которого нет в других языках, или, в зависимости от вашего темперамента, как о «Schmutzeffekt» функциях оболочки. По условию это состояние равно 0 в случае успеха или целое число в диапазоне 1-255 для чего-то еще. Чтобы прояснить это: return(например exit) может принимать значение только от 0 до 255, а значения, отличные от 0, не обязательно являются ошибками, как это часто утверждается.

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

Андреас Шпиндлер
источник
4
+1 за объяснение функций по сравнению с командами и как это влияет на представление об отправке данных обратно вызывающей стороне
Оливер
4
+1 за объяснение, что программирование оболочки - это соединение команд через каналы. Другие языки программирования составляют функции через типы возврата. Bash создает команды через потоки текста.
Джрахали
29

returnОператор устанавливает код завершения функции, так же , как exitбудет делать для всего сценария.

Код выхода для последней команды всегда доступен в $?переменной.

function fun1(){
  return 34
}

function fun2(){
  local res=$(fun1)
  echo $? # <-- Always echos 0 since the 'local' command passes.

  res=$(fun1)
  echo $?  #<-- Outputs 34
}
Остин Филлипс
источник
21

Проблема с другими ответами заключается в том, что они либо используют глобальные переменные, которые могут быть перезаписаны, когда несколько функций находятся в цепочке вызовов, либо echoэто означает, что ваша функция не может выводить диагностическую информацию (вы забудете, что ваша функция выполняет это, и "результат", т.е. возвращает значение, будет содержать больше информации, чем ожидает ваш вызывающий объект, что приведет к странным ошибкам) ​​или evalслишком тяжелым и хакерским.

Правильный способ сделать это - поместить в функцию материал верхнего уровня и использовать localправило динамической видимости bash. Пример:

func1() 
{
    ret_val=hi
}

func2()
{
    ret_val=bye
}

func3()
{
    local ret_val=nothing
    echo $ret_val
    func1
    echo $ret_val
    func2
    echo $ret_val
}

func3

Это выводы

nothing
hi
bye

Динамическая область видимости означает, что ret_valв зависимости от абонента указывается на другой объект! Это отличается от лексической области видимости, которая используется большинством языков программирования. На самом деле это документированная функция , которую легко не заметить, и она не очень хорошо объяснена, вот документация к ней (выделение мое):

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

Для кого-то с фоном C / C ++ / Python / Java / C # / javascript это, вероятно, самое большое препятствие: функции в bash не являются функциями, они являются командами и ведут себя как таковые: они могут выводить в stdout/ stderr, они могут передавать в / out, они могут вернуть код выхода. По сути, нет никакой разницы между определением команды в скрипте и созданием исполняемого файла, который можно вызывать из командной строки.

Так что вместо того, чтобы писать свой сценарий так:

top-level code 
bunch of functions
more top-level code

напишите это так:

# define your main, containing all top-level code
main() 
bunch of functions
# call main
main  

где main()объявляется ret_valкак localи все другие функции возвращают значения через ret_val.

См. Также следующий вопрос по Unix и Linux: Область действия локальных переменных в функциях оболочки .

Другое, возможно, даже лучшее решение, в зависимости от ситуации, - это то , которое использует ya.tecklocal -n .

Оливер
источник
17

Еще один способ добиться этого - ссылки на имена (требуется Bash 4.3+).

function example {
  local -n VAR=$1
  VAR=foo
}

example RESULT
echo $RESULT
ya.teck
источник
3
Любой, кто интересуется, что -n <name>=<reference>делает: делает вновь созданную переменную ссылкой на другую указанную <reference>. Дальнейшие присвоения <name>выполняются для указанной переменной.
Валерио
7

Мне нравится делать следующее, если выполняется в сценарии, где определена функция:

POINTER= # used for function return values

my_function() {
    # do stuff
    POINTER="my_function_return"
}

my_other_function() {
    # do stuff
    POINTER="my_other_function_return"
}

my_function
RESULT="$POINTER"

my_other_function
RESULT="$POINTER"

Мне нравится это, потому что я могу включить эхо-операторы в свои функции, если я хочу

my_function() {
    echo "-> my_function()"
    # do stuff
    POINTER="my_function_return"
    echo "<- my_function. $POINTER"
}
доктор
источник
5

В качестве дополнения к отличным постам других, вот статья, обобщающая эти приемы:

  • установить глобальную переменную
  • установить глобальную переменную, имя которой вы передали функции
  • установить код возврата (и забрать его с помощью $?)
  • 'echo' некоторые данные (и забрать их с MYVAR = $ (myfunction))

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

Том Хандт
источник
Это лучший ответ, так как в статье четко обсуждаются все варианты.
Мзиммерманн
-2

Git Bash в Windows с использованием массивов для нескольких возвращаемых значений

BASH КОД:

#!/bin/bash

##A 6-element array used for returning
##values from functions:
declare -a RET_ARR
RET_ARR[0]="A"
RET_ARR[1]="B"
RET_ARR[2]="C"
RET_ARR[3]="D"
RET_ARR[4]="E"
RET_ARR[5]="F"


function FN_MULTIPLE_RETURN_VALUES(){

   ##give the positional arguments/inputs
   ##$1 and $2 some sensible names:
   local out_dex_1="$1" ##output index
   local out_dex_2="$2" ##output index

   ##Echo for debugging:
   echo "running: FN_MULTIPLE_RETURN_VALUES"

   ##Here: Calculate output values:
   local op_var_1="Hello"
   local op_var_2="World"

   ##set the return values:
   RET_ARR[ $out_dex_1 ]=$op_var_1
   RET_ARR[ $out_dex_2 ]=$op_var_2
}


echo "FN_MULTIPLE_RETURN_VALUES EXAMPLES:"
echo "-------------------------------------------"
fn="FN_MULTIPLE_RETURN_VALUES"
out_dex_a=0
out_dex_b=1
eval $fn $out_dex_a $out_dex_b  ##<--Call function
a=${RET_ARR[0]} && echo "RET_ARR[0]: $a "
b=${RET_ARR[1]} && echo "RET_ARR[1]: $b "
echo
##----------------------------------------------##
c="2"
d="3"
FN_MULTIPLE_RETURN_VALUES $c $d ##<--Call function
c_res=${RET_ARR[2]} && echo "RET_ARR[2]: $c_res "
d_res=${RET_ARR[3]} && echo "RET_ARR[3]: $d_res "
echo
##----------------------------------------------##
FN_MULTIPLE_RETURN_VALUES 4 5  ##<---Call function
e=${RET_ARR[4]} && echo "RET_ARR[4]: $e "
f=${RET_ARR[5]} && echo "RET_ARR[5]: $f "
echo
##----------------------------------------------##


read -p "Press Enter To Exit:"

ОЖИДАЕМЫЙ ВЫХОД:

FN_MULTIPLE_RETURN_VALUES EXAMPLES:
-------------------------------------------
running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[0]: Hello
RET_ARR[1]: World

running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[2]: Hello
RET_ARR[3]: World

running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[4]: Hello
RET_ARR[5]: World

Press Enter To Exit:
JMI MADISON
источник