идиомы для возврата нескольких значений в сценариях оболочки - PullRequest
21 голосов
/ 22 марта 2010

Существуют ли идиомы для возврата нескольких значений из функции bash в скрипте?

http://tldp.org/LDP/abs/html/assortedtips.html описывает, как отобразить несколько значений и обработать результаты (например, пример 35-17), но это сложно, если некоторые из возвращаемых значений являются строками с пробелами в.

Более структурированным способом возврата было бы присвоение глобальным переменным, например

foo () {
    FOO_RV1="bob"
    FOO_RV2="bill"
}

foo
echo "foo returned ${FOO_RV1} and ${FOO_RV2}"

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

Есть ли лучший способ? Я бы предпочел переносимость, но это, вероятно, не является реальным ограничением, если я должен указать #!/bin/bash.

Ответы [ 9 ]

12 голосов
/ 21 августа 2016

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

get_vars () {
  #...
  echo "value1" "value2"
}

read var1 var2 < <(get_vars)
echo "var1='$var1', var2='$var2'"

Но, конечно, он разрывается, как только в одном из значений появляется пробел. Вы можете изменить IFS и использовать специальный разделитель в echo вашей функции, но тогда результат не будет намного проще, чем другие предлагаемые решения.

10 голосов
/ 16 августа 2015

Этот вопрос был опубликован 5 лет назад, но у меня есть интересный ответ на этот пост. Я только начал изучать bash и столкнулся с той же проблемой, что и вы. Я думаю, что этот трюк может быть полезен:

#!/bin/sh

foo=""
bar=""

my_func(){
    echo 'foo="a"; bar="b"'
}

eval $(my_func)
echo $foo $bar
# result: a b

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

8 голосов
/ 22 марта 2010

Как бы я ни любил shell, вероятно, дело в том, что, как только вы добавляете произвольные структурированные данные, оболочка Unix bourne / posix не является правильным выбором.

Если есть символы, которые непроисходят внутри полей, а затем разделяются с одним из них.Классический пример - /etc/passwd, /etc/group и другие файлы, в которых в качестве разделителя полей используется двоеточие.

Если используется оболочка, которая может обрабатывать символ NUL внутри строк, то соединение с NUL и разделениена нем (через $ IFS или что-то еще) может хорошо работать.Но несколько общих оболочек, включая bash, ломаются на NUL.Тест был бы моим старым .sig:

