Как вернуть строковое значение из функции Bash

462

Я хотел бы вернуть строку из функции 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 знает только коды состояния (целые числа) и строки, записанные в стандартный вывод.

Philipp
источник
15
+1 @ tomas-f: вы должны быть очень осторожны с тем, что у вас есть в этой функции "getSomeString ()", поскольку наличие любого кода, который в конечном итоге будет отображаться, будет означать, что вы получите неверную возвращаемую строку.
Мани
11
Это просто неправильно. Вы можете вернуть произвольные данные внутри возвращаемой переменной. Что явно лучше.
Evi1M4chine
36
@ Evi1M4chine, хм ... нет, ты не можешь. Вы можете установить глобальную переменную и назвать ее «return», как я вижу в ваших сценариях. Но тогда это по соглашению, НЕ на самом деле программно связано с выполнением вашего кода. "явно лучший способ"? Нет Подстановка команд гораздо более явная и модульная.
Wildcard
6
«Подстановка команд гораздо более явная и модульная» была бы уместна, если бы речь шла о командах; вопрос в том, как вернуть строку из функции bash! Начиная с Bash 4.3 (2014?) Доступен встроенный способ сделать то, о чем просил ОП - см. Мой ответ ниже.
Зенаан
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
bstpierre
источник
1
Этот ответ великолепен! Параметры могут быть переданы по ссылкам, аналогично идее в C ++.
Юн Хуан
4
Было бы неплохо получить ответ от эксперта об этом ответе. Я никогда не видел, чтобы это использовалось в сценариях, возможно, по уважительной причине В любом случае: это +1 Это должно было голосование за правильный ответ
John
Разве это не тот же fgmответ, написанный в упрощенном виде? Это не будет работать, если строка fooсодержит пробелы, в то время как строка fgmбудет ... как он показывает.
Лука Боррионе
4
@XichenLi: спасибо, что оставили комментарий со своим понижением; пожалуйста, смотрите мое редактирование. Вы можете получить начальное значение переменной в функции с помощью \$$1. Если вы ищете что-то другое, пожалуйста, дайте мне знать.
bstpierre
1
@timiscoding Это можно исправить с помощью 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 #. Я подозреваю, что другие могут иметь такой же опыт.
LOAS
4
@ ElmarZander Вы не правы, это совершенно актуально. Это простой способ получить в глобальной области видимости значения функции-области, и некоторые считают, что это лучше / проще, чем подход eval для переопределения глобальной переменной, как обрисовано в общих чертах bstpierre.
KomodoDave
local не переносится на сценарии, отличные от bash, что является одной из причин, по которой некоторые люди избегают этого.
Дон
Вопрос: как насчет переменных в циклах?
ана
1
В mac ($ bash --version GNU bash, версия 3.2.57 (1) -релиз (x86_64-apple-darwin14) Copyright (C) 2007 Free Software Foundation, Inc.) правильно, что соответствующая глобальная переменная имеет вид инициализируется, но когда я пытаюсь побочным эффектом той же переменной в другой функции f2, этот побочный эффект не сохраняется. Таким образом, это кажется очень противоречивым и, следовательно, не подходит для моего использования.
AnneTheAgile
45

Bash, начиная с версии 4.3, февраль 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 для встроенных команд объявлений или локальных команд (см. Описания объявлений и локальных ресурсов ниже), чтобы создать именованную ссылку или ссылку на другую переменную. Это позволяет косвенно манипулировать переменными. Всякий раз, когда на переменную nameref ссылаются или назначают, операция фактически выполняется над переменной, указанной значением переменной nameref. Nameref обычно используется в функциях оболочки для ссылки на переменную, имя которой передается в качестве аргумента функции. Например, если имя переменной передается функции оболочки в качестве первого аргумента, выполняется

      declare -n ref=$1

внутри функции создается переменная nameref ref, значением которой является имя переменной, переданное в качестве первого аргумента. Ссылки и присваивания ref обрабатываются как ссылки и присваивания переменной, имя которой было передано как $ 1. Если управляющая переменная в цикле for имеет атрибут nameref, список слов может быть списком переменных оболочки, и ссылка на имя будет установлена ​​для каждого слова в списке, в свою очередь, при выполнении цикла. Переменным массива не может быть присвоен атрибут -n. Тем не менее, переменные nameref могут ссылаться на переменные массива и подписанные переменные массива. Namerefs можно отключить, используя опцию -n встроенной функции unset. В противном случае, если unset выполняется с именем переменной nameref в качестве аргумента,

