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

Я хотел бы вернуть строку из функции Bash.

Я напишу пример на java, чтобы показать, что я хотел бы сделать:

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

String variable = getSomeString();

пример ниже работает в bash, но есть ли лучший способ сделать это?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)

18 ответов


нет лучшего способа, я знаю. Bash знает только коды состояния (целые числа) и строки, записанные в stdout.


вы можете заставить функцию взять переменную в качестве первого arg и изменить переменную со строкой, которую вы хотите вернуть.

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "='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 "='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

редактировать: демонстрация того, что значение исходной переменной is доступно в функции, так как было неправильно раскритиковано @Xichen Li в комментарии.

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "echo in pass_back_a_string, original  is $"
    eval "='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

все ответы выше игнорируют то, что было указано на главной странице 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 этот скрипт делает то же самое!


как bstpierre выше, я использую и рекомендую использовать явно именование выходных переменных:

function some_func() # OUTVAR ARG1
{
   local _outvar=
   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 ""
   echo result is $result
}

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

      declare -n ref=

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

например (EDIT 2: (спасибо Ron) namespaced (prefixed) функция-имя внутренней переменной, чтобы свести к минимуму столкновения внешних переменных, которые должны, наконец, ответить правильно, вопрос, поднятый в комментариях Карстена):

#  : string; your variable to contain the return value
function return_a_string () {
    declare -n ret=
    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 "builtin, при использовании в функции, делает объявленную переменную "локальной" по умолчанию, и "- n" также может использоваться с "местный."

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

правка 1 - (ответ на комментарий ниже Карстена) - я больше не могу добавлять комментарии ниже, но комментарий Карстена заставил меня задуматься, поэтому я сделал следующий тест, который отлично работает, AFAICT-Карстен если Вы читаете это, пожалуйста, предоставьте точный набор шагов теста из командной строки, показывая проблема, которую вы предполагаете, существует, потому что эти следующие шаги работают просто отлично:

$ return_a_string ret; echo $ret
The date is 20170104

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


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

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

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

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


Как уже упоминалось ранее, "правильный"способ возврата строки из функции - это замена команды. В случае, если функция также должна выводиться на консоль (как упоминалось выше @Mani), создайте временный fd в начале функции и перенаправьте на консоль. Закройте временный fd перед возвращением строки.

#!/bin/bash
# file:  func_return_test.sh
returnString() {
    exec 3>&1 >/dev/tty
    local s=
    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]

надеюсь, это поможет людям

-Энди


самое простое и надежное решение - использовать замену команд, как писали другие люди:

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

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

минус в производительности, как это требует отдельного процесса.

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

assign()
{
    local x
    x="Test"
    eval "=$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 
    x="Test"
    eval "=$x"
}

если в качестве аргумента передается имя "x", вторая строка тела функции перезапишет предыдущее локальное объявление. Но сами имена могут по-прежнему мешать, поэтому, если вы собираетесь использовать значение, ранее сохраненное в переданной переменной, до записи возвращаемого значения там, имейте в виду, что вы должны скопировать его в другую локальную переменную в самом начале; в противном случае результат будет непредсказуемым! Кроме того, это будет работать только в самых последних версия BASH, а именно 4.2. Более переносимый код может использовать явные условные конструкции с тем же эффектом:

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

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


вы можете использовать глобальную переменную:

declare globalvar='some string'

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

string globalvar

echo "'${globalvar}'"

это дает

'some other string'

чтобы проиллюстрировать мой комментарий к ответу Энди, с дополнительной манипуляцией файловым дескриптором, чтобы избежать использования /dev/tty:

#!/bin/bash

exec 3>&1

returnString() {
    exec 4>&1 >&3
    local s=
    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]"

все еще противно, хотя.


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


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

function use_global
{
    eval "='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 правильно указывать.


все варианты были перечислены, я думаю. Выбор одного из них может сводиться к вопросу о лучшем стиле для вашего конкретного приложения, и в этом ключе я хочу предложить один конкретный стиль, который я нашел полезным. В 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=""
}

и, пример использования таких функций:

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.


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

единственный способ обойти это-использовать одну выделенную выходную переменную, такую как REPLY (как было предложено Evi1M4chine) или конвенции, как предусмотрено Рон Бурк.

тем не менее, возможно, что функции используют фиксированную выходную переменную внутри, а затем добавить некоторые сахар сверху до скрыть этот факт от абонента, как я сделал с


в моих программах, по соглашению, это то, что существовавшие ранее $REPLY переменная для которой read использует для этой цели.

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

этой echoes

tadaa

но чтобы избежать конфликтов, подойдет любая другая глобальная переменная.

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

если этого недостаточно, я рекомендую Markarian451’ы.


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

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


Баш шаблон для возврата как скаляр и массив значение объекта:

определение

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
}

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

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

function f1 {
    res=$[(+)*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=