函数

函数 属于 Bash

{other: "%{count} 个练习"}

关于 函数

许多 Bash 脚本都用严格的命令式风格编写:先执行一条命令,再执行另一条命令,依此类推。 有时你需要把一系列在概念上完成同一件事的命令组合在一起。 这时就要用到函数了。

定义函数

像这样定义一个函数:

my_function () {
    COMMANDS
}

那对空括号只是表示你在定义一个函数。 括号里面不写任何东西。

函数的参数

函数一旦定义好,就和任何其他命令(内置的或不是内置的)一样。 和任何命令一样,你可以向函数提供实参。 在函数内部,你通过位置参数$1、$2 等来访问这些实参。 (回想一下,我们在 变量 概念里学过位置参数。)

Advanced

特殊参数$0在函数内部不会改变,它仍然是正在执行的脚本的名字。 当前正在执行的函数可以用$FUNCNAME变量访问自己的名字。

参见手册中的 3.4.2 特殊参数。

变量

你可以在函数内部定义变量。 如果你用local命令声明变量,变量的作用域就只限于当前函数(以及由它调用的任何函数)。 否则,变量就处于全局作用域中。

局部变量可以和全局变量同名。 在这种情况下,局部变量会“遮蔽”全局变量。 例如,在函数里声明的局部变量会隐藏同名的全局变量:引用和赋值都指向这个局部变量,全局变量保持不变。 函数返回后,全局变量又重新可见。

x=5

myfunc () {
    local x=100
    echo "in my function, $x == 100"
}

echo "in the global scope, $x == 5"

myfunc

echo "back in the global scope, $x == 5"

输出结果为

in the global scope, 5 == 5
in my function, 100 == 100
back in the global scope, 5 == 5

在函数内部,你可以访问调用者作用域中的变量。 也就是说,你既可以使用全局变量,也可以使用在调用者中声明的局部变量(或在调用调用者的某个函数中声明的局部变量)。

Advanced

严格来说,“全局”并不是准确的说法。 在函数中展开某个变量时,Bash 会沿着调用栈一路向上查找,直到全局作用域,寻找声明了该变量名的函数。

下面这个例子改编自手册的 Shell 函数 一节:

func1() {
    local var='func1 local'
    func2
}

func2() {
    echo "In func2, var = $var"
}

var=global
func1
func2

输出为:

In func2, var = func1 local
In func2, var = global

同样,给变量赋值时,赋值会发生在声明该变量的作用域里。 这种“远距离作用”会让代码难以理解,因为变量究竟在哪儿被赋值并不总是那么明显。

Advanced

可以用 FUNCNAME数组变量 来查看调用栈。

返回值

函数和任何命令一样,都有一个退出状态。 默认情况下,函数的状态就是最后执行的那条命令的退出状态。

你可以用return命令以特定的退出状态从函数中返回。

check_password () {
    if [[ $1 == "secret" ]]; then
        return 0
    else
        return 1
    fi
}

read -sp "Enter your password: " pass

if check_password "$pass"; then
    echo "Correct!"
else
    echo "Wrong password."
fi

不带参数使用return时,返回的是最后执行的那条命令的状态。

Note

注意,check_password函数可以简化成:

check_password () { [[ $1 == "secret" ]]; }
  1. [[...]]条件结构也有退出状态:0表示“真”,1表示“假”。
  2. {...}分组结构在结尾的右大括号之前必须有换行或分号。

函数的输出

函数的返回状态只是一个数字。 函数怎样才能产生输出呢?

你的函数可以打印到标准输出。 用熟悉的命令替换把它捕获下来:

d6 () { echo "$(( 1 + RANDOM % 6 ))"; }

die=$( d6 )
echo "You rolled a $die."

同时使用输出和状态

即使你正在捕获输出,函数的退出状态也一样可以使用。

roll () {
    local n=$1
    if (( 4 <= n && n <= 20 )); then
        echo "$(( 1 + RANDOM % n ))"  # exit status is 0
    else
        return 1
    fi
}

read -p "How many faces does your die have? " faces
if die=$( roll "$faces" ); then
    echo "You rolled a $die."
else
    echo "I can't roll a die with $faces faces."
fi

递归

函数可以递归地调用自身。 默认情况下,递归深度没有限制。

举个例子:

fibonacci() {
    local n=$1
    if (( n <= 1 )); then
        echo "1"
    else
        local a=$(fibonacci "$(( n - 1 ))")
        local b=$(fibonacci "$(( n - 2 ))")
        echo "$(( a + b ))"
    fi
}

for i in {1..10}; do fibonacci "$i"; done
# => 1
# => 2
# => 3
# => 5
# => 8
# => 13
# => 21
# => 34
# => 55
# => 89
Advanced

可以用 FUNCNEST变量 控制递归深度。

bash -c '
    recur() {
        echo $1
        recur $(($1 + 1))
    }
    FUNCNEST=5
    recur 1
'
1
2
3
4
5
environment: line 1: recur: maximum function nesting level exceeded (5)
通过 GitHub 编辑 该链接会在新窗口或标签页中打开