Например ( РЕДАКТИРОВАТЬ 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 «Declare», когда используется в функции, делает объявленную переменную «локальной» по умолчанию, и «-n» также может использоваться с «локальной».

Я предпочитаю отличать переменные «важное объявление» от «скучных локальных» переменных, поэтому использование «объявления» и «локального» таким образом действует как документация.

РЕДАКТИРОВАТЬ 1 - (Ответ на комментарий ниже от Karsten) - я не могу больше добавлять комментарии ниже, но комментарий Karsten заставил меня задуматься, поэтому я сделал следующий тест, который РАБОТАЕТ КАЧЕСТВЕННО, AFAICT - Karsten, если вы читаете это, пожалуйста, предоставьте точный набор шагов теста из командной строки, показывающих, что проблема, по вашему мнению, существует, потому что эти следующие шаги работают просто отлично:

$ return_a_string ret; echo $ret
The date is 20170104

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

zenaan
источник
4
Я надеюсь, что это просочится к вершине. Eval должен быть последним средством. Стоит упомянуть, что переменные nameref доступны только начиная с bash 4.3 (согласно журналу изменений ) (выпущен в феврале 2014 [?]). Это важно, если переносимость является проблемой. Пожалуйста, процитируйте руководство по bash о том, что declareвнутри функций создаются локальные переменные (эта информация не передается help declare): "... При использовании в функции объявление и набор текста делают каждое имя локальным, как с локальной командой, если только - g опция предоставлена ​​... "
init_js
2
Это имеет ту же проблему наложения имен, что и решение eval. Когда вы вызываете функцию и передаете имя выходной переменной, вы должны избегать передачи имени переменной, которая используется локально в вызываемой вами функции. Это серьезная проблема с точки зрения инкапсуляции, так как вы не можете просто добавить или переименовать новые локальные переменные в функции без того, чтобы кто-либо из вызывающих функций мог использовать это имя для выходного параметра.
Карстен
1
@ Карстен согласился. в обоих случаях (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, где захват stdout из вызовов функций почти катастрофичен.

Можно отправлять локальные переменные, поскольку в bash динамически ограничиваются локальные переменные:

function another_func() # ARG
{
   local result
   some_func result "$1"
   echo result is $result
}
Markarian451
источник
4
Это помогает мне, потому что мне нравится использовать несколько операторов echo для целей отладки / регистрации. Идиома захвата эха не удается, так как он захватывает их всех. Спасибо!
AnneTheAgile
Это (второе лучшее) правильное решение! Чисто, быстро, элегантно, толково.
Evi1M4chine
+2 за поддержание реальности Я собирался сказать. Как многие люди могут игнорировать объединение echoвнутренней части функции в сочетании с подстановкой команд!
Энтони Ратледж
23

Вы также можете захватить вывод функции:

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

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

Выглядит странно, но лучше, чем использование глобальных переменных ИМХО. Передача параметров работает как обычно, просто поместите их в фигурные скобки или кавычки.

chiborg
источник
11
кроме альтернативного примечания по синтаксису, разве это не то же самое, что оп уже написал в своем собственном вопросе?
Лука Боррионе
очень ясно, спасибо!
bcag2
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]"

Тем не менее, все еще противный.

JMB
источник
3

То, как вы это делаете, - единственный способ сделать это, не выходя за рамки. Bash не имеет концепции типов возврата, только коды выхода и файловые дескрипторы (stdin / out / err и т. Д.)

Daenyth
источник
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

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

Конечно, это всего лишь соглашение. Вы можете не устанавливать соответствующее значение перед возвратом (отсюда мое соглашение всегда обнулять его в начале функции) или растоптать его значение, вызывая функцию снова (возможно, косвенно). Тем не менее, это соглашение, которое я считаю очень полезным, если я использую функции bash.

В противоположность ощущению, что это признак, например, «переход на perl», моя философия заключается в том, что условные обозначения всегда важны для управления сложностью любого языка.

Рон Берк
источник
2

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

Вы можете сделать это expr, хотя ShellCheck сообщает об этом как об устаревшем.

apennebaker
источник
Беда в том, что вещь справа от трубы - это недоработка. Так что myfunc | read OUTPUT ; echo $OUTPUTничего не дает. myfunc | ( read OUTPUT; echo $OUTPUT )действительно получает ожидаемое значение и разъясняет, что происходит с правой стороны. Но, конечно, выход не доступен там, где вам это нужно ...
Эд Рэндалл
2

Они являются ключевой проблемой любой схемы «именованных выходных переменных», в которой вызывающая сторона может передать имя переменной (с использованием evalили declare -n) непреднамеренного псевдонима, т. Е. Столкновения имен: с точки зрения инкапсуляции ужасно не иметь возможности добавлять или переименовывать локальная переменная в функции без предварительной проверки ВСЕХ вызывающих функций, чтобы убедиться, что они не хотят передавать то же имя, что и выходной параметр. (Или в другом направлении, я не хочу читать источник вызываемой функции, просто чтобы убедиться, что выходной параметр, который я собираюсь использовать, не является локальным в этой функции.)

Единственный способ обойти это - использовать одну выделенную выходную переменную, такую ​​как 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 для возврата объектов значений скаляров и массивов :

определение

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 .

Evi1M4chine
источник
-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=
agtsoft
источник