# Linux Shell自定义函数

在脚本中一遍又一遍地编写同样的代码会很烦人,为了避免这种麻烦,可以将代码封装成函数,多次引用。

# 1)函数的定义与使用

Linux Shell中函数的定义有两种方式:

  • 采用关键字function
function name {
    commands
}
  • 函数名后跟空括号
name() {
    commands
}

使用函数:

#!/bin/bash

sayHello

function sayHello {
    echo "Hello, Bash Shell."
}

sayHi() {
    echo "Hi, Bash Shell."
}

sayHello
sayHi

sayHi() {
    echo "Hi2, Bash Shell."
}

sayHi

执行,

$ ./test.sh 
# ./test.sh: 行 3: sayHello:未找到命令
# Hello, Bash Shell.
# Hi, Bash Shell.
# Hi2, Bash Shell.

每次引用函数名时,bash shell会找到函数的定义并执行在那里定义的命令。

从上面的例子可以看到,

  • 使用未定义的函数脚本会报错
  • 函数可以被重定义,调用时会使用最新的定义,原来的定义会被覆盖

# 2)函数的返回值

通常,在使用函数时为了与调用者交互,会希望函数能给出局部逻辑执行后的返回值,如运算结果,执行有没有出错。

# 默认情况

bash shell中,默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码。函数执行结束后,可以用标准变量 $? 来确定函数的退出状态码

#!/bin/bash
function sayHello {
    echo "Hello, Bash Shell."
}
sayHello
echo ret value: $?

sayHi() {
    echo "Hi2, Bash Shell."
    ls -al rob
}

sayHi
echo ret value: $?

sayHi() {
    ls -al rob
    echo "Hi2, Bash Shell."
}

执行,

$ ./test.sh

# Hello, Bash Shell.
# ret value: 0
# Hi2, Bash Shell.
# ls: 无法访问 'rob': 没有那个文件或目录
# ret value: 2
# ls: 无法访问 'rob': 没有那个文件或目录
# Hi2, Bash Shell.
# ret value: 0

可以看到,在sayHi函数中执行最后一行指令ls -al rob时出错,因此$?的值是2。但是,看上面的例子中,前后两个sayHi函数因为出错语句位置的不同,函数的退出状态也不同。因此,使用$?判断函数的状态是很不可靠的。

# 使用return命令

bash shell使用 return 命令来退出函数并返回特定的退出状态码。 return 命令允许指定一个整数值来定义函数的退出状态码。

  • return返回的状态码也在$?中,必须函数一结束就返回,否则返回值会结束
  • 退出状态码必须是0-255,任何大于256的值都会产生一个错误值。
#!/bin/bash

doubleCount(){
    read -p "Please Input A Value:" ival
    echo doubling the value...
    return $[ $ival * 2 ]
} 

doubleCount
echo "The New Value:" $?

执行,

$ ./test.sh 
# Please Input A Value:100
# doubling the value...
# The New Value: 200
$ ./test.sh 
# Please Input A Value:200
# doubling the value...
# The New Value: 144

可以看到输入200时,输出本应该是400,但因为其超出了256的范围,因此输出错误。

# 捕获函数输出

可以将函数的输出保存到shell变量中。

#!/bin/bash

doubleCount(){
    read -p "Please Input A Value:" ival
    echo $[ $ival * 2 ]
} 

r=$(doubleCount)
echo "The New Value:" $r

执行,

$ ./test.sh 
# Please Input A Value:200
# The New Value: 400

从上面的例子可以看到两点和以前不一样的地方:

  • 函数echo语句的输出赋值给了接收函数返回值的变量
  • 返回值可以大于256了,其实这种方式还可以返回浮点值和字符串值

# 3)函数参数

前面讲bash shell会将函数当作小型脚本来对待。这意味着可以像普通脚本那样向函数传递参数,并可以用$1$2这样获取传递给函数的参数值。

