函数

和大多数编程语言一样,shell 脚本也支持函数。在 shell 可以用下面的方式定义和使用函数:

#!/bin/bash

# 定义函数
sayHello () {
    echo "hello $1"    
}

# 调用函数并传递参数
sayHello jack

上面的例子将输出“hello jack”。shell 中虽然也有函数的概念,但和 c、php、js 等编程语言中函数不太一样,shell 脚本中的函数更像是注入在主 shell 脚本中的一段子脚本,调用函数就像是运行这段子脚本,但它不是在新的进程中运行,所以可以和主 shell 脚本共享变量和函数。函数也可以像 shell 脚本那样通过 $0、$1 等变量获取参数。还要注意 shell 函数的调用一定要在函数定义之后,解释器是顺序解析的,在函数定义之前调用函数将报错。

另外,就像 shell 脚本通过 exit code 返回状态码那样,函数也可以通过 return code 的方式返回一个状态码。但要注意状态码不是文本数据,无法输出,想要返回文本数据可以在函数中 echo 文本数据,再通过反引号或 $() 的方式解析,例如:

#!/bin/bash

# 函数返回状态码
getCode() {
    return 0
}
code=`getCode`
echo $code
# 输出为空

# 函数返回文本值
getData () {
    echo "some text"    
}
data=`getData`
echo "data is $data"
# 输出为 data is some text

最后 shell 函数也支持嵌套和递归调用,让我们可以实现一些复杂的算法逻辑,当然这样会比较吃资源。

#!/bin/bash

# shell 递归实现斐波那契数列
fibo () {
    # 获取参数,即想输出数列的第几个值
    index=$1

    # index 为 1 或 2 时值为 1
    # 用 -lt 选项进行数字小于比较,具体请参考 test 命令帮助
    if [ $index -lt 3 ]; then 
        echo 1
    else
        # index 大于等于 3 时数列值为前两项的和
        # 注意 shell 中变量都为字符串类型,要借助 expr 命令进行数字计算
        n1=`expr $index - 1`
        n2=`expr $index - 2`
        expr `fibo n2` + `fibo n1`  
    fi
}

# 调用函数求斐波那契数列第 10 项的值 
fibo 10

作用域

谈到函数当然逃不开作用域。在 shell 脚本中默认所有的变量都在全局作用域下,也就是全局变量。想要只在函数中有效的局部变量可以用 local 来声明。

#!/bin/bash

# 变量作用域
func () {
    # 变量 val1 默认在全局作用域下
    val1=123 
    # 用 local 声明变量 val2 为局部变量 
    local val2=456

    echo "val1 in func is $val1"
    echo "val2 in func is $val2"
}

func
echo "val1 outsite is $val1"
echo "val2 outsite is $val2"

# 运行结果
# val1 in func is 123
# val2 in func is 456
# val1 outside is 123
# val2 outside is

使用 getopts

getopts 是 shell 内建的一个命令,它可以用来检查和获取传递给命令的选项和参数。它的用法如下

#!/bin/bash

# getopts 获取选项和参数
# 脚本 getopts.sh
# getopts 的语法为 getopts opstring name
# opstring 中可以指定脚本接受哪些选项,如果选项必须提供参数就在后面加冒号
# 例如下面的 'xy:z:' 表示脚本接受 -x -y -z 选项且 -y -z 选项必须提供参数
# name 是一个 shell 变量用来保存选项
# getopts 还通过 $OPTIND 保存参数的索引,$OPTARG 保存参数的值
while getopts 'xy:z:' name; do
    echo "$name" $OPTIND $OPTARG
done

# shell
./getopts.sh -xy "one" -z "two"

# 运行结果
# x 1
# y 3 one
# z 5 two

由于函数也可以看作是一段脚本,所以 getopts 命令也可以用在函数中来判断和获取函数接受的选项和参数。

函数库

如果我们有一些通用的函数或处理在很多地方都用的上,那么最好将他们做成库文件供其他脚本调用。类似 php 的 include,shell 脚本想要包含其他脚本或者说是库文件也很简单,用 . 操作符就行了,点后面跟脚本文件名就可以运行指定的脚本,它们不是另起进程而是在同一进程下运行,所以可以共享变量和函数定义,这样就实现了函数库的效果。

例如下面的代码将脚本 lib.sh 包含到当前脚本中,注意 . 操作符后面要有一个空格。另外也可以通过 source 来包含库文件,与 . 操作符效果相同。

#!/bin/bash
# 将指定的脚本文件包含到当前脚本
. /root/lib/lib.sh

# 用 source 命令可以达到相同的目的 
source /root/lib/lib.sh 

# 包含之后就可以使用 lib.sh 中定义的变量和函数了