它们是密钥问题的任何'named output variable'方案,其中调用者可以传入变量名称(无论是使用 eval 还是 declare -n )是无意的别名,即名称冲突:从封装的角度来看,很难无法添加或重命名本地函数中的变量没有检查 ALL 函数's callers first to make sure they'不想传递与输出参数相同的名称 . (或者在另一个方向上,我只是为了确保我打算使用的输出参数不是该函数中的本地 . )
但是,函数可以使用固定的输出变量 internally ,然后在顶部添加一些糖到 hide this fact from the caller ,就像我在下面的例子中使用 call 函数一样 . 认为这是一个概念证明,但关键点是
该函数始终将返回值分配给 REPLY ,并且还可以像往常一样返回退出代码
从调用者的角度来看,返回值可以分配给任何变量(本地或全局),包括 REPLY (参见 wrapper 示例) . 函数的退出代码被传递,因此在例如它们中使用它们 . if 或 while 或类似的结构按预期工作 .
从语法上讲,函数调用仍然是一个简单的语句 .
这样做的原因是因为 call 函数本身没有本地,并且不使用 REPLY 以外的任何变量,从而避免了任何名称冲突的可能性 . 在分配调用者定义的输出变量名称时,我们使用're effectively in the caller'范围(技术上与 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
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
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()
{
if [[ $1 != x ]]; then
local x
fi
x="Test"
eval "$1=\$x"
}
也许最优雅的解决方案是为函数返回值保留一个全局名称,并在您编写的每个函数中始终如一地使用它 .
6
上面的所有答案都忽略了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下,这个脚本也是一样的!
241
您可以使用全局变量:
declare globalvar='some string'
string ()
{
eval "$1='some other string'"
} # ---------- end of function string ----------
string globalvar
echo "'${globalvar}'"
这给了
'some other string'
-2
您拥有它的方式是在不破坏范围的情况下实现此目的的唯一方法 . Bash没有返回类型的概念,只有退出代码和文件描述符(stdin / out / err等)
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
#!/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
Edit :证明原始变量的值在函数中可用,正如@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
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 中的内容解释为shell特殊字符 . 我发现这是一个 order of magnitude faster 而不是捕获回声的 result=$(some_func "arg1") 成语 . 使用MSYS上的bash速度差异显得更加显着,其中从函数调用捕获的stdout几乎是灾难性的 .
发送局部变量是可以的,因为本地变量在bash中是动态范围的:
function another_func() # ARG
{
local result
some_func result "$1"
echo result is $result
}
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...
# $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
18 回答
我知道没有更好的方法 . Bash只知道写入stdout的状态代码(整数)和字符串 .
在我的程序中,按照惯例,这是预先存在的
$REPLY
变量的用途,read
用于该确切目的 .这个
echo
es但是为了避免冲突,任何其他全局变量都可以 .
如果这还不够,我推荐 Markarian451 的解决方案 .
为了说明我对Andy的回答的评论,使用额外的文件描述符操作来避免使用
/dev/tty
:但仍然很讨厌 .
它们是密钥问题的任何'named output variable'方案,其中调用者可以传入变量名称(无论是使用
eval
还是declare -n
)是无意的别名,即名称冲突:从封装的角度来看,很难无法添加或重命名本地函数中的变量没有检查 ALL 函数's callers first to make sure they'不想传递与输出参数相同的名称 . (或者在另一个方向上,我只是为了确保我打算使用的输出参数不是该函数中的本地 . )唯一的方法是使用单个专用输出变量,如
REPLY
(由Evi1M4chine建议)或类似Ron Burk建议的约定 .但是,函数可以使用固定的输出变量 internally ,然后在顶部添加一些糖到 hide this fact from the caller ,就像我在下面的例子中使用
call
函数一样 . 认为这是一个概念证明,但关键点是该函数始终将返回值分配给
REPLY
,并且还可以像往常一样返回退出代码从调用者的角度来看,返回值可以分配给任何变量(本地或全局),包括
REPLY
(参见wrapper
示例) . 函数的退出代码被传递,因此在例如它们中使用它们 .if
或while
或类似的结构按预期工作 .从语法上讲,函数调用仍然是一个简单的语句 .
这样做的原因是因为
call
函数本身没有本地,并且不使用REPLY
以外的任何变量,从而避免了任何名称冲突的可能性 . 在分配调用者定义的输出变量名称时,我们使用're effectively in the caller'范围(技术上与call
函数的范围相同),而不是在被调用函数的范围内 .输出:
考虑到以下代码,解决了Vicky Ronnen的问题:
会给
也许正常情况是使用
test_inside_a_func
函数中使用的语法,因此在大多数情况下你可以使用这两种方法,虽然捕获输出是更安全的方法总是在任何情况下工作,模仿你的函数的返回值可以找到其他语言,正如Vicky Ronnen
正确指出 .如前所述,从函数返回字符串的“正确”方法是使用命令替换 . 如果函数还需要输出到控制台(如上面提到的@Mani),请在函数的开头创建一个临时fd并重定向到控制台 . 在返回字符串之前关闭临时fd .
执行没有参数的脚本会产生......
希望这有助于人们
-Andy
其他人写道,最直接和最强大的解决方案是使用命令替换:
缺点是性能,因为这需要一个单独的过程 .
本主题中提出的另一种技术,即传递变量的名称以作为参数赋值,具有副作用,我不建议以其基本形式 . 问题是您可能需要函数中的一些变量来计算返回值,并且可能发生用于存储返回值的变量的名称将干扰其中一个:
当然,您可能不会将函数的内部变量声明为本地变量,但实际上您应该始终这样做,否则如果存在具有相同名称的变量,您可能会意外地从父作用域覆盖不相关的变量 . .
一种可能的解决方法是将传递的变量显式声明为全局变量:
如果名称“x”作为参数传递,则函数体的第二行将覆盖先前的本地声明 . 但是名称本身可能仍会干扰,因此如果您打算在将返回值写入之前使用先前存储在传递变量中的值,请注意必须在最开始时将其复制到另一个局部变量中;否则结果将无法预测!此外,这只适用于最新版本的BASH,即4.2 . 更多可移植代码可能使用显式条件具有相同效果的构造:
也许最优雅的解决方案是为函数返回值保留一个全局名称,并在您编写的每个函数中始终如一地使用它 .
上面的所有答案都忽略了bash手册中的内容 .
在函数内声明的所有变量都将与调用环境共享 .
所有声明为本地的变量都不会被共享 .
示例代码
并输出
同样在pdksh和ksh下,这个脚本也是一样的!
您可以使用全局变量:
这给了
您拥有它的方式是在不破坏范围的情况下实现此目的的唯一方法 . Bash没有返回类型的概念,只有退出代码和文件描述符(stdin / out / err等)
我认为,所有选项都已列举 . 选择一个可能归结为您的特定应用的最佳风格问题,并且在这种情况下,我想提供一种我认为有用的特定风格 . 在bash中,变量和函数不在同一名称空间中 . 因此,处理与函数值相同名称的变量是一种惯例,我发现如果我严格应用它,可以最大限度地减少名称冲突并增强可读性 . 现实生活中的一个例子:
并且,使用这些函数的示例:
如您所见,返回状态可供您在需要时使用,如果不需要则可忽略 . 同样可以使用或忽略"returned"变量,但当然仅在调用函数之后 .
当然,这只是一个惯例 . 您可以自由地在返回之前设置关联值(因此我的约定总是在函数开始时将其置零)或者通过再次调用函数(可能是间接的)来践踏其值 . 不过,如果我发现自己大量使用bash函数,这是一个非常有用的约定 .
与情绪相反,这是一个标志,例如“转向perl”,我的理念是,约定对于管理任何语言的复杂性始终是重要的 .
您还可以捕获函数输出:
看起来很奇怪,但比使用全局变量恕我直言更好 . 传递参数照常工作,只需将它们放在大括号或反引号中即可 .
你可以
echo
一个字符串,但通过管道(|
)该函数来捕获它 .您可以使用
expr
执行此操作,但ShellCheck将此用法报告为已弃用 .您可以让函数将变量作为第一个arg,并使用要返回的字符串修改变量 .
打印“foo bar rab oof” .
Edit :在适当的地方添加引用以允许字符串中的空格来解决@Luca Borrione的评论 .
Edit :作为演示,请参阅以下程序 . 这是一个通用解决方案:它甚至允许您将字符串接收到本地变量中 .
这打印:
Edit :证明原始变量的值在函数中可用,正如@Xichen Li在评论中错误批评的那样 .
这给出了输出:
与上面的bstpierre一样,我使用并建议使用显式命名输出变量:
注意使用引用$ . 这将避免将
$result
中的内容解释为shell特殊字符 . 我发现这是一个 order of magnitude faster 而不是捕获回声的result=$(some_func "arg1")
成语 . 使用MSYS上的bash速度差异显得更加显着,其中从函数调用捕获的stdout几乎是灾难性的 .发送局部变量是可以的,因为本地变量在bash中是动态范围的:
Bash,自版本4.3,2014年2月(?),明确支持引用变量或名称引用(namerefs),超出“eval”,具有相同的有益性能和间接效果,并且在脚本中可能更清晰,也更难要“忘记'eval'并且必须修复此错误”:
并且:
例如( EDIT 2 :(谢谢Ron)命名空间(前缀)函数内部变量名称,以最小化外部变量冲突,最终应该正确回答,Karsten在评论中提出的问题):
并测试此示例:
请注意,bash“declare”builtin在函数中使用时,默认情况下声明变量为“local”,“ - n”也可以与“local”一起使用 .
我更喜欢将“重要声明”变量与“无聊的本地”变量区分开来,因此以这种方式使用“declare”和“local”作为文档 .
EDIT 1 - (回复Karsten的评论) - 我不能再在下面添加评论了,但Karsten的评论让我思考,所以我做了以下测试,哪些工作精细,AFAICT - Karsten如果你读到这个,请提供一套确切的从命令行测试步骤,显示您认为存在的问题,因为以下步骤可以正常工作:
(在将上述函数粘贴到bash术语之后,我刚刚运行了这个 - 正如您所看到的,结果可以正常工作 . )
bash 模式返回 scalar 和 array 值对象:
定义
调用