#!/bin/bash
function add {
    if [ $# -eq 0 ] || [ $# -gt 2 ]
    then
        echo -1
    elif [ $# -eq 1 ]
    then
        echo $[ $1 + $1 ]
    else
        echo $[ $1 + $2 ]
    fi
}

echo -n "Adding 10 and 15: "
value=$(addem 10 15)
echo $value

echo -n "Try adding just one number: "
value=$(addem 10)
echo $value

echo -n "Trying adding no numbers: "
value=$(addem)
echo $value

echo -n "Try adding three numbers: "
value=$(addem 10 15 20)
echo $value

执行,

$ ./test.sh 
# Adding 10 and 15: 25
# Try adding just one number: 20
# Trying adding no numbers: -1
# Try adding three numbers: -1

上面这个函数实例给出了如何在函数中获取参数,如何传递参数给函数。不过呢,由于在函数中使用$1是获取传递给函数的参数,因此,函数中没有办法直接获取传给脚本的命令行参数,只能以函数参数的方式传递给函数

# 4)函数变量

全局变量是在shell脚本中任何地方都有效的变量。如果在脚本的主体部分定义了一个全局变量,那么可以在函数内读取它的值。

#!/bin/bash

function multiply2 {
    v=$[ $v * 2 ]
}

read -p "Enter a value: " v
multiply2
echo "The New Value Is: $v"

执行,

$ ./test.sh 
# Enter a value: 200
# The New Value Is: 400

很诧异吧,函数multiply2中变量v没有定义就可以使用,这是因为v是全局变量,在调用multiply2前定义了。这种方式很容易造成混乱,不建议过度使用。

函数内部使用的任何变量都可以被声明成局部变量,只要在变量声明的前面加上 local 关键字就可以了

#!/bin/bash

function add5 {
    local temp=$[ $value + 5 ]
    result=$[ $temp * 2 ]
}

temp=4
value=6

add5
echo "The result is $result"
if [ $temp -gt $value ]
then
    echo "temp is larger"
else
    echo "temp is smaller"
fi

执行,

$ ./test.sh 
# The result is 22
# temp is smaller

上面例子中有两点,

  • result没有使用local修饰,因此是全局变量
  • local temp限定了其作用域仅在函数中,不会影响函数外temp的值

# 5)数组与函数

# 数组作为函数参数

将数组变量当作单个参数传递,它不会起作用。

#!/bin/bash

function test_array {
    echo "The parameters are: $@"
    thisarray=$1
    echo "The received array is ${thisarray[*]}"
}

myarray=(1 2 3 4 5)
echo "The original array is: ${myarray[*]}"
test_array $myarray

执行,

$ ./test.sh 
# The original array is: 1 2 3 4 5
# The parameters are: 1
# The received array is 1

可以看到通过$myarray指定传入的只是数组的第一个元素。要解决这个问题,必须将该数组变量的值分解成单个的值,然后将这些值作为函数参数使用。

#!/bin/bash

function arraySum {
    local sum=0
    local newarray
    newarray=($(echo "$@"))
    for value in ${newarray[*]}
        sum=$($sum + $value)
    done
    echo $sum
}

array=(1 2 3 4 5)
echo "The array is: ${array[*]}"
arg1=$(echo ${array[*]})
result=$(arraySum $arg1)
echo "The result is $result"

执行,

$ ./test.sh 
# The array is: 1 2 3 4 5
# The result is 15

使用$(echo $value)可以将echo的输出保存到变量中,这样可以将数组传入到函数中

# 数组作为函数的返回值

从函数中返回数组的操作也是使用echo语句,其操作为:

#!/bin/bash

function multi2 {
    local origarray
    local newarray
    local eles
    local i
    origarray=($(echo "$@"))
    newarray=($(echo "$@"))
    eles=$[ $# - 1 ]
    for (( i = 0; i <= $eles; i++ ))
    {
        newarray[$i]=$[ ${origarray[$i]} * 2 ]
    }
    echo ${newarray[*]}
}

array=(1 2 3 4 5)
echo "Array is: ${array[*]}"
arg1=$(echo ${array[*]})
result=($(multi2 $arg1))
echo "The new array is: ${result[*]}"

执行,

$ ./test.sh 
# Array is: 1 2 3 4 5
# The new array is: 2 4 6 8 10

注意,上面newarray=($(echo "$@"))newarray=$(echo "$@"),如果没有(),输出其实是一个整体,加上括号才是数组,不带括号,上面的脚本执行会报错

# 6)创建shell函数库

写一个脚本文件:

# compute文件

function addem {
    echo $[ $1 + $2 ]
}
function multem {
    echo $[ $1 * $2 ]
}

在需要使用该库文件的地方,只需要在脚本开头

source /xxx/xx/compute

然后就可以使用定义在compute中的函数了。

source 命令有个快捷的别名,称作点操作符.,还可以写成:

. /xxx/xx/compute

函数的应用绝不仅限于创建自己的函数自娱自乐。在开源世界中,可以下载大量各式各样的函数,并将其用于自己的应用程序中。譬如GNU shtool shell脚本函数库其中提供了一些简单的shell脚本函数,可以用来完成日常的shell功能。

(adsbygoogle = window.adsbygoogle || []).push({});