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

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

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

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

String variable = getSomeString();

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

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)

Ответ 1

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

Ответ 2

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

#!/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

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

#!/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

Ответ 3

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

Ответ 4

Как 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
}

Ответ 5

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 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    с именем переменной nameref в качестве аргумента, переменная, на которую ссылается ⋅    переменная nameref будет отключена.

Например ( РЕДАКТИРОВАТЬ 2: (спасибо Рон) namespaced (префикс) имя функции-внутренняя переменная, чтобы свести к минимуму столкновения внешних переменных, которые должны, наконец, правильно ответить, проблема, поднятая в комментариях Карстен):

# $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" builtin при использовании в функции делает объявленную переменную "local" по умолчанию, а "-n" также может использоваться с "local" .

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

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

$ return_a_string ret; echo $ret
The date is 20170104

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

Ответ 6

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

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

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

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

Ответ 7

Как упоминалось ранее, "правильный" способ вернуть строку из функции - это замена команды. В случае, когда функция также должна выводиться на консоль (как упоминает @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]"

Выполнение script без каких-либо параметров создает...

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

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

-Andy

Ответ 8

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

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"
}

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

Ответ 9

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

declare globalvar='some string'

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

string globalvar

echo "'${globalvar}'"

Это дает

'some other string'

Ответ 10

Чтобы проиллюстрировать мой комментарий к Энди, ответьте с помощью дополнительной обработки дескриптора файла, чтобы избежать использования /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]"

Все еще противно.

Ответ 11

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

Ответ 12

Адресация Вики Роннен, рассмотрев следующий код:

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.

Ответ 13

Вы можете echo строку, но поймайте ее по трубопроводу (|) на что-то еще.

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

Ответ 14

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

Ответ 15

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

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

Это echo es

tadaa

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

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

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

Ответ 16

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

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

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

  • Функция всегда назначает возвращаемое значение REPLY, а также может возвращать код выхода как обычно
  • С точки зрения вызывающего, возвращаемое значение может быть присвоено любой переменной (локальной или глобальной), включая REPLY (см. пример wrapper). Код выхода функции пропускается, поэтому их использование, например, a 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 "[email protected]"
}

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)

Ответ 17

bash, чтобы возвращать объекты скалярного и массива:

определение

url_parse() { # parse 'url' into: 'url_host', 'url_port', ...
   local "[email protected]" # 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
}

Ответ 18

[email protected]:~/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

[email protected]:~/temp$ ./fc
f2:10
after:a=3, res=