Ответ 1
Нет лучшего способа узнать. Bash знает только коды состояния (целые числа) и строки, записанные в стандартный вывод.
Я хочу вернуть строку из функции Bash.
Я напишу пример в java, чтобы показать, что я хотел бы сделать:
public String getSomeString() {
return "tadaa";
}
String variable = getSomeString();
Пример ниже работает в bash, но есть ли лучший способ сделать это?
function getSomeString {
echo "tadaa"
}
VARIABLE=$(getSomeString)
Нет лучшего способа узнать. Bash знает только коды состояния (целые числа) и строки, записанные в стандартный вывод.
Вы могли бы заставить функцию принять переменную как первый 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
Все ответы выше игнорируют то, что было указано на странице руководства 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 делает то же самое!
Как 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
}
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 - как вы можете видеть, результат работает просто отлично.)
Вы также можете записать вывод функции:
#!/bin/bash
function getSomeString() {
echo "tadaa!"
}
return_var=$(getSomeString)
echo $return_var
# Alternative syntax:
return_var=`getSomeString`
echo $return_var
Выглядит странно, но лучше, чем использование глобальных переменных IMHO. Передача параметров работает как обычно, просто поместите их в скобки или обратные метки.
Как упоминалось ранее, "правильный" способ вернуть строку из функции - это замена команды. В случае, когда функция также должна выводиться на консоль (как упоминает @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
Самое простое и надежное решение - использовать подстановку команд, как писали другие люди:
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"
}
Возможно, самым элегантным решением является резервирование одного глобального имени для возвращаемых значений функции и используйте его последовательно в каждой функции, которую вы пишете.
Вы можете использовать глобальную переменную:
declare globalvar='some string'
string ()
{
eval "$1='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=$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]"
Все еще противно.
То, как у вас есть, это единственный способ сделать это, не нарушая рамки. Bash не имеет понятия возвращаемых типов, просто коды выхода и дескрипторы файлов (stdin/out/err и т.д.)
Адресация Вики Роннен, рассмотрев следующий код:
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
.
Вы можете echo
строку, но поймайте ее по трубопроводу (|
) на что-то еще.
Вы можете сделать это с помощью expr
, хотя ShellCheck сообщает об этом использовании как устаревшее.
Все параметры перечислены, я думаю. Вы можете выбрать подходящий стиль для вашего конкретного приложения, и в этом ключе я хочу предложить один конкретный стиль, который я нашел полезным. В 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", моя философия состоит в том, что соглашения всегда важны для управления сложностью любого языка.
В моих программах, по соглашению, это то, к чему предназначена ранее существовавшая переменная $REPLY
, для которой read
используется именно для этой цели.
function getSomeString {
REPLY="tadaa"
}
getSomeString
echo $REPLY
Это echo
es
tadaa
Но чтобы избежать конфликтов, любая другая глобальная переменная будет делать.
declare result
function getSomeString {
result="tadaa"
}
getSomeString
echo $result
Если этого недостаточно, я рекомендую Markarian451 s решение.
Они являются ключевой проблемой любой схемы "именованной выходной переменной", в которой вызывающий может передать имя переменной (с использованием 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)
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
}
[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=