天天看点

如何从函数中返回一个字符串(或大数、负数)?

如何从函数中返回一个字符串(或大数、负数)?

知识点:shell中的return语句

如何从函数中返回一个字符串(或大数、负数)?

在 Shell 中,函数的返回值可以使用 return 命令来设置。return​ 命令用于将一个整数值作为函数的返回值,并将流程控制返回到函数的调用者处。以下是 return​ 命令的语法:

return [n]           

其中,n​ 是一个整数值,表示函数的返回值。如果省略 n​,则默认返回值为最后一条命令的退出状态码。在使用 return​ 命令时,可以将其作为函数中的任何一条命令来使用。

在函数返回时,返回值会被传递给函数的调用者。可以使用 $?​ 变量来获取函数的返回值,即 $?​ 变量会被设置为函数的返回值。以下是一个示例:

function add() {
    local sum=$(($1 + $2))
    return $sum
}

add 2 3
echo "函数的返回值为: $?"           

在这个示例中,add​ 函数接受两个参数,计算它们的和,并使用 return​ 命令将和作为返回值。然后,在函数调用完成后,使用 echo​ 命令输出函数的返回值,即 5。

需要注意的是,在 Shell 中,函数的返回值只能是一个整数值,而不能是其他类型的值。那如果需要函数返回非0-255的数字时该怎么办?接下来将会分享常用的几种方法。

方法1:捕获标准输出

你可以让函数将数据写入标准输出(stdout),然后让调用者捕获标准输出。

foo() {
   echo "this is my data"
}

x=$(foo)
printf 'foo returned "%s"\n' "$x"           

该方法的一个缺点是函数在子Shell中执行,这意味着在函数中进行的任何变量赋值等操作不会在调用者的环境中生效(并且还会因为fork()而增加速度开销)。这可能是一个问题,也可能不是一个问题,这取决于你的程序和函数的需求。另一个缺点是函数foo打印的所有内容都会被捕获并放入变量中。如果foo还写入了不打算作为返回值的内容,则会导致问题。为了将用户提示和/或错误消息与“返回”的数据隔离开来,请将它们重定向到不会被调用者捕获的stderr。

foo() {
   echo "运行 foo()..."  >&2        # 将用户提示和错误消息发送到 stderr
   echo "这是我的数据"              # 将在下面的变量中被赋值
}

x=$(foo)                               # 输出:运行 foo()...
printf 'foo 返回了 "%s"\n' "$x"      # 输出:foo 返回了 "这是我的数据"           

方法2:全局变量

你可以将数据赋值给全局变量,然后在调用者中引用这些变量。

foo() {
   return="this is my data"
}

foo
printf 'foo returned "%s"\n' "$return"           

与捕获stdout相比,该方法的优点是函数不会在子Shell中执行,这意味着函数调用速度更快。它还意味着副作用(如其他变量赋值和文件描述符更改)会影响脚本的其余部分。

该方法的缺点是,如果函数在子Shell中执行,则函数内部对全局变量的赋值对调用者是不可见的。这意味着你无法在管道中使用该函数,例如。

方法3:写入文件

你的函数可以将数据写入文件,然后调用者可以从该文件中读取数据。

foo() {
   echo "这是我的数据" > "$1"
}

# 这不是处理临时文件的可靠代码!
tmpfile=$(mktemp)   # GNU/Linux
foo "$tmpfile"
printf 'foo 返回 "%s"\n' "$(<"$tmpfile")"
rm "$tmpfile"
# 如果这是一个真实的程序,应该会有错误检查和陷阱。           

该方法的缺点显而易见:你需要管理一个临时文件,这总是不方便的;必须有某个可写的目录,并且要有足够的空间来存放其中的数据;等等。好处是,不管函数是否在子Shell中执行,它都能正常工作。

方法4:动态作用域变量

你可以使用作用域仅限于调用者和被调用函数的变量,而不是使用全局变量。

rand() {
   local max=$((32768 / $1 * $1))
   while (( (r=$RANDOM) >= max )); do :; done
   r=$(( r % $1 ))
}

foo() {
   local r
   rand 6
   echo "You rolled $((r+1))!"
}

foo
# 在全局作用域中,'r' 是不可见的。           

这种方法与使用全局变量的方法具有相同的优点和缺点,另外还有一个额外的优点,即全局变量命名空间不会被函数返回变量所“污染”。

然而,这种技术在递归函数中不起作用。

# 这个例子无法工作。
fact() {
   local r      # 用于保存我们调用的函数的返回值
   if (($1 == 1)); then
      r=1       # 把数据发送回给调用者
   else
      fact $(($1 - 1))       # 递归调用自身
      r=$((r * $1))          # 把数据发送回给调用者
   fi
}           

存在变量名称冲突-上面的示例尝试同时使用r作为两个冲突目的的变量。对于递归函数,请坚持使用全局变量技术。

# 这个例子是有效的。虽然不是计算阶乘的最佳方法,但是它是一个简单的递归函数示例。
fact() {
   if (($1 <= 1)); then
      r=1
   else
      fact "$(($1 - 1))"
      ((r *= $1))
   fi
}

fact 11
echo "$r"           

了解更多

如果您觉得文章内容对你有一点帮助可以关注我,我在头条平台会持续分享更多实用的shell技巧和最佳实践,如果想系统的快速学习shell的各种高阶用法和生产环境避坑指南可以看看《shell脚本编程最佳实践》专栏,专栏里有更多的实用小技巧和脚本代码分享。