shell脚本学习八(函数)

shell脚本学习八(函数)

Scroll Down

创建函数:

 1、基本的脚本函数

      在开始编写较复杂的 shell 脚本时,你会发现自己重复使用了部分能够执行特定任务的代码。 这些代码有时很简单,比如显示一条文本消息,或者从脚本用户那里获得一个答案;有时则会比 较复杂,需要作为大型处理过程中的一部分被多次使用。        在后一类情况下,在脚本中一遍又一遍地编写同样的代码会很烦人。如果能只写一次,随后 在脚本中可多次引用这部分代码就好了。

       bash shell 提供了这种功能。函数是一个脚本代码块,你可以为其命名并在代码中任何位置重 用。要在脚本中使用该代码块时,只要使用所起的函数名就行了(这个过程称为调用函数)。

     1.1  创建函数

          有两种格式可以用来在 bash shell 脚本中创建函数。第一种格式采用关键字 function,后跟分配给该代码块的函数名。 ( name 与 { } 之间应该有空格,否则会报错
                  function name {    

                             commands

                                         }

           name 属性定义了赋予函数的唯一名称。脚本中定义的每个函数都必须有一个唯一的名称。commands 是构成函数的一条或多条 bash shell 命令。在调用该函数时,bash shell 会按命令在 函数中出现的顺序依次执行,就像在普通脚本中一样。  

          第二种格式更接近于其他编程语言中定义函数的方式。 (  name() 与 {} 之间要有空格,否则会报错
                   name() {

                           commands

                                }

           函数名后的空括号表明正在定义的是一个函数。这种格式的命名规则和之前定义 shell 脚本函数的格式一样

       1.2  使用函数

            要在脚本中使用函数,在行中指定函数名就行了。 

            

            

            每次引用函数名 func1 时,bash shell 会找到 func1 函数的定义并执行你在那里定义的命令。 函数定义不一定非得是 shell 脚本中首先要做的事,但一定要小心。如果在函数被定义前使用函数,会收到一条错误消息

           函数引用要在函数体后边 ( 因为 bash shell 脚本是一行一行的执行的)

           也必须注意函数名。函数名必须是唯一的,否则也会有问题。如果重定义了函数, 新定义会覆盖原来函数的定义,这一切不会产生任何错误消息

      1.3  返回值

             bash shell 会把函数当作一个小型脚本,运行结束时会返回一个退出状态码。 有 3 种不同的方法来为函数生成退出状态码

        1.3.1  默认退出状态码

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

             ( 在命令行中显示 退出状态码得用 echo   例如:echo   $? )

            

           

           函数的退出状态码是 2,这是因为函数中的最后一条命令 ls -l   badfile 没有成功运行。但无法知道函数中其他命令中是否成功运行

          更换最后一条运行的命令,如下

         

         

          这次,由于函数最后一条语句 echo 运行成功,该函数的退出状态码就是 0,尽管其中有一条命令并没有正常运行。

          这表明使用函数的默认退出状态码是很危险的,可以使用几种办法来解决这个问题

       1.3.2  使用 return 命令

           bash shell 使用 return 命令来退出函数并返回特定的退出状态码。return 命令允许指定一个整数值来定义函数的退出状态码,从而提供了一种简单的途径来编程设定函数退出状态码

          

          

          函数会将 $value 变量中用户输入的值翻倍,然后用 return 命令返回结果。脚本用 $? 变量显示了该值。

          但当用这种方法从函数中返回值时,要小心了。记住下面两条技巧来避免问题:

                       记住,函数一结束就取返回值;

                       记住,退出状态码必须是 0~255

          如果在用 $? 变量提取函数返回值之前执行了其他命令,函数的返回值就会丢失。记住,$? 变量会返回执行的最后一条命令的退出状态码

          第二条规定就限制了 return 返回值的取值范围,所有超过 255 的数值都会出错,所以要返回较大的整数值或者字符串值的话,就不能使用 return 了

        1.3.3  使用函数输出

**使用 return 返回的是退出状态码,要用 $? 查看,使用变量存储函数结果输出要用 echo 等  **

             正如可以将命令的输出保存到 shell 变量中一样,也可以对函数的输出采用同样的处理办 法。可以用这种技术来获得任何类型的函数输出,并将其保存到变量中: 

               result=$(doublevalue)

             这个命令会将 doublevalue 函数的输出赋给 $result 变量,例子如下

             

            

           新函数会用 echo 语句来显示计算的结果。该脚本会获取 doublevalue 函数的输出,而不是查看退出状态码。

           ( 通过这种技术,你还可以返回浮点值和字符串值。这使它成为一种获取函数返回值的强大方法 )

           这个例子中演示了一个不易察觉的技巧。你会注意到 doublevalue 函数实际上输出了两条消息。read 命令输出了一条简短的消息来向用户询问输入值。bash shell 脚本非常聪明,并不将 read 作为 STDOUT 输出的一部分,并且忽略掉它。如果你用 echo 语句生成这条消息来向用户查询,那么它会与输出值一起被读进 shell 变量中。 

          与下面直接输出的例子对比一下,可能你会有所思考:

          

          

          上述脚本中函数 fun1 使用的第一条 echo 语句也被读进 shell 变量,与上一个例子的 read -p “ ” 作对比

      1.4  在函数中使用变量

         1.4.1  向函数传递参数

             bash shell 会将函数当作小型脚本来对待。这意味着你可以像普通脚本那样向函数传递参数

             函数可以使用标准的参数环境变量来表示命令行上传给函数的参数。例如,函数名会在 $0 变量中定义,函数命令行上的任何参数都会通过 $1、$2 等定义。也可以用特殊变量 $# 来判断传给函数的参数数目。

            在脚本中指定函数时,必须将参数和函数放在同一行,像这样: 

                   func1 $value 10

            然后函数可以用参数环境变量来获得参数值,这里有个使用此方法向函数传值的例子:

           在脚本中向函数传递参数,

           

          

          脚本中的 addem 函数首先会检查脚本传给它的参数数目。如果没有任何参数,或者参数多于两个,addem 会返回值 - 1。如果只有一个参数,addem 会将参数与自身相加。如果有两个参数,addem 会将它们进行相加。

          由于函数使用特殊参数环境变量作为自己的参数值,因此它无法直接获取脚本在命令行中的参数值。

          ( 脚本中的函数不能获取到运行脚本时所携带的参数)正如下例,尝试使用函数获取命令行参数将会报错

          

        尽管函数也使用了 $1 和 $2 变量,但它们和脚本主体中的 $1 和 $2 变量并不相同。要在函数中使用这些值,必须在调用函数时手动将它们传过去

         

         通过将 $1 和 $2 变量传给函数,它们就能跟其他变量一样供函数使用了

      1.4.2  在函数中处理变量   

           给 shell 脚本程序员带来麻烦的原因之一就是变量的作用域。作用域是变量可见的区域。函数 中定义的变量与普通变量的作用域不同

           函数使用两种类型的变量:

**                全局变量**

**                局部变量**

**             ① 全局变量**

全局变量是在 shell 脚本中任何地方都有效的变量。如果你在脚本的主体部分定义了一个全局 变量,那么可以在函数内读取它的值。如果你在函数内定义了一个全局变量,也可以在脚本的主体部分读取它的值。 默认情况下,你在脚本中定义的任何变量都是全局变量。在函数外定义的变量可在函数内正常访问

          

          

          $value 变量在函数外定义并被赋值。当 fun1 函数被调用时,该变量及其值在函数中都依然有效。如果变量在函数内被赋予了新值,那么在脚本中引用该变量时,新值也依然有效。

          但这其实很危险,尤其是如果你想在不同的 shell 脚本中使用函数的话。它要求你清清楚楚地知道函数中具体使用了哪些变量,包括那些用来计算非返回值的变量。这里有个例子可说明事情是如何搞砸的

          

          

          最初的想法是拿着 temp=4 与 value=6 相比较,但是由于在函数中使用了 temp 变量,导致其值发生变化,所以产生了意外的结果。

**          ② 局部变量**

其实根本无需在函数中使用全局变量,函数内部使用的任何变量都可以被声明成局部变量。要实现这 一点,只要在变量声明的前面加上 local 关键字就可以了

           local  temp   

           也可以在变量赋值语句中使用 local 关键字: 

           local temp=$[$value + 5] 

          local 关键字保证了变量只局限在该函数中。如果脚本中在该函数之外有同样名字的变量, 那么 shell 将会保持这两个变量的值是分离的。现在你就能很轻松地将函数变量和脚本变量隔离开 了,只共享需要共享的变量

          

          

          在函数 func1 中使用了 local 关键字之后,func1 函数中去使用 $temp 变量时,并不会影响在脚本主体中赋给 $temp 变量的值

    1.5  数组变量和函数  

          Shell 数组用括号来表示,元素用 "空格" 符号分割开,语法格式如下:

           array_name=(value1 ... valuen)

           读取数组元素值的一般格式是:

           ${array_name[index]}

            使用 @ 或 * 可以获取数组中的所有元素,例如:

   ${my_array[*]}

                     ${my_array[@]}

       1.5.1  向函数传数组参数

           向脚本函数传递数组变量的方法会有点不好理解。将数组变量当作单个参数传递的话,它不会起作用

           

           $@变量将所有变量都保存为单独的词,$# 变量设为命令行输入的参数总数,$* 变量会将所有参数保存为一个字符串。

          脚本解析:

                    第 9 行定义 myarray 变量是一个数组,第 10 行 echo 打印数组全部元素,11 行引用函数,函数体第 4 行将参数 $myarray 的数组元素传入 $@(但是只能传入第一个元素值),紧接着定义变量 thisarray 接受数组第一个元素

           

          如果你试图将该数组变量作为函数参数,函数只会取数组变量的第一个值。

          要解决这个问题,你必须将该数组变量的值分解成单个的值,然后将这些值作为函数参数使用。在函数内部,可以将所有的参数重新组合成一个新的变量。下面是个具体的例子

           

           

           该脚本用 $myarray 变量来保存所有的数组元素,然后将它们都放在函数的命令行上作为参数传入函数。该函数随后从命令行参数中重建数组变量,定义局部变量 newarray 数组进行接收所有元素。在函数内部,数组仍然可以像其他数组一样使用。

           

          

          addarray 函数会遍历所有的数组元素,将它们累加在一起。你可以在 myarray 数组变量中放置任意多的值,addarry 函数会将它们都加起来

       1.5.2  从函数返回数组

            从函数里向 shell 脚本传回数组变量也用类似的方法。函数用 echo 语句来按正确顺序输出单个 数组值,然后脚本再将它们重新放进一个新的数组变量中

            

            

            该脚本用 $arg1 变量将数组值传给 funct1 函数。func1 函数将该数组重组到新的数组变量中,生成该输出数组变量的一个副本。然后对数据元素进行遍历,将每个元素值翻倍,并将结果存入函数中该数组变量的副本。

            func1 函数使用 echo 语句来输出每个数组元素的值。脚本用 func1 函数的输出来重新生成一个新的数组变量。

      1.6  函数递归

           局部函数变量的一个特性是自成体系。除了从脚本命令行处获得的变量,自成体系的函数不 需要使用任何外部资源 

           这个特性使得函数可以递归地调用,也就是说,函数可以调用自己来得到结果。通常递归函数都有一个最终可以迭代到的基准值。许多高级数学算法用递归对复杂的方程进行逐级规约,直到基准值定义的那级。

           递归算法的经典例子是计算阶乘。一个数的阶乘是该数之前的所有数乘以该数的值。因此,要计算 5 的阶乘,可以执行如下方程:   

                   5! = 1 * 2 * 3 * 4 * 5 = 120

          使用递归,方程可以简化成以下形式: 
                      x! = x * (x-1)!

           就是说,x 的阶乘等于 x 乘以 x1 的阶乘。这可以用简单的递归脚本表达为: 

                function factorial {    

                       if [$1 -eq 1]    

                       then      

                                 echo 1    

                        else      

                               local temp=$[$1 - 1]      

                               local result='factorial $temp'      

                               echo $[$result * $1]    

                        fi

                    }

              阶乘函数用它自己来计算阶乘的值: 

              

      1.7  创建库      

        source 命令用法:

                  **  source FileName**

          **   source 特点: 在当前 bash 环境下读取并执行 FileName 中的命令。该 filename 文件可以执行权限**

              source 命令通常用命令 “.” 来替代          

**               ./ 的命令用法:**

                        ./FileName

            **  ./  特点:打开一个子 shell 来读取并执行 FileName 中命令,该 filename 文件不可以无执行权限**


             使用函数可以在脚本中省去一些输入工作,这一点是显而易见的。但如果你碰巧要在多个脚 本中使用同一段代码呢?显然,为了使用一次而在每个脚本中都定义同样的函数太过麻烦。

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

              $ cat myfuncs

                  # my script functions 
 
                  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.sh         (执行当前文件夹下的 myfuncs.sh)**

           这个例子假定 myfuncs 库文件和 shell 脚本位于同一目录。如果不是,你需要使用相应路径访问该文件。这里有个用 myfuncs 库文件创建脚本的例子

          

         

          如果不在同一路径下就要用绝对路径

         

          

           该脚本成功使用了 myfuncs 库文件中定义的函数

    1.8  在命令行上使用函数

          可以用脚本函数来执行一些十分复杂的操作。有时也很有必要在命令行界面的提示符下直接 使用这些函数。

         和在 shell 脚本中将脚本函数当命令使用一样,在命令行界面中你也可以这样做。这个功能很不错,因为一旦在 shell 中定义了函数,你就可以在整个系统中使用它了,无需担心脚本是不是在 PATH 环境变量里。重点在于让 shell 能够识别这些函数。有几种方法可以实现。

 

       1.8.1  在命令行上创建函数

             因为 shell 会解释用户输入的命令,所以可以在命令行上直接定义一个函数。

             有两种方法。

                ①  第一种方法是采用单行方式定义函数

             

            当在命令行上定义函数时,必须记得在每个命令后面加个分号,这样 shell 就能知道在哪里是命令的起止了

            

            ②  第二种方法是采用多行方式来定义函数,在定义时,bash shell 会使用次提示符来提示输入更多命令。用这种方法,你不用在每条命令的末尾放一个分号,只要按下回车键就行

           

          在函数的尾部使用花括号,shell 就会知道你已经完成了函数的定义                

--------------------------------------------------------------------------------------------------------------------- 警告       

          在命令行上创建函数时要特别小心。如果你给函数起了个跟内建命令或另一个命令相同 的名字,函数将会覆盖原来的命令

---------------------------------------------------------------------------------------------------------------------

        1.8.2  在. bashrc 文件中定义函数

              在命令行上直接定义 shell 函数的明显缺点是退出 shell 时,函数就消失了。对于复杂的函数来说,这可是个麻烦事。 一个非常简单的方法是将函数定义在一个特定的位置,这个位置在每次启动一个新 shell 的时 候,都会由 shell 重新载入。 最佳地点就是. bashrc 文件。bash shell 在每次启动时都会在主目录下查找这个文件,不管是交互式 shell 还是从现有 shell 中启动的新 shell 

          ①  直接定义函数

               可以直接在主目录下的. bashrc 文件中定义函数。许多 Linux 发行版已经在. bashrc 文件中定义了 一些东西,所以注意不要误删了。把你写的函数放在文件末尾就行了。

          ②  读取函数文件

               只要是在 shell 脚本中,都可以用 source 命令(或者它的别名点操作符)将库文件中的函数 添加到你的. bashrc 脚本中

                要确保库文件的路径名正确,以便 bash shell 能够找到该文件。下次启动 shell 时,库中的所有 函数都可在命令行界面下使用了

                

                

                修改完配置文件需要让其生效

                

               当你将函数写进了. bascrc 文件。shell 还会将定义好的函数传给子 shell 进程,这样一来,这些函数就自动能够用于该 shell 会话中的任何 shell 脚本了。你可以写个脚本,试试在不定义或使用 source 的情况下, 直接使用这些函数

               

               

              上例证明,如果将函数定义在. bashrc 中,你可以在别的脚本中直接使用

       1.9  实例

               函数的应用绝不仅限于创建自己的函数自娱自乐。在开源世界中,共享代码才是关键,而这 一点同样适用于脚本函数。你可以下载大量各式各样的函数,并将其用于自己的应用程序中

             1.9.1 下载及安装

               首先是将 GNU shtool 库下载并安装到你的系统中,这样你才能在自己的 shell 脚本中使用这些 库函数。要完成这项工作,可以使用 FTP 客户端或者图像化桌面中的浏览器。shtool 软件包的下载 地址是:

                ftp://ftp.gnu.org/gnu/shtool/shtool-2.0.8.tar.gz 

              可以直接使用 wget 命令进行下载

             

             将其挪到主目录并进行解压

              

        1.9.2  构建库

               shtool 文件必须针对特定的 Linux 环境进行配置。配置工作必须使用标准的 configure 和 make 命令,这两个命令常用于 C 编程环境。要构建库文件,只要输入: 

                $ ./confifgure

                $ make

               

               

             configure 命令会检查构建 shtool 库文件所必需的软件。一旦发现了所需的工具,它会使用 工具路径修改配置文件。

             make 命令负责构建 shtool 库文件。最终的结果(shtool)是一个完整的库软件包。你也可以使用 make 命令测试这个库文件

            

           测试模式会测试 shtool 库中所有的函数。如果全部通过测试,就可以将库安装到 Linux 系统中的公用位置,这样所有的脚本就都能够使用这个库了。要完成安装,需要使用 make 命令的 install 选项。不过你得以 root 用户的身份运行该命令

            

          现在就能在自己的 shell 脚本中使用这些函数了

     本人学习到这里的时候还用 centos7 尝试了一下

     

    

  1.9.3  shtool 库函数

      

  1.9.4  使用库

       可以在命令行或自己的 shell 脚本中直接使用 shtool 函数。下面是一个在命令行中使用 platform 函数的例子

       (当然你可以在脚本中使用该库函数)

       

       

       platform 函数会返回 Linux 发行版以及系统所使用的 CPU 硬件的相关信息。我喜欢的一个函数 prop 函数。它可以使用 \、|、/ 和 - 字符创建一个旋转的进度条。这是一个非常漂亮的工具,可 以告诉 shell 脚本用户目前正在进行一些后台处理工作

       要使用 prop 函数,只需要将希望监看的输出管接到 shtool 脚本就行了 

       

       prop 函数会在处理过程中不停地变换进度条字符。在本例中,输出信息来自于 ls 命令。你 能看到多少进度条取决于 CPU 能以多快的速度列出 / usr/bin 中的文件!-p 选项允许你定制输出文 本,这段文本会出现在进度条字符之前

小结:

           shell 脚本函数允许你将脚本中多处用到的代码放到一个地方。可以创建一个包含该代码块的 函数,然后在脚本中通过函数名来引用这块代码,而不用一次次地重写那段代码。bash shell 只要 看到函数名,就会自动跳到对应的函数代码块处。            

           甚至可以创建能返回值的函数。这样你的函数就能够同脚本进行交互,返回数字和字符串数 据。脚本函数可以用函数中最后一条命令的退出状态码或 return 命令来返回数值。return 命令 可以基于函数的结果,通过编程的方式将函数的退出状态码设为特定值。

           函数也可以用标准的 echo 语句来返回值。可以跟其他 shell 命令一样用反引号来获取输出的 数据。这样你就能从函数中返回任意类型的数据了(包括字符串和浮点数) 。

可以在函数中使用 shell 变量,对其赋值以及从中取值。这样你就能将任何类型的数据从主体 脚本程序的脚本函数中传入传出。函数也支持定义只能在函数内部访问的局部变量。局部变量使 得用户可以创建自成体系的函数,这样就不会影响到 shell 脚本主体中变量或处理过程了。

          函数也可以调用包括它自身在内的其他函数。函数的自调用行为称为递归。递归函数通常有 个作为函数终结条件的基准值。函数在调用自身的同时会不停地减少参数值,直到达到基准值。
        如果需要在 shell 脚本中使用大量函数,可以创建脚本函数库文件。库文件可以用 source 命 令(或该命令的别名)在任何 shell 脚本文件中引用,这也称为 sourcing。shell 不会运行库文件, 但会使这些函数在运行该脚本的 shell 中生效。可以用同样的方法创建在普通 shell 命令行上使用的 函数。你可以直接在命令行上定义函数,或者将它们加到. bashrc 文件中,这样每次启动新的 shell 会话时就可以使用这些函数了。这是一种创建实用工具的简便方法,不管 PATH 环境变量设置成 什么,都可以直接拿来使用