我想从Bash函数返回一个字符串。
我将用java写这个例子来说明我想做什么:
public String getSomeString() {
return "tadaa";
}
String variable = getSomeString();
下面的示例可以在bash中工作,但是是否有更好的方法来做到这一点?
function getSomeString {
echo "tadaa"
}
VARIABLE=$(getSomeString)
我想从Bash函数返回一个字符串。
我将用java写这个例子来说明我想做什么:
public String getSomeString() {
return "tadaa";
}
String variable = getSomeString();
下面的示例可以在bash中工作,但是是否有更好的方法来做到这一点?
function getSomeString {
echo "tadaa"
}
VARIABLE=$(getSomeString)
当前回答
您可以回显字符串,但通过将函数(|)连接到其他函数来捕获它。
您可以使用expr来实现,不过ShellCheck报告这种用法已弃用。
其他回答
They key problem of any 'named output variable' scheme where the caller can pass in the variable name (whether using eval or declare -n) is inadvertent aliasing, i.e. name clashes: From an encapsulation point of view, it's awful to not be able to add or rename a local variable in a function without checking ALL the function's callers first to make sure they're not wanting to pass that same name as the output parameter. (Or in the other direction, I don't want to have to read the source of the function I'm calling just to make sure the output parameter I intend to use is not a local in that function.)
解决这个问题的唯一方法是使用一个单独的专用输出变量,比如REPLY(由ev1m4chine建议),或者使用Ron Burk建议的约定。
但是,也可以让函数在内部使用固定的输出变量,然后在上面添加一些糖来对调用者隐藏这一事实,就像我在下面的示例中对call函数所做的那样。把这看作是概念的证明,但关键是
该函数总是将返回值赋给REPLY,也可以像往常一样返回退出码 从调用者的角度来看,返回值可以分配给任何变量(本地或全局),包括REPLY(参见包装器示例)。函数的退出码是传递的,因此在if或while或类似结构中使用它们可以正常工作。 从语法上讲,函数调用仍然是一条简单的语句。
这样做的原因是调用函数本身没有局部变量,并且除了REPLY之外不使用其他变量,从而避免了任何名称冲突的可能性。当调用方定义的输出变量名被赋值时,我们实际上处于调用方的作用域(技术上讲,在调用函数的相同作用域中),而不是在被调用函数的作用域中。
#!/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)
最直接和健壮的解决方案是使用命令替换,就像其他人写的那样:
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"
}
If name "x" is passed as an argument, the second row of the function body will overwrite the previous local declaration. But the names themselves might still interfere, so if you intend to use the value previously stored in the passed variable prior to write the return value there, be aware that you must copy it into another local variable at the very beginning; otherwise the result will be unpredictable! Besides, this will only work in the most recent version of BASH, namely 4.2. More portable code might utilize explicit conditional constructs with the same effect:
assign()
{
if [[ $1 != x ]]; then
local x
fi
x="Test"
eval "$1=\$x"
}
也许最优雅的解决方案是为函数返回值和保留一个全局名称 在您编写的每个函数中一致地使用它。
我想,所有的选择都已经列举出来了。选择一种可以归结为最适合您的特定应用程序的样式,因此,我想提供一种我认为有用的特定样式。在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”,我的哲学是,对于管理任何语言的复杂性,约定总是很重要的。
没有比这更好的方法了。Bash只知道写入标准输出的状态码(整数)和字符串。
提醒薇姬·罗嫩,考虑一下下面的代码
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正确地指出了这一点。