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

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

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

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

String variable = getSomeString();

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

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)
вопрос задан 13.07.2010
Tomas F
2515 репутация

18 ответов


  • 237 рейтинг

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

    ответ дан Philipp, с репутацией 36110, 13.07.2010
  • 177 рейтинг

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

    #!/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
    
    ответ дан bstpierre, с репутацией 19662, 14.07.2010
  • 91 рейтинг

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

    ответ дан Vicky Ronnen, с репутацией 927, 24.01.2012
  • 30 рейтинг

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

          declare -n ref=$1
    

    внутри функции создает переменную nameref ref, значением которой является переменная имя передано в качестве первого аргумента. Ссылки и ссылки на ссылки обрабатываются как ссылки и присваивания переменной, имя которой было передано как $ 1. Если управляющая переменная в цикле for имеет атрибут nameref, список слов может быть списком переменных оболочки, а ссылка на имя будет устанавливается для каждого слова в списке, в свою очередь, при выполнении цикла. Переменным массива не может быть присвоен атрибут -n. Тем не менее, переменные nameref может ссылаться на переменные массива и подписанные переменные массива. Имена могут быть сбросить с помощью опции -n встроенной функции unset. В противном случае, если выполняется unset с именем переменной nameref в качестве аргумента, переменная, на которую ссылается⋅ переменная 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, с репутацией 486, 17.08.2016
  • 30 рейтинг

    Как и 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, с репутацией 427, 26.01.2013
  • 17 рейтинг

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

    #!/bin/bash
    function getSomeString() {
         echo "tadaa!"
    }
    
    return_var=$(getSomeString)
    echo $return_var
    # Alternative syntax:
    return_var=`getSomeString`
    echo $return_var
    

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

    ответ дан chiborg, с репутацией 15793, 17.08.2010
  • 11 рейтинг

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

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

    -Анди

    ответ дан Andy, с репутацией 119, 8.10.2013
  • 9 рейтинг

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

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

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

    ответ дан Tomasz Żuk, с репутацией 998, 12.08.2013
  • 8 рейтинг

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

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

    Это дает

    'some other string'
    
    ответ дан Fritz G. Mehner, с репутацией 11578, 13.07.2010
  • 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, с репутацией 343, 13.03.2014
  • 3 рейтинг

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

    ответ дан Daenyth, с репутацией 23140, 14.07.2010
  • 3 рейтинг

    Обращаясь к Vicky Ronnen с головой, учитывая следующий код:

    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.

    ответ дан Luca Borrione, с репутацией 10664, 8.05.2012
  • 2 рейтинг

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

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

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

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

    В отличие от чувства, что это признак, следует e. г. «Перейдите на Perl», моя философия заключается в том, что соглашения всегда важны для управления сложностью любого языка.

    ответ дан Ron Burk, с репутацией 4458, 5.05.2015
  • 1 рейтинг

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

    function getSomeString {
      REPLY="tadaa"
    }
    
    getSomeString
    echo $REPLY
    

    Это echo и

    tadaa
    

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

    declare result
    
    function getSomeString {
      result="tadaa"
    }
    
    getSomeString
    echo $result
    

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

    ответ дан Evi1M4chine, с репутацией 4490, 5.01.2016
  • 1 рейтинг

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

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

    ответ дан apennebaker, с репутацией 323, 20.11.2013
  • 0 рейтинг

    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
    }
    
    ответ дан Andrei Pozolotin, с репутацией 390, 22.10.2017
  • -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, с репутацией 11, 16.11.2012