美文网首页
shell 入门 07控制脚本

shell 入门 07控制脚本

作者: wjundong | 来源:发表于2022-09-22 19:52 被阅读0次

    有两种格式可以用来在bash shell脚本中创建函数。第一种格式采用关键字function,后跟
    分配给该代码块的函数名。

    function name { 
        commands 
    }
    
    name() { 
        commands 
    }
    

    函数返回值

    # 要在脚本中使用函数,只需要像其他shell命令一样,在行中指定函数名就行了
    # 每次引用函数名func1时,bash shell会找到func1函数的定义并执行你在那里定义的命令。
    # 函数定义不一定非得是shell脚本中首先要做的事,但一定要小心。如果在函数被定义前使用函数,你会收到一条错误消息。
    function func1 {
        echo "This is an example of a function"
    }
    func1
    # 你也必须注意函数名。记住,函数名必须是唯一的,否则也会有问题。如果你重定义了函数,
    # 新定义会覆盖原来函数的定义,这一切不会产生任何错误消息
    function func1 {
        echo "This is an example of a function"
    }
    
    function func1 {
        echo "The second function"
    }
    func1
    
    # bash shell会把函数当作一个小型脚本,运行结束时会返回一个退出状态码
    # 有3种不同的方法来为函数生成退出状态码
    
    # 1. 默认退出状态码
    # 默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码。在函数执行结束
    # 后,可以用标准变量 $? 来确定函数的退出状态码. 使用函数的默认退出状态码是很危险的
    # 因为你无法知道函数中其他命令中是否成功运行
    func1() {
        echo "trying to display a non-existent file"
        ls -l badfile
    }
    func1
    echo "The exit status is: $?"
    
    # 2. 使用 return 命令
    # bash shell使用return命令来退出函数并返回特定的退出状态码。return命令允许指定一个
    # 整数值来定义函数的退出状态码,从而提供了一种简单的途径来编程设定函数退出状态码
    # 但当用这种方法从函数中返回值时,要小心了。记住下面两条技巧来避免问题
    # * 记住,函数一结束就取返回值, 如果在用$?变量提取函数返回值之前执行了其他命令,函数的返回值就会丢失
    # * 记住,退出状态码必须是0~255
    function dbl {
        read -p "Enter a value: " value
        echo "doubling the value"
        return $(($value * 2))
    }
    dbl
    echo "The new value is $?"
    
    # 3. 使用函数输出
    # 正如可以将命令的输出保存到shell变量中一样,你也可以对函数的输出采用同样的处理办
    # 法。可以用这种技术来获得任何类型的函数输出,并将其保存到变量中:result='dbl'
    function dbl {
        read -p "Enter a value: " value
        echo $(($value * 2))
    }
    result=$(dbl)
    echo "The new value is $result"
    
    # 这个例子中演示了一个不易察觉的技巧。你会注意到dbl函数实际上输出了两条消息。read
    # 命令输出了一条简短的消息来向用户询问输入值。bash shell脚本非常聪明,并不将其作为STDOUT
    # 输出的一部分,并且忽略掉它。如果你用echo语句生成这条消息来向用户查询,那么它会与输
    # 出值一起被读进shell变量中
    
    # 通过这种技术,你还可以返回浮点值和字符串值。这使它成为一种获取函数返回值的强大方法。
    

    函数传参

    # 向函数传递参数
    # 由于 bash shell会将函数当作小型脚本来对待。这意味着你可以像普通脚本那样向函数传递参数。
    # 函数可以使用标准的参数环境变量来表示命令行上传给函数的参数。例如,函数名会在 $0
    # 变量中定义,函数命令行上的任何参数都会通过$1、$2等定义。也可以用特殊变量$#来判断传给函数的参数数目。
    # 在脚本中指定函数时,必须将参数和函数放在同一行
    function addem {
        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 "Let's try adding just one number: "
    value=$(addem 10)
    echo $value
    echo -n "Now trying adding no numbers: "
    value=$(addem)
    echo $value
    echo -n "Finally, try adding three numbers: "
    value=$(addem 10 15 20)
    echo $value
    
    # 由于函数使用特殊参数环境变量作为自己的参数值,因此它无法直接获取脚本在命令行中的参数值。下面的例子将会运行失败。
    # ./test.sh 2 3
    function badfunc1 {
        echo $(($1 * $2))
    }
    if [ $# -eq 2 ]; then
        value=$(badfunc1)
        echo "The result is $value"
    else
        echo "Usage: badtest1 a b"
    fi
    
    # 尽管函数也使用了$1和$2变量,但它们和脚本主体中的$1和$2变量并不相同。要在函数中使用这些值,必须在调用函数时手动将它们传过去
    # ./test.sh 2 3
    function badfunc1 {
        echo $(($1 * $2))
    }
    if [ $# -eq 2 ]; then
        value=$(badfunc1 $1 $2)
        echo "The result is $value"
    else
        echo "Usage: badtest1 a b"
    fi
    
    
    在函数中处理变量

    给shell脚本程序员带来麻烦的原因之一就是变量的作用域。作用域是变量可见的区域。函数
    中定义的变量与普通变量的作用域不同。也就是说,对脚本的其他部分而言,它们是隐藏的。
    函数使用两种类型的变量:

    • 全局变量
    • 局部变量

    下面几节将会介绍这两种类型的变量在函数中的用法

    # 1. 全局变量
    # 全局变量是在shell脚本中任何地方都有效的变量。如果你在脚本的主体部分定义了一个全局
    # 变量,那么可以在函数内读取它的值。类似地,如果你在函数内定义了一个全局变量,可以在脚
    # 本的主体部分读取它的值。默认情况下,你在脚本中定义的任何变量都是全局变量。在函数外定义的变量可在函数内正常访问。
    function dbl {
        value=$(($value * 2))
    }
    read -p "Enter a value: " value
    dbl
    echo "The new value is: $value"
    
    # 但这其实很危险,尤其是如果你想在不同的shell脚本中使用函数的话。它要求你清清楚楚地
    # 知道函数中具体使用了哪些变量,包括那些用来计算非返回值的变量。这里有个例子可说明事情是如何搞砸的。
    function func1 {
        temp=$(($value + 5))
        result=$(($temp * 2))
    }
    temp=4
    value=6
    func1
    echo "The result is $result"
    if [ $temp -gt $value ]; then
        echo "temp is larger"
    else
        echo "temp is smaller"
    fi
    
    # 2.  局部变量
    # 无需在函数中使用全局变量,函数内部使用的任何变量都可以被声明成局部变量。要实现这
    # 一点,只要在变量声明的前面加上local关键字就可以了 
    # local关键字保证了变量只局限在该函数中。如果脚本中在该函数之外有同样名字的变量,
    # 那么shell将会保持这两个变量的值是分离的。现在你就能很轻松地将函数变量和脚本变量隔离开
    # 了,只共享需要共享的变量
    function func1 {
        local temp=$(($value + 5))
        result=$(($temp * 2))
    }
    temp=4
    value=6
    func1
    echo "The result is $result"
    if [ $temp -gt $value ]; then
        echo "temp is larger"
    else
        echo "temp is smaller"
    fi
    

    关于局部变量和全局变量心得

    
    val=123
    val2=456
    
    function func {
        val=234
    }
    
    function func2 {
        local val2=789
    }
    
    # 使用命令替换执行时都不会修改全局变量
    $(func)
    $(func2)
    echo $val           # 123
    echo $val2          # 456
    
    `func` 
    `func2` 
    echo $val           # 123
    echo $val2          # 456
    
    # 直接执行时会修改全局变量, 但是在加入 local 修饰后也不会修改
    func
    func2
    echo $val           # 234
    echo $val2          # 456
    
    # 不论哪种调用方法, 函数都无法获取脚本本身参数 
    # test.sh 1 2       输出
    # 0
    funcc () {
        echo $#
        echo $@
    }
    func
    
    # 间接传入
    # test.sh 1 2       输出 
    # 2
    # 1 2
    funcc () {
        echo $#
        echo $@
    }
    func $@
    

    数组变量和函数

    # 1. 向函数传数组参数
    # 向脚本函数传递数组变量的方法会有点不好理解。将数组变量当作单个参数传递的话,它不会起作用
    # 如果你试图将该数组变量作为函数参数,函数只会取数组变量的第一个值
    function testit {
        echo "The parameters are: $@"
        thisarray=$1
        echo "The received array is ${thisarray[*]}"
    }
    
    myarray=(1 2 3 4 5)
    echo "The original array is: ${myarray[*]}"
    testit $myarray
    
    # 要解决这个问题,你必须将该数组变量的值分解成单个的值,然后将这些值作为函数参数使
    # 用。在函数内部,可以将所有的参数重新组合成一个新的变量。下面是个具体的例子
    # 括号 ($@) 将单值 $@ 重新转成了数组
    function testit { 
        local newarray 
        newarray=($@)
        echo "The new array value is: ${newarray[*]}" 
    } 
    myarray=(1 2 3 4 5)
    echo "The original array is ${myarray[*]}" 
    testit ${myarray[*]}
    
    # 从函数返回数组
    # ${#var[*]} 将获取数组长度, echo ${var[*]} 将数组所有元素取出并转为了单值, 
    # 但是在结果赋值中又将其转为了数组, 因此第一个输出只有一个 2, 若没有括号, 则两个输出都是 2 4 6 8 10
    function addarray {
        local var=($@)
        for ((i = 0; i < ${#var[*]}; i++)); do
            var[$i]=$((${var[$i]} * 2))
        done
        echo ${var[*]}
    }
    
    arr=(1 2 3 4 5)
    result=($(addarray ${arr[*]}))
    echo $result
    echo ${result[*]}
    

    以下是一些测试后的心得

    # 数组
    arr=(1 2 3)
    
    # arr 数组所有元素转为单值
    a=${arr[*]}
    
    echo ${a[0]}    # 1 2 3
    echo ${a[1]}    # 空
    
    # a 根据单值 $a 重组成数组
    a=($a)
    
    echo ${a[0]}    # 1
    echo ${a[1]}    # 2
    
    # 单值
    b="1 2 3"
    
    echo ${b[0]}    # 1 2 3
    echo ${b[1]}    # 空
    
    # b 转为数组
    b=($b)
    
    echo ${b[0]}    # 1
    echo ${b[*]}    # 1 2 3
    
    function fun {
        echo $#
    }
    
    # b 此时为数组, $b 等于 ${b[0]}
    fun $b          # 1
    
    # 数组所有元素转为单值并传入
    fun ${b[*]}     # 3
    
    # 将单值多项数据作为一个数据传入
    fun "${b[*]}" 1 # 2
    

    函数递归

    # 5! = 1 * 2 * 3 * 4 * 5 = 120 
    # 使用递归,方程可以简化成以下形式:
    function factorial {
        if [ $1 -eq 1 ]; then
            echo 1
        else
            local temp=$(($1 - 1))
            local result=$(factorial $temp)
            echo $(($result * $1))
        fi
    }
    
    factorial 5
    

    创建库

    bash shell允许创建函数库文件,然后在多个脚本中引用该库文件。
    这个过程的第一步是创建一个包含脚本中所需函数的公用库文件。这里有个叫作myfuncs的
    库文件,它定义了3个简单的函数

    function addem {
        echo $(($1 + $2))
    }
    function multem {
        echo $(($1 * $2))
    }
    function divem {
        if [ $2 -ne 0 ]; then
            echo $(($1 / $2))
        else
            echo -1
        fi
    }
    

    下一步是在用到这些函数的脚本文件中包含myfuncs库文件。从这里开始,事情就变复杂了。
    问题出在shell函数的作用域上。和环境变量一样,shell函数仅在定义它的shell会话内有效。
    如果你在shell命令行界面的提示符下运行myfuncs shell脚本,shell会创建一个新的shell并在其中
    运行这个脚本。它会为那个新shell定义这三个函数,但当你运行另外一个要用到这些函数的脚本
    时,它们是无法使用的。这同样适用于脚本。如果你尝试像普通脚本文件那样运行库文件,函数并不会出现在脚本中。

    使用函数库的关键在于source命令。source命令会在当前shell上下文中执行命令,而不是
    创建一个新shell。可以用source命令来在shell脚本中运行库文件脚本。这样脚本就可以使用库
    中的函数了。

    source命令有个快捷的别名,称作点操作符(dot operator)。要在shell脚本中运行myfuncs
    库文件,只需添加下面这行: . ./myfuncs

    # 这个例子假定myfuncs库文件和shell脚本位于同一目录。如果不是,你需要使用相应路径访问该文件
    . ./demo.sh
    
    result=$(addem 10 15) 
    echo "The result is $result"
    

    在命令行上使用函数

    可以用脚本函数来执行一些十分复杂的操作。有时也很有必要在命令行界面的提示符下直接
    使用这些函数。和在shell脚本中将脚本函数当命令使用一样,在命令行界面中你也可以这样做。这个功能很
    不错,因为一旦在shell中定义了函数,你就可以在整个系统中使用它了,无需担心脚本是不是在
    PATH环境变量里。重点在于让shell能够识别这些函数。有几种方法可以实现。

    # 1. 在命令行上创建函数
    # 定义好后就可以直接使用
    # 在命令行上创建函数时要特别小心。如果你给函数起了个跟内建命令或另一个命令相同
    # 的名字,函数将会覆盖原来的命令。
    
    # 2. 在.bashrc 文件中定义函数
    # 在命令行上直接定义shell函数的明显缺点是退出shell时,函数就消失了。对于复杂的函数来,这可是个麻烦事。
    # 一个非常简单的方法是将函数定义在一个特定的位置,这个位置在每次启动一个新shell的时候,都会由shell重新载入。
    # 最佳地点就是.bashrc文件。bash shell在每次启动时都会在主目录下查找这个文件,不管是交互式shell还是从现有shell中启动的新shell。
    
    # 直接定义函数
    
    # 可以直接在主目录下的.bashrc文件中定义函数。许多Linux发行版已经在.bashrc文件中定义了
    # 一些东西,所以注意不要误删了。把你写的函数放在文件末尾就行了。这里有个例子。
    function addem { 
     echo $[ $1 + $2 ] 
    } 
    # 该函数会在下次启动新bash shell时生效。随后你就能在系统上任意地方使用这个函数了
    
    # 读取函数文件
    # 只要是在shell脚本中,都可以用source命令(或者它的别名点操作符)将库文件中的函数添加到你的.bashrc脚本中
    . /home/shino/libraries/myfuncs 
    
    # 要确保库文件的路径名正确,以便bash shell能够找到该文件。下次启动shell时,库中的所有
    # 函数都可在命令行界面下使用了
    
    # 更好的是,shell还会将定义好的函数传给子shell进程,这样一来,这些函数就自动能够用
    # 于该shell会话中的任何shell脚本了。你可以写个脚本,试试在不定义或使用source的情况下,
    # 直接使用这些函数. 甚至都不用对库文件使用source,这些函数就可以完美地运行在shell脚本中。
    
    

    相关文章

      网友评论

          本文标题:shell 入门 07控制脚本

          本文链接:https://www.haomeiwen.com/subject/zuowortx.html