foo=$'a\0b'; [ ${#foo} -eq 3 ] && echo "$0 rocks"

Даже если это сработало бы для вас, вы только что достигли одного из предупреждающих признаков того, что пришло время переключиться на более структурированный язык (Python, Perl, Ruby, Lua, Javascript ... выберите нужный яд).Ваш код, вероятно, станет трудно поддерживать;даже если вы можете, есть небольшая группа людей, которые поймут это достаточно хорошо, чтобы поддерживать это.

2 голосов
/ 27 января 2018

Еще один способ:

function get_tuple()
{
  echo -e "Value1\nValue2"
}

IFS=$'\n' read -d '' -ra VALUES < <(get_tuple)
echo "${VALUES[0]}" # Value1
echo "${VALUES[1]}" # Value2
2 голосов
/ 16 июля 2016

В той версии Bash, которая не поддерживает nameref (введена в Bash 4.3-alpha), я могу определить вспомогательную функцию, в которой возвращаемое значение назначается данной переменной. Это похоже на использование eval для такого же назначения переменных.

Пример 1

##  Add two complex numbers and returns it.
##  re: real part, im: imaginary part.
##
##  Helper function named by the 5th positional parameter
##  have to have been defined before the function is called.
complexAdd()
{
    local re1="$1" im1="$2" re2="$3" im2="$4" fnName="$5" sumRe sumIm

    sumRe=$(($re1 + $re2))
    sumIm=$(($im1 + $im2))

    ##  Call the function and return 2 values.
    "$fnName" "$sumRe" "$sumIm"
}

main()
{
    local fooRe='101' fooIm='37' barRe='55' barIm='123' bazRe bazIm quxRe quxIm

    ##  Define the function to receive mutiple return values
    ##  before calling complexAdd().
    retValAssign() { bazRe="$1"; bazIm="$2"; }
    ##  Call comlexAdd() for the first time.
    complexAdd "$fooRe" "$fooIm" "$barRe" "$barIm" 'retValAssign'

    ##  Redefine the function to receive mutiple return values.
    retValAssign() { quxRe="$1"; quxIm="$2"; }
    ##  Call comlexAdd() for the second time.
    complexAdd "$barRe" "$barIm" "$bazRe" "$bazIm" 'retValAssign'

    echo "foo = $fooRe + $fooIm i"
    echo "bar = $barRe + $barIm i"
    echo "baz = foo + bar = $bazRe + $bazIm i"
    echo "qux = bar + baz = $quxRe + $quxIm i"
}

main

Пример 2

##  Add two complex numbers and returns it.
##  re: real part, im: imaginary part.
##
##  Helper functions
##      getRetRe(), getRetIm(), setRetRe() and setRetIm()
##  have to have been defined before the function is called.
complexAdd()
{
    local re1="$1" im1="$2" re2="$3" im2="$4"

    setRetRe "$re1"
    setRetRe $(($(getRetRe) + $re2))

    setRetIm $(($im1 + $im2))
}

main()
{
    local fooRe='101' fooIm='37' barRe='55' barIm='123' bazRe bazIm quxRe quxIm

    ##  Define getter and setter functions before calling complexAdd().
    getRetRe() { echo "$bazRe"; }
    getRetIm() { echo "$bazIm"; }
    setRetRe() { bazRe="$1"; }
    setRetIm() { bazIm="$1"; }
    ##  Call comlexAdd() for the first time.
    complexAdd "$fooRe" "$fooIm" "$barRe" "$barIm"

    ##  Redefine getter and setter functions.
    getRetRe() { echo "$quxRe"; }
    getRetIm() { echo "$quxIm"; }
    setRetRe() { quxRe="$1"; }
    setRetIm() { quxIm="$1"; }
    ##  Call comlexAdd() for the second time.
    complexAdd "$barRe" "$barIm" "$bazRe" "$bazIm"

    echo "foo = $fooRe + $fooIm i"
    echo "bar = $barRe + $barIm i"
    echo "baz = foo + bar = $bazRe + $bazIm i"
    echo "qux = bar + baz = $quxRe + $quxIm i"
}

main
1 голос
/ 09 июля 2016

Более поздняя версия Bash поддерживает nameref . Используйте declare -n var_name, чтобы присвоить var_name атрибут nameref . nameref дает вашей функции возможность «передавать по ссылке», которая обычно используется в функциях C ++ для возврата нескольких значений. Согласно странице руководства Bash:

Переменной можно присвоить атрибут nameref , используя параметр -n для встроенных команд объявляют или local для создания nameref или ссылка на другую переменную. Это позволяет косвенно манипулировать переменными. Всякий раз, когда на переменную nameref ссылаются или присваивают ее, операция фактически выполняется над переменной, определенной значением переменной nameref . nameref обычно используется в функциях оболочки для ссылки на переменную, имя которой передается в качестве аргумента функции.

Ниже приведены некоторые примеры интерактивной командной строки.

Пример 1:

$ unset xx yy
$ xx=16
$ yy=xx
$ echo "[$xx] [$yy]"
[16] [xx]
$ declare -n yy
$ echo "[$xx] [$yy]"
[16] [16]
$ xx=80
$ echo "[$xx] [$yy]"
[80] [80]
$ yy=2016
$ echo "[$xx] [$yy]"
[2016] [2016]
$ declare +n yy # Use -n to add and +n to remove nameref attribute.
$ echo "[$xx] [$yy]"
[2016] [xx]

Пример 2:

$ func()
> {
>     local arg1="$1" arg2="$2"
>     local -n arg3ref="$3" arg4ref="$4"
> 
>     echo ''
>     echo 'Local variables:'
>     echo "    arg1='$arg1'"
>     echo "    arg2='$arg2'"
>     echo "    arg3ref='$arg3ref'"
>     echo "    arg4ref='$arg4ref'"
>     echo ''
> 
>     arg1='1st value of local assignment'
>     arg2='2st value of local assignment'
>     arg3ref='1st return value'
>     arg4ref='2nd return value'
> }
$ 
$ unset foo bar baz qux
$ 
$ foo='value of foo'
$ bar='value of bar'
$ baz='value of baz'
$ qux='value of qux'
$ 
$ func foo bar baz qux

Local variables:
    arg1='foo'
    arg2='bar'
    arg3ref='value of baz'
    arg4ref='value of qux'

$ 
$ {
>     echo ''
>     echo '2 values are returned after the function call:'
>     echo "    foo='$foo'"
>     echo "    bar='$bar'"
>     echo "    baz='$baz'"
>     echo "    qux='$qux'"
> }

2 values are returned after the function call:
    foo='value of foo'
    bar='value of bar'
    baz='1st return value'
    qux='2nd return value'
1 голос
/ 27 января 2013

Я бы пошел за решение, которое я предложил здесь , но вместо этого использовал переменную массива.Старые bash: они не поддерживают ассоциативные массивы.Например,

function some_func() # ARRVAR args...
{
    local _retvar=$1 # I use underscore to avoid clashes with return variable names
    local -a _out
    # ... some processing ... (_out[2]=xxx etc.)
    eval $_retvar='("${_out[@]}")'
}

Звонящий по сайту:

function caller()
{
    local -a tuple_ret # Do not use leading '_' here.
    # ...
    some_func tuple_ret "arg1"
    printf "  %s\n" "${tuple_ret[@]}" # Print tuple members on separate lines
}
1 голос
/ 22 марта 2010

вы можете использовать ассоциативные массивы, если у вас есть bash 4, например

declare -A ARR
function foo(){
  ...
  ARR["foo_return_value_1"]="VAR1"
  ARR["foo_return_value_2"]="VAR2"
}

Вы можете комбинировать их как строки.

function foo(){
  ...
  echo "$var1|$var2|$var3"
}

тогда всякий раз, когда вам нужно использовать эти возвращаемые значения,

ret="$(foo)"
IFS="|"
set -- $ret
echo "var1 one is: $1"
echo "var2 one is: $2"
echo "var3 one is: $3"
0 голосов
/ 22 марта 2010

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

Чтобы вернуть строку, один из способов может быть следующим:

function fun()
{
  echo "a+b"
}

var=`fun` # Invoke the function in a new child shell and capture the results
echo $var # use the stored result

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

...