Як повернути значення рядка з функції Bash


461

Я хотів би повернути рядок з функції Bash.

Я напишу приклад в java, щоб показати, що я хотів би зробити:

public String getSomeString() {
  return "tadaa";
}

String variable = getSomeString();

Наведений нижче приклад працює в bash, але чи є кращий спосіб зробити це?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)

4
На відміну function funcName {від попереднього POSIX-синтаксису, успадкованого з раннього ksh (де він мав смислові відмінності, які bash не шанує). funcName() {, без function, слід використовувати замість цього; дивіться wiki.bash-hackers.org/scripting/obsolete
Чарльз Даффі

Це посилання говорить про використання NAME () COMPOUND-CMD або функцію NAME {CMDS; } Так function myFunction { blah; }добре; це function myFunction() { blah }не добре, тобто використання дужок із функцією ключового слова.
Буде

Відповіді:


290

Немає кращого способу, про який я знаю. Bash знає лише коди стану (цілі числа) та рядки, записані в stdout.


15
+1 @ tomas-f: ви повинні бути дуже обережними щодо того, що у вас є у цій функції "getSomeString ()", оскільки будь-який код, який у підсумку перегукнеться, означатиме, що ви отримаєте неправильний рядок повернення.
Мані

11
Це просто неправильно. Ви можете повернути довільні дані всередині змінної, що повертається. Що, очевидно, є кращим способом.
Evi1M4chine

36
@ Evi1M4chine, гм ... ні, ти не можеш. Ви можете встановити глобальну змінну і назвати її "return", як я бачу у своїх сценаріях. Але тоді це за умовою, НЕ насправді програмно прив’язаний до виконання коду. "явно кращий спосіб"? Гм, ні. Підстановка команд набагато більш чітка і модульна.
Wildcard

6
"Заміна команд набагато більш чітка і модульна" була б доречною, якби питання стосувалося команд; це питання - як повернути рядок з функції bash! Побудований спосіб зробити те, що просила ОП, доступний з Bash 4.3 (2014?) - див. Мою відповідь нижче.
zenaan

4
Оригінальне запитання містить найпростіший спосіб зробити це і працює в більшості випадків. Значення повернення Bash, ймовірно, слід називати "кодами повернення", оскільки вони менш схожі на стандартні значення повернення в сценаріях, і більше схожі на числові коди виходу командної оболонки (ви можете робити подібні речі somefunction && echo 'success'). Якщо ви думаєте про функцію, як просто іншу команду, це має сенс; команди не "повертають" нічого при виході, крім коду статусу, але вони можуть тим часом виводити речі, які ви можете зробити.
Beejor

193

У вас може бути функція приймати змінну як перший аргумент і змінювати змінну за допомогою рядка, який ви хочете повернути.

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "$1='foo bar rab oof'"
}

return_var=''
pass_back_a_string return_var
echo $return_var

Друкує "foo bar rab oof".

Редагувати : додано цитування у відповідному місці, щоб дозволити пробіл у рядку звертатися до коментаря @Luca Borrione.

Редагувати : Як демонстрацію дивіться наступну програму. Це рішення загального призначення: воно навіть дозволяє отримувати рядок у локальну змінну.

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "$1='foo bar rab oof'"
}

return_var=''
pass_back_a_string return_var
echo $return_var

function call_a_string_func() {
     local lvar=''
     pass_back_a_string lvar
     echo "lvar='$lvar' locally"
}

call_a_string_func
echo "lvar='$lvar' globally"

Це відбитки:

