Повернення значення у функції 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
У цьому випадку набагато гнучкіше вже використовувати ехо у забаві1. Це ідея програмування unix: echo надсилає результати на стандартний вихід, який потім може бути повторно використаний іншими функціями з res = $ (fun1) - або безпосередньо переноситься на інші функції:function a() { echo 34; } function b() { while read data; do echo $data ; done ;} a | b
Arne Babenhauserheide

Правильний спосіб зробити це - ввести функції верхнього рівня у функцію та використовувати локальний з динамічним правилом визначення рівня Bash. Я створимо відповідь, щоб продемонструвати, що це не відома функція, але одна повністю підтримувана.
Олівер


Відповіді:


373

Хоча у 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значення. Значення повернення за замовчуванням - це значення виходу останнього оператора, виконаного в межах функції.


2
Вам потрібно виконати, fun1а потім повернене значення зберігається в $?. Хоча я б не рекомендував цього робити…
tamasgal

9
Чому б не використовувати $??
Pithikos

147
Ні, мені потрібна чортова повернена вартість . До біса з відлунням.
Томаш Зато - Відновіть Моніку

7
@Blauhirn у цьому середовищі з цією ||конструкцією код виходу 0 вважається успішним і тому "істинним". Ненульовий - помилка, а отже, помилкова. Розгляньте це fun1 || fun2як скорочення для "якщо fun1 повертає успіх або fun2 повертає успіх", а не оператора на самі фактичні значення повернення.
davidA

6
Що дратує те, що функція, яка має надавати дані, також не може повторювати інші речі для stdout, тому що абонент, що використовує $ (), теж отримає це і заплутається або повинен розібрати вихід. Глобальні змінні не є великими, тому що просто використовувати час, перш ніж використовувати один і той же глобальний var у двох місцях, які можуть бути вкладеними і дані можуть загубитися. Повинні бути окремі канали для друку даних проти надсилання даних назад.
Олівер

68

$(...)фіксує текст, надісланий stdout командою, що міститься в. returnне виводить на stdout. $?містить код результату останньої команди.

fun1 (){
  return 34
}

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

6
Так return, використовується для встановлення, $?яке є exit status. У наведеному вище прикладі, fun1«s exit statusбуде 34. Також зауважте, що $(...)на додаток stdout від вказаної команди також фіксується stderr.
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). Подумайте про вихідний код як бонус, якого інші мови не мають, або, залежно від вашого темпераменту, як «Шмутцеффект» функцій оболонки. Цей статус, за умовою, 0 на успіх або ціле число в діапазоні 1-255 для чогось іншого. Щоб це було зрозуміло: return(як exit) можна приймати значення лише від 0-255, а значення, відмінні від 0, не обов'язково є помилками, як це часто стверджується.

Якщо ви не надаєте явного значення, returnстатус береться з останньої команди в операторі / функції / команді Bash тощо. Тому статус завжди є, і returnце просто простий спосіб його надати.


4
+1 для пояснення функцій проти команд та як це впливає на поняття відправлення даних назад абоненту
Олівер,

4
+1 для пояснення, що програмування оболонки - це з'єднання команд через труби. Інші мови програмування складають функції через типи повернення. Bash складає команди через потоки тексту.
jrahhali

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
}

20

Проблема з іншими відповідями полягає в тому, що вони або використовують глобальний, який можна перезаписати, коли кілька функцій перебувають у ланцюзі викликів, або 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, вони можуть передавати в / вихід, вони можуть повернути вихідний код. В основному немає різниці між визначенням команди в скрипті та створенням виконуваного файлу, який можна викликати з командного рядка.

Тож замість того, щоб писати сценарій так:

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.teck, яке використовує local -n.


17

Ще один спосіб досягти цього - посилання на імена (потрібен Bash 4.3+).

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

example RESULT
echo $RESULT

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

Ось як доповнення до чудових публікацій інших людей, ось стаття, узагальнюючи ці методи:

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

Повернення значень з функції Bash


Це найкраща відповідь, оскільки у статті чітко обговорюються всі варіанти.
mzimmermann

-2

Git Bash в Windows, використовуючи масиви для кількох повернених значень

БАЗОВИЙ КОД:

#!/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:
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.