如何从Bash函数返回字符串值


461

我想从Bash函数返回一个字符串。

我将用Java编写示例以显示我想做的事情:

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

String variable = getSomeString();

下面的示例在bash中有效,但是有更好的方法吗?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)

4
顺便说一句,function funcName {POSIX之前的旧语法是从早期的ksh继承的(bash在语义上有区别,bash无法兑现)。funcName() {,如果没有function,则应改用;参见wiki.bash-hackers.org/scripting/obsolete
Charles Duffy

该链接说使用NAME()COMPOUND-CMD或函数NAME {CMDS; } function myFunction { blah; }很好;那function myFunction() { blah }不是很好,即在关键字函数中使用括号。
威尔

Answers:


290

我没有更好的方法了。Bash仅知道状态代码(整数)和写入标准输出的字符串。


15
+1 @ tomas-f:您必须非常小心此函数“ getSomeString()”中的内容,因为拥有任何最终会回显的代码都意味着您将获得错误的返回字符串。
玛尼2012年

11
这是完全错误的。您可以在返回变量内返回任意数据。这显然是更好的方法。
Evi1M4chine

36
@ Evi1M4chine,嗯...不,你不能。您可以设置一个全局变量并将其称为“ return”,就像我在脚本中看到的那样。但这是按照惯例,实际上不以编程方式与您的代码执行联系在一起。“显然是更好的方法”?不。命令替换要更加明确和模块化。
通配符

6
如果问题与命令有关,则“命令替换要更加明确和模块化”。这个问题是如何从bash函数返回字符串!自Bash 4.3(2014?)起,就有一种内置的方法可以执行OP所要求的操作-请参阅下面的答案。
zenaan

4
最初的问题包含最简单的方法,并且在大多数情况下都可以正常工作。Bash返回值可能应该称为“返回代码”,因为它们不太像脚本中的标准返回值,而更像是数字shell命令退出代码(您可以执行诸如此类的操作somefunction && echo 'success')。如果您将一个函数想像成另一个命令,那是很有意义的。命令不会在退出时“返回”状态码以外的任何东西,但是在您可以捕获的同时,它们可能会输出一些东西。
Beejor

193

您可以让该函数将变量作为第一个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李在评论批评。

#!/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

1
这个答案很棒!参数可以通过引用传递,类似于C ++中的思想。
黄Huang

4
收到专家关于该答案的答复,将是很好的。我从未见过在脚本中使用过它,也许是有充分理由的。无论如何:那是+1应该应该被选为正确答案
John

这不是fgm以简化的方式写的答案吗?如果字符串foo包含空格,则此方法将不起作用,而fgm如他所显示的,空格将..
卡·博里奥内

4
@XichenLi:感谢您对我的投票发表评论;请看我的编辑。您可以使用来获取函数中变量的初始值\$$1。如果您正在寻找不同的东西,请告诉我。
bstpierre 2012年

1
@timiscoding可以用来解决printf '%q' "$var"。%q是用于shell转义的格式字符串。然后将其原始传递。
bb010g 2015年

99

上面的所有答案都忽略了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下,此脚本也可以实现!


10
这个答案确实有其优点。我来到这里的时候以为我想从函数中返回一个字符串。这个答案使我意识到那只是我的C#习惯而已。我怀疑其他人可能也有同样的经历。
LOAS

4
@ElmarZander你错了,这是完全相关的。这是进入函数作用域值的全局范围的一种简单方法,并且某些人会认为这比重新定义bstpierre概述的全局变量的eval方法更好/更简单。
KomodoDave

local无法移植到非bash脚本中,这是某些人避免使用它的原因之一。
唐明亮

问题:循环中的变量呢?
阿努

1
在Mac($ bash --version GNU bash版本3.2.57(1)-发行版(x86_64-apple-darwin14)版权所有(C)2007 Free Software Foundation,Inc.)上,正确的是匹配的全局变量是已初始化,但是当我尝试在另一个函数f2中对同一变量产生副作用时,该副作用不会持续存在。因此,它看起来非常不一致,因此对我的用法不利。
AnneTheAgile 2015年

45

Bash从4.3版(2014年2月)开始,对引用变量或名称引用(namerefs)提供了明确的支持,除了“ 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...

并且:

参数

可以使用-n选项为声明或本地内置命令(请参见下面的声明和本地说明)为变量分配nameref属性,以创建nameref或对另一个变量的引用。这允许变量被间接操纵。每当引用或分配nameref变量时,实际上都会对nameref变量的值指定的变量执行操作。在外壳函数中,通常使用nameref来引用变量,该变量的名称作为该函数的参数传递。例如,如果将变量名作为第一个参数传递给shell函数,则运行

      declare -n ref=$1

函数内部将创建一个nameref变量ref,其值是作为第一个参数传递的变量名称。对ref的引用和赋值被视为对名称作为$ 1传递的变量的引用和赋值。如果for循环中的控制变量具有nameref属性,则单词列表可以是shell变量列表,并且在执行循环时,将依次为列表中的每个单词建立名称引用。数组变量不能赋予-n属性。但是,nameref变量可以引用数组变量和下标数组变量。可以使用-n选项对未设置的内置变量名进行设置。否则,如果使用nameref变量的名称作为参数执行unset,

例如(EDIT 2:(感谢Ron)对函数内部变量名称进行命名(前缀),以最大程度地减少外部变量冲突,该冲突最终应能正确地解决,由Karsten在注释中引起的问题):

# $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”在函数中使用时,默认情况下使声明的变量为“ local”,“-n”也可以与“ local”一起使用。

我更喜欢将“重要的声明”变量与“无聊的本地”变量区分开,因此以这种方式使用“声明”和“本地”充当文档。

编辑1-(在下面由Karsten回复评论)-我无法再在下面添加评论,但是Karsten的评论让我开始思考,所以我进行了以下测试,即:FINES FINE,AFAICT-Karsten,如果您阅读此书,请提供准确的设置命令行中的测试步骤,显示您认为存在的问题,因为以下步骤可以正常工作:

$ return_a_string ret; echo $ret
The date is 20170104

(在将上述函数粘贴到bash术语中后,我才运行此代码-如您所见,结果运行良好。)


4
我希望这能渗透到顶部。评估应该是不得已的方法。值得一提的是,nameref变量仅在bash 4.3(根据changelog)(2014年2月发布?)之后可用。如果要考虑可移植性,这一点很重要。请引用bash手册,它涉及declare在函数内部创建局部变量的事实(该信息未由给出help declare):“ ...在函数中使用时,声明和排版会使每个名称成为局部名称,就像使用local命令一样,除非-提供了g选项...”
init_js,2016年

2
这具有与评估解决方案相同的别名问题。调用函数并传递输出变量的名称时,必须避免传递在调用函数中本地使用的变量的名称。就封装而言,这是一个主要问题,因为如果没有任何函数调用者可能想使用该名称作为输出参数,就不能在函数中添加或重命名新的局部变量。
卡尔斯滕

1
@Karsten同意。在这两种情况下(eval和nameref),您可能必须选择其他名称。与eval相比,nameref方法的一个优点是不必处理转义字符串。当然,您总是可以做类似的操作K=$1; V=$2; eval "$A='$V'";,但是会犯一个错误(例如,参数为空或省略),这样会更加危险。@zenaan如果您选择“消息”作为返回变量名称而不是“ ret”,则@Karsten提出的问题将适用。
init_js

3
大概必须从一开始就设计一个函数来接受nameref参数,因此函数作者应意识到名称冲突的可能性,并可以使用一些典型的约定来避免这种情况。例如,在函数X中,使用约定“ X_LOCAL_name”命名局部变量。
罗恩·伯克

34

像上面的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")。在MSYS上使用bash时,速度差异似乎更加明显,因为从函数调用捕获stdout几乎是灾难性的。

可以发送局部变量,因为局部变量在bash中动态作用域:

function another_func() # ARG
{
   local result
   some_func result "$1"
   echo result is $result
}

4
这对我有帮助,因为我喜欢将多个echo语句用于调试/记录目的。捕获回声的习语失败了,因为它捕获了所有回声。谢谢!
AnneTheAgile 2015年

这是(第二好的)适当的解决方案!干净,快速,优雅,明智。
Evi1M4chine

+2用于保持真实。我正要说。有那么多人怎么会忽略将echo函数的内部与命令替换相结合!
安东尼·鲁特里奇

22

您还可以捕获函数输出:

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

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

看起来很奇怪,但是比使用全局变量恕我直言更好。传递参数照常工作,只需将其放在花括号或反引号中即可。


11
除了可选的语法注释,这是否与op在他自己的问题中已经写的完全一样?
卡·博里奥内

12

正如其他人所写,最直接,最可靠的解决方案是使用命令替换:

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

当然,您可能不将函数的内部变量声明为局部变量,但实际上应始终这样做,否则,如果存在一个同名变量,则可能会意外覆盖父作用域中不相关的变量。

一种可能的解决方法是将传递的变量显式声明为global:

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

也许最优雅的解决方案只是为函数返回值保留一个全局名称,并在您编写的每个函数中一致地使用它。


3
这^^^。evaldeclare -n解决方案都存在一个大问题,即破坏封装的无意别名。result对所有输出参数使用单个专用变量名的解决方法似乎是唯一不需要解决方案的函数,该函数不需要函数知道所有调用者即可避免冲突。
卡尔斯滕

12

如前所述,从函数返回字符串的“正确”方法是使用命令替换。如果该功能也需要输出到控制台(如@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]

希望这可以帮助人们

-安迪


6
它有其用途,但是总的来说,您应该避免显式重定向到控制台。输出可能已经重定向,或者脚本可能在不存在tty的上下文中运行。您可以通过3>&1在脚本的开头进行复制,然后在函数中使用&1 &3另一个占位符来解决&4此问题。丑陋的,但是。
2014年

8

您可以使用全局变量:

declare globalvar='some string'

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

string globalvar

echo "'${globalvar}'"

这给

'some other string'

6

为了说明我对Andy的答案的评论,并通过其他文件描述符操作来避免使用/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]"

仍然令人讨厌。


3

您拥有它的方式是在不超出范围的前提下执行此操作的唯一方法。Bash没有返回类型的概念,只是退出代码和文件描述符(stdin / out / err等)


3

解决玉萍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正确指出的那样,以其他语言查找。


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函数,这仍然是一个非常有用的约定。

与人们认为这是一个标志(例如“移至Perl”)的观点相反,我的哲学是约定对于管理任何语言的复杂性始终很重要。


2

您可以输入echo一个字符串,但可以通过管道(|将函数传递)来。

您可以使用来完成此操作expr,尽管ShellCheck报告此用法已弃用。


麻烦的是,管道右边的东西是一个子壳。所以myfunc | read OUTPUT ; echo $OUTPUT什么都不产生。myfunc | ( read OUTPUT; echo $OUTPUT )确实获得了期望值并阐明了右侧发生的情况。但是当然,在您需要的地方也无法使用输出
Ed Randall

2

它们的关键问题是调用者可以传入变量名称(无论使用eval还是declare -n)的任何“命名输出变量”方案都是无意的别名,即名称冲突:从封装的角度来看,无法添加或重命名是很糟糕的函数中的局部变量而不检查全部函数的调用方,以确保他们不想传递与输出参数相同的名称。(或者换句话说,我不想读取我正在调用的函数的源代码,只是确保要使用的输出参数不是该函数中的局部参数。)

解决此问题的唯一方法是使用单个专用输出变量REPLY(如Evi1M4chine所建议)或一种约定(如Ron Burk所建议的约定

但是,有可能让函数在内部使用固定的输出变量,然后在顶部添加一些糖以向调用者隐藏此事实,如call下面示例中对函数所做的那样。认为这是一个概念证明,但关键点是

  • 该函数始终将返回值分配给REPLY,并且还可以照常返回退出代码
  • 从调用者的角度来看,可以将返回值分配给任何变量(本地或全局),包括REPLY(请参见wrapper示例)。该函数的退出代码是通过的,因此可以在例如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)

1

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
}

0

在我的程序中,按照约定,这就是预先存在的$REPLY变量read用于此目的的目的。

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

echoES

tadaa

但是为了避免冲突,任何其他全局变量都可以。

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

如果那还不够,我推荐Markarian451的解决方案。


-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=
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.