+ return_var=
+ pass_back_a_string return_var
+ eval 'return_var='\''foo bar rab oof'\'''
++ return_var='foo bar rab oof'
+ echo foo bar rab oof
foo bar rab oof
+ call_a_string_func
+ local lvar=
+ pass_back_a_string lvar
+ eval 'lvar='\''foo bar rab oof'\'''
++ lvar='foo bar rab oof'
+ echo 'lvar='\''foo bar rab oof'\'' locally'
lvar='foo bar rab oof' locally
+ echo 'lvar='\'''\'' globally'
lvar='' globally

Edit : показати , що значення вихідного змінної є доступні в функції, як це було неправильно розкритикований @Xichen Лі в коментарі.

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "echo in pass_back_a_string, original $1 is \$$1"
    eval "$1='foo bar rab oof'"
}

return_var='original return_var'
pass_back_a_string return_var
echo $return_var

function call_a_string_func() {
     local lvar='original lvar'
     pass_back_a_string lvar
     echo "lvar='$lvar' locally"
}

call_a_string_func
echo "lvar='$lvar' globally"

Це дає вихід:

+ return_var='original return_var'
+ pass_back_a_string return_var
+ eval 'echo in pass_back_a_string, original return_var is $return_var'
++ echo in pass_back_a_string, original return_var is original return_var
in pass_back_a_string, original return_var is original return_var
+ eval 'return_var='\''foo bar rab oof'\'''
++ return_var='foo bar rab oof'
+ echo foo bar rab oof
foo bar rab oof
+ call_a_string_func
+ local 'lvar=original lvar'
+ pass_back_a_string lvar
+ eval 'echo in pass_back_a_string, original lvar is $lvar'
++ echo in pass_back_a_string, original lvar is original lvar
in pass_back_a_string, original lvar is original lvar
+ eval 'lvar='\''foo bar rab oof'\'''
++ lvar='foo bar rab oof'
+ echo 'lvar='\''foo bar rab oof'\'' locally'
lvar='foo bar rab oof' locally
+ echo 'lvar='\'''\'' globally'
lvar='' globally

1
Ця відповідь чудова! Параметри можуть передаватися посиланнями, аналогічно ідеї в C ++.
Юнь Хуан

4
Було б непогано отримати відповідь від експерта про цю відповідь. Я ніколи не бачив, щоб це було використано в сценаріях, можливо, з поважної причини. У всякому разі: це +1 За правильну відповідь слід було проголосувати
Джон

Хіба це не той самий fgmвідповідь, написаний спрощеним чином? Це не спрацює, якщо рядок fooмістить білі пробіли, тоді як fgm"буде", як він показує.
Лука Борріоне

4
@XichenLi: дякую за те, що ви залишили коментар зі своєю головою; будь ласка, дивіться мою редакцію. Ви можете отримати початкове значення змінної у функції за допомогою \$$1. Якщо ви шукаєте щось інше, будь ласка, повідомте мене про це.
bstpierre

1
@timiscoding Це можна виправити за допомогою a printf '%q' "$var". % q - це рядковий формат для виходу з оболонки. Тоді просто передайте його сирим.
bb010g

99

Усі відповіді вище ігнорують те, що було сказано на чоловічій сторінці bash.

  • Усі змінні, задекларовані всередині функції, будуть спільними з викликовим середовищем.
  • Усі змінні, оголошені локальними, не поділяться.

Приклад коду

#!/bin/bash

f()
{
    echo function starts
    local WillNotExists="It still does!"
    DoesNotExists="It still does!"
    echo function ends
}

echo $DoesNotExists #Should print empty line
echo $WillNotExists #Should print empty line
f                   #Call the function
echo $DoesNotExists #Should print It still does!
echo $WillNotExists #Should print empty line

І вихід

$ sh -x ./x.sh
+ echo

+ echo

+ f
+ echo function starts 
function starts
+ local 'WillNotExists=It still does!'
+ DoesNotExists='It still does!'
+ echo function ends 
function ends
+ echo It still 'does!' 
It still does!
+ echo

Також під pdksh та ksh цей сценарій робить те саме!


10
Ця відповідь має свої плюси. Я зайшов сюди, думаючи, що хочу повернути рядок з функції. Ця відповідь дала мені зрозуміти, що це просто мої C # -говори. Я підозрюю, що інші можуть мати такий же досвід.
ЛОЗ

4
@ElmarZander Ви неправі, це цілком актуально. Це простий спосіб отримати в глобальному масштабі значення функції області, і дехто вважає це кращим / простішим, ніж підхід eval для переосмислення глобальної змінної, окресленої bstpierre.
KomodoDave

локальний не переноситься на не-баш сценарії, що є однією з причин, що деякі люди уникають цього.
нехай яскравий

Питання: Що з змінними в циклі?
anu

1
На mac ($ bash - версія GNU bash, версія 3.2.57 (1) -випуск (x86_64-apple-darwin14) Copyright (C) 2007 Free Software Foundation, Inc.), правильно, що відповідна глобальна змінна ініціалізований, але коли я намагаюся побічно впливати на ту саму змінну в іншій функції f2, цей побічний ефект не зберігається. Отже, це здається дуже непостійним і, отже, не корисним для мого використання.
AnneTheAgile

45

Bash, оскільки версія 4.3, feb 2014 (?), Має явну підтримку посилальних змінних чи іменних посилань (namerefs), що не перевищує "eval", з однаковою корисною ефективністю та ефектом непрямості, які можуть бути чіткішими у ваших сценаріях, а також складнішими щоб "забути" eval "і виправити цю помилку":

declare [-aAfFgilnrtux] [-p] [name[=value] ...]
typeset [-aAfFgilnrtux] [-p] [name[=value] ...]
  Declare variables and/or give them attributes
  ...
  -n Give each name the nameref attribute, making it a name reference
     to another variable.  That other variable is defined by the value
     of name.  All references and assignments to name, except for
     changing the -n attribute itself, are performed on the variable
     referenced by name's value.  The -n attribute cannot be applied to
     array variables.
...
When used in a function, declare and typeset make each name local,
as with the local command, unless the -g option is supplied...

а також:

ПАРАМЕТРИ

Змінній може бути призначений атрибут nameref, використовуючи параметр -n для оголошень або локальних вбудованих команд (див. Описи оголошень та місцевих нижче) для створення імені refre або посилання на іншу змінну. Це дозволяє непрямим чином маніпулювати змінними. Щоразу, коли на змінну nameref посилається ⋅ або присвоюється їй, операція фактично виконується на змінній, визначеній значенням змінної nameref. Іменний реф зазвичай використовується у функціях оболонки для позначення змінної, ім'я якої передається як аргумент для функції. Наприклад, якщо ім'я змінної передається функції оболонки як її перший аргумент, запущений

      declare -n ref=$1

всередині функції створюється переменная nameref ref, значенням якої є назва змінної, передана як перший аргумент. Посилання та завдання для перегляду трактуються як посилання та присвоєння змінній, ім'я якої було передано як $ 1. Якщо контрольна змінна в циклі for for має атрибут nameref, у списку слів може бути список змінних оболонок, а посилання на ім’я буде встановлено⋅ для кожного слова у списку, у свою чергу, коли цикл виконується. Змінній масиву не можна надати атрибут -n. Однак змінні nameref можуть посилатись на змінні масиву та на підписані масиви змінних. Namerefs можна відмінити, використовуючи опцію -n для скидання вбудованого. В іншому випадку, якщо unset виконується з ім'ям змінної nameref як аргументом,

Наприклад ( EDIT 2 : (спасибі Рон) іменний (префікс) ім'я змінної внутрішньої функції, щоб мінімізувати зовнішні зіткнення змінної, які, нарешті, повинні відповісти належним чином, питання, порушене в коментарях Карстен):

# $1 : string; your variable to contain the return value
function return_a_string () {
    declare -n ret=$1
    local MYLIB_return_a_string_message="The date is "
    MYLIB_return_a_string_message+=$(date)
    ret=$MYLIB_return_a_string_message
}

і тестування цього прикладу:

$ return_a_string result; echo $result
The date is 20160817

Зауважимо, що вбудований bash "оголосити", коли використовується у функції, за замовчуванням робить задекларовану змінну "локальною", а "-n" також можна використовувати з "local".

Я вважаю за краще відрізняти змінні "важливого оголошення" від "нудних локальних" змінних, тому використання "заявити" та "локальне" таким чином діє як документація.

EDIT 1 - (Відповідь на коментар нижче від Karsten) - Я більше не можу додавати коментарі нижче, але коментар Karsten змусив мене задуматися, тому я зробив наступний тест, який РОБОТИ ТОПЛИВО, АФАЙКТ - Карстен, якщо ви читаєте це, будь ласка, надайте точний набір тестових кроків з командного рядка, що показує, що проблема, на яку ви припускаєте, існує, тому що ці наступні кроки працюють добре:

$ return_a_string ret; echo $ret
The date is 20170104

(Я запустив це саме зараз, вставивши вищевказану функцію в термін bash - як ви бачите, результат працює чудово.)


4
Я сподіваюся, що це пронизає вершину. eval має бути в крайньому випадку. Варто зазначити, що змінні nameref доступні лише з bash 4.3 (відповідно до журналу змін ) (випущено в feb 2014 [?]). Це важливо, якщо портативність викликає занепокоєння. Будь ласка, цитуйте посібник з bash про те, що declareстворює локальні змінні всередині функцій (цю інформацію не надає help declare): "... При використанні у функції оголошуйте та набирайте набір, щоб кожне ім'я було локальним, як і для локальної команди, якщо тільки - g опція поставляється ... "
init_js

2
Ця проблема має ту саму проблему, як і рішення eval. Коли ви викликаєте функцію та передаєте ім'я вихідної змінної, вам слід уникати передачі імені змінної, яка використовується локально в межах функції, яку ви викликаєте. Це основна проблема з точки зору інкапсуляції, оскільки ви не можете просто додати або перейменувати нові локальні змінні у функцію, якщо будь-яка з функцій, що викликають, може захотіти використовувати це ім'я для вихідного параметра.
Карстен

1
@Karsten погодився. в обох випадках (eval та namerefs), можливо, доведеться вибрати інше ім’я. Однією з переваг підходу до nameref над eval є те, що не потрібно мати справу з вхідними рядками. Звичайно, завжди можна зробити щось на кшталт K=$1; V=$2; eval "$A='$V'";, але одна помилка (наприклад, порожній або пропущений параметр), і це було б небезпечніше. @zenaan питання, порушене @Karsten, застосовується, якщо ви вибрали "message" як ім'я змінної, що повертається, замість "ret".
init_js

3
Імовірно, функція повинна бути розроблена з самого початку, щоб прийняти аргумент nameref, тому автор функції повинен знати про можливість зіткнення імені та може використовувати деякі типові умови, щоб цього уникнути. Наприклад, всередині функції X назви локальні змінні з умовою "X_LOCAL_name".
Рон Берк

34

Як і bstpierre вище, я використовую та рекомендую використовувати явно іменування вихідних змінних:

function some_func() # OUTVAR ARG1
{
   local _outvar=$1
   local _result # Use some naming convention to avoid OUTVARs to clash
   ... some processing ....
   eval $_outvar=\$_result # Instead of just =$_result
}

Зверніть увагу на використання котирування $. Це дозволить уникнути інтерпретації вмісту $resultяк спеціальних символів оболонки. Я виявив, що це на порядок швидше, ніжresult=$(some_func "arg1") ідіома захоплення відлуння. Різниця швидкостей здається ще більш помітною при використанні bash на MSYS, де захоплення виходу з функціональних викликів майже катастрофічно.

Добре надсилати локальні змінні, оскільки місцеві жителі динамічно розміщені в басі:

function another_func() # ARG
{
   local result
   some_func result "$1"
   echo result is $result
}

4
Це допомагає мені, тому що я люблю використовувати декілька операторів ехо для налагодження / ведення журналу. Ідіома захоплення відлуння провалюється, оскільки фіксує їх усіх. Дякую!
AnneTheAgile

Це (друге найкраще) правильне рішення! Чистий, швидкий, елегантний, розважливий.
Evi1M4chine

+2 для збереження реальності. Я збирався сказати. Як багато людей можуть ігнорувати поєднанняecho всередині функції в поєднанні з заміною команд!
Ентоні

22

Ви також можете зафіксувати вихід функції:

#!/bin/bash
function getSomeString() {
     echo "tadaa!"
}

return_var=$(getSomeString)
echo $return_var
# Alternative syntax:
return_var=`getSomeString`
echo $return_var

Виглядає дивно, але краще, ніж використання глобальних змінних IMHO. Параметри передачі працюють як завжди, просто покладіть їх усередину брекетів або задників.


11
окрім альтернативної синтаксичної ноти, чи це не те саме, що оп уже написав у своєму власному запитанні?
Лука Борріоне

12

Найпростішим та надійним рішенням є використання підстановки команд, як писали інші:

assign()
{
    local x
    x="Test"
    echo "$x"
}

x=$(assign) # This assigns string "Test" to x

Мінусом є продуктивність, оскільки для цього потрібен окремий процес.

Інша методика, запропонована в цій темі, а саме передача імені змінної, яку слід призначити аргументом, має побічні ефекти, і я не рекомендував би її в її базовій формі. Проблема полягає в тому, що вам, ймовірно, знадобляться деякі змінні у функції для обчислення значення, що повертається, і може статися так, що ім'я змінної, призначеної для зберігання повернутого значення, буде заважати одній з них:

assign()
{
    local x
    x="Test"
    eval "$1=\$x"
}

assign y # This assigns string "Test" to y, as expected

assign x # This will NOT assign anything to x in this scope
         # because the name "x" is declared as local inside the function

Ви, звичайно, не можете оголосити внутрішні змінні функції як локальні, але ви дійсно завжди повинні це робити, оскільки в іншому випадку ви можете випадково перезаписати незв'язану змінну з батьківської області, якщо є одна з такою ж назвою .

Один з можливих шляхів вирішення - це явне оголошення пройденої змінної як глобальної:

assign()
{
    local x
    eval declare -g $1
    x="Test"
    eval "$1=\$x"
}

Якщо ім'я "x" буде передано як аргумент, другий рядок функції функції замінить попередню локальну декларацію. Але самі імена можуть все-таки заважати, тому якщо ви збираєтесь використовувати значення, яке раніше зберігалося в передаваній змінній, до того, щоб записати туди повернене значення, пам’ятайте, що ви повинні скопіювати її в іншу локальну змінну на самому початку; інакше результат буде непередбачуваним! Крім того, це буде працювати лише в останній версії BASH, а саме 4.2. Більш портативний код може використовувати явні умовні конструкції з тим же ефектом:

assign()
{
    if [[ $1 != x ]]; then
      local x
    fi
    x="Test"
    eval "$1=\$x"
}

Мабуть, найелегантніше рішення - просто зарезервувати одне глобальне ім’я для повернених значень функції та послідовно використовувати його у кожній функції, яку ви пишете.


3
Це ^^^. Невмисне згладжування, яке порушує інкапсуляцію, є великою проблемою як із рішеннями, так evalі з declare -nрішеннями. Вирішення проблеми, що має єдине виділене ім'я змінної, як resultдля всіх вихідних параметрів, здається єдиним рішенням, яке не потребує функцій, щоб знати всі його викликаючі для уникнення конфліктів.
Карстен

12

Як вже було сказано раніше, "правильний" спосіб повернути рядок з функції - це із заміною команд. У тому випадку, якщо функцію також потрібно вивести на консоль (як згадує @Mani вище), створіть тимчасовий fd на початку функції та перенаправіть на консоль. Перед поверненням рядка закрийте тимчасовий fd.

#!/bin/bash
# file:  func_return_test.sh
returnString() {
    exec 3>&1 >/dev/tty
    local s=$1
    s=${s:="some default string"}
    echo "writing directly to console"
    exec 3>&-     
    echo "$s"
}

my_string=$(returnString "$*")
echo "my_string:  [$my_string]"

виконання сценарію без парамів створює ...

# ./func_return_test.sh
writing directly to console
my_string:  [some default string]

сподіваюся, що це допомагає людям

-Анді


6
Це має свою користь, але в цілому вам слід уникати явного перенаправлення на консоль; вихід може вже бути переспрямований, або сценарій може працювати в контексті, де немає tty. Ви зможете обійти це, копіюючи 3>&1на чолі сценарію, маніпулюючи &1 &3та іншим заповнювачем заповнення &4функції. Некрасиво все кругло.
jmb

8

Ви можете використовувати глобальну змінну:

declare globalvar='some string'

string ()
{
  eval  "$1='some other string'"
} # ----------  end of function string  ----------

string globalvar

echo "'${globalvar}'"

Це дає

'some other string'

6

Щоб проілюструвати мій коментар до відповіді Енді з додатковими маніпуляціями дескрипторами файлів, щоб уникнути використання /dev/tty:

#!/bin/bash

exec 3>&1

returnString() {
    exec 4>&1 >&3
    local s=$1
    s=${s:="some default string"}
    echo "writing to stdout"
    echo "writing to stderr" >&2
    exec >&4-
    echo "$s"
}

my_string=$(returnString "$*")
echo "my_string:  [$my_string]"

Досі неприємно, хоча.


3

У вас це є єдиний спосіб зробити це, не порушуючи сферу застосування. Bash не має поняття типів повернення, просто вихідні коди та дескриптори файлів (stdin / out / err тощо)


3

Звертаючись до голови Вікі Роннен , враховуючи наступний код:

function use_global
{
    eval "$1='changed using a global var'"
}

function capture_output
{
    echo "always changed"
}

function test_inside_a_func
{
    local _myvar='local starting value'
    echo "3. $_myvar"

    use_global '_myvar'
    echo "4. $_myvar"

    _myvar=$( capture_output )
    echo "5. $_myvar"
}

function only_difference
{
    local _myvar='local starting value'
    echo "7. $_myvar"

    local use_global '_myvar'
    echo "8. $_myvar"

    local _myvar=$( capture_output )
    echo "9. $_myvar"
}

declare myvar='global starting value'
echo "0. $myvar"

use_global 'myvar'
echo "1. $myvar"

myvar=$( capture_output )
echo "2. $myvar"

test_inside_a_func
echo "6. $_myvar" # this was local inside the above function

only_difference



дам

0. global starting value
1. changed using a global var
2. always changed
3. local starting value
4. changed using a global var
5. always changed
6. 
7. local starting value
8. local starting value
9. always changed

Можливо, звичайним сценарієм є використання синтаксису, який використовується у test_inside_a_funcфункції, таким чином ви можете використовувати обидва методи в більшості випадків, хоча захоплення виводу є більш безпечним методом, який завжди працює в будь-якій ситуації, імітуючи повернене значення з функції, яку ви можете знайти в інших мовах, як Vicky Ronnenправильно вказано.


2

Варіанти були перелічені, я думаю. Вибір одного з них може звестись до питання найкращого стилю для вашої конкретної програми, і я хочу запропонувати один конкретний стиль, який я вважаю корисним. У bash, змінні та функції знаходяться не в одному просторі імен. Таким чином, трактування однойменної змінної як значення функції - це умова, згідно з яким я вважаю, що мінімізує зіткнення імен та підвищує читабельність, якщо застосовувати її жорстко. Приклад з реального життя:

UnGetChar=
function GetChar() {
    # assume failure
    GetChar=
    # if someone previously "ungot" a char
    if ! [ -z "$UnGetChar" ]; then
        GetChar="$UnGetChar"
        UnGetChar=
        return 0               # success
    # else, if not at EOF
    elif IFS= read -N1 GetChar ; then
        return 0           # success
    else
        return 1           # EOF
    fi
}

function UnGetChar(){
    UnGetChar="$1"
}

І, приклад використання таких функцій:

function GetToken() {
    # assume failure
    GetToken=
    # if at end of file
    if ! GetChar; then
        return 1              # EOF
    # if start of comment
    elif [[ "$GetChar" == "#" ]]; then
        while [[ "$GetChar" != $'\n' ]]; do
            GetToken+="$GetChar"
            GetChar
        done
        UnGetChar "$GetChar"
    # if start of quoted string
    elif [ "$GetChar" == '"' ]; then
# ... et cetera

Як бачите, статус повернення є для вас, коли ви потребуєте, або ігноруйте, якщо цього не зробите. Змінна "повернута" також може бути використана або проігнорована, але, звичайно, лише після виклику функції.

Звичайно, це лише умовність. Ви не можете встановити пов’язане значення перед поверненням (звідси моє домовленість про те, щоб він завжди занурив його на початку функції) або перетоптати його значення, викликавши функцію ще раз (можливо, побічно). Все-таки це конвенція, яку я вважаю дуже корисною, якщо мені здається, що я активно використовую баш-функції.

На відміну від думки про те, що це знак, наприклад, "перейти на перл", моя філософія полягає в тому, що конвенції завжди важливі для управління складністю будь-якої мови.


2

Ви можете echoввести рядок, але зафіксуйте її трубопроводами (| ) функцію на щось інше.

Ви можете це зробити expr, хоча ShellCheck повідомляє про це використання як про застаріле.


Проблема в тому, що справа від труби - це нижня оболонка. Так що myfunc | read OUTPUT ; echo $OUTPUTнічого не дає. myfunc | ( read OUTPUT; echo $OUTPUT )отримує очікувану цінність і уточнює, що відбувається з правого боку. Але, звичайно, вихід тут недоступний там, де вам це потрібно ...
Ед Рандалл,

2

Вони є ключовою проблемою будь-якої схеми "названої змінної виводу", коли абонент може передати ім'я змінної (будь то використання evalабо declare -n) - ненавмисне псевдонім, тобто зіткнення імен. локальна змінна у функції без перевірки ALL абонентів функції, щоб переконатися, що вони не хочуть передавати це ім'я як вихідний параметр. (Або в іншому напрямку мені не хочеться читати джерело функції, яку я викликаю, просто щоб переконатися, що вихідний параметр, який я маю намір використовувати, не є локальним для цієї функції.)

Єдиний спосіб цього - використовувати одну виділену вихідну змінну на зразок REPLY(як це запропонував Evi1M4chine ) або конвенції , як один з них запропонував Рон Берк .

Тим НЕ менше, це можливо , щоб мати функції використовувати змінну фіксований вихідну всередині , а потім додати трохи цукру поверх , щоб приховати цей факт від абонента , як я зробив з callфункцією в наступному прикладі. Вважайте це доказом концепції, але ключові моменти є

  • Функція завжди присвоює повернене значення REPLY, а також може повернути вихідний код, як зазвичай
  • З точки зору абонента, що повертається, значення може бути призначене будь-якій змінній (локальній чи глобальній), включаючи REPLY(див. wrapperПриклад). Код виходу функції передається, тому використовуючи їх у напрif або whileчи аналогічні конструкти роботи , як і очікувалося.
  • Синтаксично виклик функції все ще є єдиним простим твердженням.

Причина цього працює в тому, що callсама функція не має локальних даних і не використовує ніяких змінних, крім того REPLY, уникаючи потенціалу для зіткнення імен. У тому випадку, коли призначено визначене викликом ім'я змінної виводу, ми фактично перебуваємо в області виклику (технічно в однаковій області callфункції), а не в області функції, яка викликається.

#!/bin/bash
function call() { # var=func [args ...]
  REPLY=; "${1#*=}" "${@:2}"; eval "${1%%=*}=\$REPLY; return $?"
}

function greet() {
  case "$1" in
    us) REPLY="hello";;
    nz) REPLY="kia ora";;
    *) return 123;;
  esac
}

function wrapper() {
  call REPLY=greet "$@"
}

function main() {
  local a b c d
  call a=greet us
  echo "a='$a' ($?)"
  call b=greet nz
  echo "b='$b' ($?)"
  call c=greet de
  echo "c='$c' ($?)"
  call d=wrapper us
  echo "d='$d' ($?)"
}
main

Вихід:

a='hello' (0)
b='kia ora' (0)
c='' (123)
d='hello' (0)

1

bash pattern для повернення об'єктів як скалярних, так і масивів :

визначення

url_parse() { # parse 'url' into: 'url_host', 'url_port', ...
   local "$@" # inject caller 'url' argument in local scope
   local url_host="..." url_path="..." # calculate 'url_*' components
   declare -p ${!url_*} # return only 'url_*' object fields to the caller
}

виклик

main() { # invoke url parser and inject 'url_*' results in local scope
   eval "$(url_parse url=http://host/path)" # parse 'url'
   echo "host=$url_host path=$url_path" # use 'url_*' components
}

0

У моїх програмах, за умовою, саме для цього існує попередня $REPLYзмінна, яка readвикористовується саме для цієї мети.

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

Це echoес

tadaa

Але, щоб уникнути конфліктів, зробить будь-яка інша глобальна змінна.

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

Якщо цього недостатньо, я рекомендую рішення Markarian451 .


-2
agt@agtsoft:~/temp$ cat ./fc 
#!/bin/sh

fcall='function fcall { local res p=$1; shift; fname $*; eval "$p=$res"; }; fcall'

function f1 {
    res=$[($1+$2)*2];
}

function f2 {
    local a;
    eval ${fcall//fname/f1} a 2 3;
    echo f2:$a;
}

a=3;
f2;
echo after:a=$a, res=$res

agt@agtsoft:~/temp$ ./fc
f2:10
after:a=3, res=
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.