Как вернуть строковое значение из функции Bash - PullRequest
425 голосов
/ 13 июля 2010

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

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

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

String variable = getSomeString();

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

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)

Ответы [ 18 ]

266 голосов
/ 13 июля 2010

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

186 голосов
/ 14 июля 2010

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

#!/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
95 голосов
/ 24 января 2012

Все ответы выше игнорируют то, что было сказано на странице руководства bash.

  • Все переменные, объявленные внутри функции, будут совместно использоваться с вызывающей средой.
  • Все объявленные переменныеlocal не будет общим.

Пример кода

#!/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 этот скрипт делает то же самое!

42 голосов
/ 17 августа 2016

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...

, а также:

PARAMETERS

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

      declare -n ref=$1

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

Например ( EDIT 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 - как видите, результат работает просто отлично.)

34 голосов
/ 27 января 2013

Как и 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
}
21 голосов
/ 17 августа 2010

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

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

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

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

12 голосов
/ 09 октября 2013

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

10 голосов
/ 13 августа 2013

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

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

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

8 голосов
/ 13 июля 2010

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

declare globalvar='some string'

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

string globalvar

echo "'${globalvar}'"

Это дает

'some other string'
6 голосов
/ 13 марта 2014

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...