一、   在脚本中创建函数

   在脚本中,如果存在重复使用的代码,可以将这段代码定义成函数,类似于Java中的方法。定义好的函数在脚本中可以被调用。下面主要内容是在脚本中如何创建函数并且如何调用他们。

14.1          脚本函数

   函数是在脚本中可以重复使用的代码块,可以将多次出现的命令封装起来并为这个代码块起个名字,在脚本中需要执行这个代码块的位置只要使用这个函数的名字就能执行函数中的代码,这个过程叫做调用函数,下面将介绍如何在脚本中创建一个函数和如何在脚本中调用函数。

14.1.1       创建函数

   在脚本中有两种方式创建函数,第一种方式是使用function关键字,后面跟上为这个代码块起的函数名称,如下格式。

function  name(){

   代码块

}

 

   注意:name属性是为函数起的唯一名称。必须为定义在脚本的每个函数起一个唯一的名称,函数体内的代码块是一条或多条合法的命令,在调用函数时函数的执行顺序和脚本执行相同,按照命令的顺序从上向下执行。

   创建函数的第二种方式是不使用function关键字,这和Java中的方法定向相似,如下格式。

name(){

   代码块

}

 

   注意:函数名后面的圆括号为空,表明定义的是一个函数。代码块的执行规则与脚本相同。另外需要注意,在脚本中,定义函数要在调用函数之前,如果在定义函数之前调用函数,系统会提示警告信息,命令找不到!。

14.1.2       定义函数和调用函数

   在脚本中调用函数只要指定函数名就可以了,就像使用普通命令一样。定义的函数要在

   调用函数之前,否则会提示警告信息。

例:bash108.sh

#!/bin/bash

# 调用函数,在定义函数之前调用函数会提示警告信息

test1

# 定义函数

test1(){

  echo "Hello function!"

}

 

控制台显示:

$  bash108.sh

./bash108.sh:  line 3: test1: command not found

 

例:bash109.sh

#!/bin/bash

# 定向函数

test1(){

  echo "Hello function!"

}

# 调用函数

test1

 

控制台显示:

$  bash109.sh

Hello  function!

 

   在脚本中创建函数要在调用之前创建。如果在一个脚本中存在多个函数,函数名一定要唯一,如果存在两个同名的函数,调用函数会出现什么情况呢!如下面的例子。

例:bash110.sh

#!/bin/bash

# 定义函数1

function01(){

  echo "Hello function1!"

}

# 定义函数2,注意:两个函数的函数名相同,后面的函数会覆盖前面定义的函数

function01(){

  echo "Hello function2!"

}

# 调用函数

function01

 

控制台显示:

$  bash110.sh

Hello  function2!

 

   可以看到存在同名函数的脚本中,后面定义的函数会覆盖之前定义的同名函数,并且不会提示警告信息。

14.2          函数的返回值

   可以将函数看做一个小的脚本,函数执行结束后会返回一个退出状态码,在脚本中有三种方式为函数生成退出状态吗。

14.2.1       默认退出状态码

   默认情况下,函数执行结束后会将函数最后一条命令的退出状态码作为函数的退出状态码返回,可以通过变量$?取得函数执行的退出状态码,如下例子。

例子:

#!/bin/bash

# 定义第一个函数,简单的打印

function1(){

  echo "Hello Bash Shell!"

}

# 调用函数

function1

# 输出函数的退出码,是最后一条命令的退出状态码

echo  "Function function1 return data: $?"

# 定义第二个函数

function2(){

  # 定义变量

  file=/home/yarn/bash01

  # 切换到目录

  cd $file

  # 创建一个临时目录并将目录名保存到变量

  dirtmp=`mktemp -d dirXXX`

  # 切换到临时目录

  cd $dirtmp

  # 查询一个不存在的文件并将错误信息重定向输出到/dev/null

  ls -l  noexistfile 2> /dev/null

}

# 调用函数

function2

# 显示第二个函数的退出状态码,也就是函数最后一条命令的退出状态码

echo  "Function function2 return data: $?"

 

控制台显示:

$  bash111.sh

Hello  Bash Shell!

Function  function1 return data: 0

Function  function2 return data: 2

 

   从输出可以看到函数返回退出状态码是函数中最后一条命令的退出状态码,但是无法知道函数中其他命令是否执行成功,如下例子。

例子:

#!/bin/bash

# 定义函数

function1(){

  file=/home/yarn/bash01

  cd $file

  dirtmp=`mktemp -d dirXXX`

  cd $dirtmp

  # 查询不存在的文件,命令的退出码非零

  ls -l noexistfile 2> /dev/null

  cd ..

  # 删除临时目录,退出状态码将是函数的退出码

  rm -rf $dirtmp

}

# 调用函数

function1

echo  "Function function1 return data: $?"

 

控制台显示:

$  bash112.sh

Function  function1 return data: 0

 

   退出码是0,也是函数最后一条命令的退出状态码。可以看出在函数中存在一条执行不成功的命令,但是在默认情况下函数的退出码没有反应失败命令的情况,所以使用函数的默认退出状态码是有风险的,通常情况下采用其他的方式替代默认方式来反应函数的真实的执行状态。

14.2.2       使用return命令

   在脚本中使用return命令返回特定的退出状态码并退出函数。并允许为return命令指定一个整数值作为函数的退出状态码,整数值在0~255之间。针对设定的退出状态码可以执行不同的执行流程。下面通过一个例子说明。

例子:

#!/bin/bash

# 定义函数

function1(){

  # 定义目录变量

  file=/etc

  # 循环遍历目录下的文件

  for files in $file/*

  do

    # 如果是目录,执行下一次循环

    if [ -d "$files" ]

    then

      continue

fi

# 如果是文件

    if [ -f "$files" ]

then

  # 判断是否是和/etc/profile配置文件

      if [ "$files" =  "/etc/profile" ]

      then

        # 退出函数并返回函数的退出码

        return 257

      fi

    fi

  done

}

# 调用函数

function1

# 打印函数退出码

echo  "Function function1 return data: $?"

# 显示文件内容

cat  $files

 

控制台显示:

$  bash113.sh

Function  function1 return data: 1

#  /etc/profile

 

   需要注意的是,返回的退出码取值范围是0~255,如果在范围内会正常返回,如果大于255,则使用这个取值减去256后的值,上面的例子返回的257但在控制台显示的是1,正好是257-256的值。所以,要想正确取得函数的退出码,要注意两点,第一是取值范围在0~255内,第二是函数执行一结束就通过变量$?取函数的返回值,否则可能是其他命令的退出码。因为变量$?保存的是最后执行的命令的退出码。

14.2.3       使用函数输出

   如果想让函数返回字符串,使用echo命令将函数产生的值输出,使用反引号将函数的输出保存到变量中。要注意采用这样的方式取得的不是函数的退出码。下面通过一个例子说明。

例:bash114.sh

#!/bin/bash

# 定义函数

function1(){

  file=/etc

  for files in $file/*

  do

    if [ -d "$files" ]

    then

      continue

    fi

    if [ -f "$files" ]

    then

      if [ "$files" =  "/etc/profile" ]

      then

        # 使用echo命令将值输出

        echo  $files

      fi

    fi

  done

}

# 通过反括号将函数的输出保存到变量中

filename=`function1`

echo  "Function function1 return data: $filename"

cat  $filename

 

控制台显示:

$  bash114.sh

Function  function1 return data: /etc/profile

#  /etc/profile

 

   通过这种方式取得函数的输出值,要注意函数中的echo命令的使用次数,如果echo命令执行次数超过两次,那么会将两次的输出返回,所以在函数中采用echo命令输出时通常采用if语句进行判断,不同的情况下输出不同的结果。下面通过一个例子说明。

例:bash115.sh

#!/bin/bash

# 定义函数

function1(){

  file=/home/yarn/bash01/bash100.sh

  if [ -f "$file" ]

  then

    echo "File $file is exist!"

  else

    echo "File $file is not exist!"

  fi

}

# 接收函数输出并保存到变量中

filename=`function1`

echo  "Function function1 return data: $filename"

 

控制台显示:

$  bash115.sh

Function  function1 return data: File /home/yarn/bash01/bash100.sh is exist!

  

   采用这种方式取得函数的返回值可以返回字符串和数值类型的值。

14.3          函数中的变量

   前面的例子中,我们在函数中使用了变量用来保存目录或文件名。在函数中使用变量时要注意,因为在函数中使用变量不当可能会出现问题。因为脚本中函数内的变量的作用域和其他高级语言是不同的,在下面的内容中将详细说明。

14.3.1       向函数传递参数

   在脚本中,函数被认为是小的脚本,所以通过执行函数的命令行向函数传递参数的方式和向脚本传递参数的方式和要求是相同的,可以通过变量$0得到脚本名称。第一个参数的值保存到变量$1中,第二个参数的值保存到变量$2中,以此类推。可以通过变量$#得到参数的个数。在给函数传递参数时,参数要和函数在同一行,下面通过一个简单的例子说明函数参数的传递。

例子:

#!/bin/bash

# 定义函数

function1(){

  # 显示变量$0的值,保存的是函数的名称

  echo "Function name is :$0"

  # 判断参数个数是否等于2

  if [ $# -eq 2 ]

  then

    # 输出第一个参数

echo "First parameter: $1"

# 输出第二个参数

    echo "Second parameter: $2"

  else

    echo "Parameter input  error!" 

  fi

}

# 调用函数并传递两个参数,中间用空格分隔

function1  /home/yarn /etc/profile

 

控制台显示:

$ bash116.sh

Function  name is :./bash116.sh

First  parameter: /home/yarn

Second  parameter: /etc/profile

 

   通过变量$1$2等变量取得参数,通过变量$#判断参数的个数是否符合要求。下面是几向函数传递参数的例子,第一个是传递三个参数计算两个值的加减乘除并将结果返回。

例:bash117.sh

#!/bin/bash

# 定义计算器函数

calculator(){

  # 判断参数是否等于3

  if [ $# -eq 3 ]

  then

    # 判断运算符,执行不同的分支

    case $2 in

      +)

        echo  $[ $1 + $3 ]

      ;;

      -)

        echo  $[ $1 - $3 ]

      ;;

      \*)

        echo  $[ $1 * $3 ]

      ;;

      \/)

        echo  $[ $1 / $3 ]

      ;;

    esac

  else

    echo  "Parameter input error!"

  fi

}

# 调用函数并传递参数

var1=`calculator 100 + 10`

echo "100 + 10 = $var1"

var2=`calculator 100 - 10`

echo "100 - 10 = $var2"

var3=`calculator 100 \* 10`

echo "100 * 10 = $var3"

var4=`calculator 100 \/ 10`

echo "100 / 10 = $var4"

var5=`calculator  100 / 12 1`

echo  $var5

 

控制台显示:

$  bash117.sh

100 +  10 = 110

100 -  10 = 90

100 *  10 = 1000

100 /  10 = 10

Parameter  input error!

 

   对于调用带参数的函数一定要在调用函数的命令行用空格分隔参数,并且参数要和函数名在同一行上。如果函数处理的参数是在执行脚本时传入的就更加灵活,脚本的命令行参数和函数的命令行参数的使用方式相同,修改上面例子的代码,通过控制台向脚本传递参数,下面通过例子说明。

例:bash118.sh

#!/bin/bash

# 定义函数

calculator(){

    case $2 in

      +)

        echo $[ $1 + $3 ]

      ;;

      -)

        echo $[ $1 - $3 ]

      ;;

      \*)

        echo $[ $1 * $3 ]

      ;;

      \/)

        echo $[ $1 / $3 ]

      ;;

    esac

}

# 判断参数个数是否是三个

if [ $#  -eq 3 ]

then

  if [ $2 = "\*" ]

  then

    # 调用函数

    var1=`calculator $1 \* $3`

    echo "$1 * $3 = $var1"

  else

    # 调用函数

    var1=`calculator $1 $2 $3`

    echo "$1 $2 $3 = $var1"

  fi

else

  echo "Parameter input error!"

fi

 

控制台显示:

$  bash118.sh 100 '+' 10

100 +  10 = 110

$  bash118.sh 100 '-' 10

100 -  10 = 90

$  bash118.sh 100 '/' 10

100 /  10 = 10

$  bash118.sh 100 '\*' 10

100 *  10 = 1000

 

   注意:*号需要转义。上面的例子采用的是case分支语句,修改上面的例子,使用if语句对参数进行判断实现与上面例子相同的功能。

例:bash119.sh

#!/bin/bash

# 定义函数

calculator(){

  if [ "$2" = "+" ]

  then

    echo $[ $1 + $3 ]

  elif [ "$2" = "-" ]

  then

    echo $[ $1 - $3 ]

  elif [ "$2" = "\*" ]

  then

    echo $[ $1 * $3 ]

  elif [ "$2" = "/" ]

  then

    echo $[ $1 / $3 ]

  fi

}

# 判断参数个数是否合法

if [ $#  -eq 3 ]

then

  # 调用函数并传递参数

  var=`calculator $1 $2 $3`

  if [ $2 = "\*" ]

  then

    echo "$1 * $3 = $var"

  else

    echo "$1 $2 $3 = $var"

  fi

else

  echo "Parameter input error!"

fi

 

控制台显示:

$  bash119.sh 100 '+' 10

100 +  10 = 110

$  bash119.sh 100 '-' 10

100 -  10 = 90

$  bash119.sh 100 '/' 10

100 /  10 = 10

$  bash119.sh 100 '\*' 10

100 *  10 = 1000

 

   下面的例子中定义一个函数判断两个参数的值是否相同

例:bash120.sh

#!/bin/bash

# 定义函数

equals(){

  case $1 in

    $2)

      echo "$1 equal to $2"

    ;;

     *)

      echo "$1 and $2 are not  equal"

    ;;

  esac

}

# 判断参数个数是否合法

if [ $#  -eq 2 ]

then

  # 调用函数

  returnValue=`equals $1 $2`

  echo "$returnValue"

else

  echo "Parameter input error!"

fi

 

控制台显示:

$  bash120.sh 19 19

19  equal to 19

$  bash120.sh abc abc

abc  equal to abc

$  bash120.sh abc ab

abc and  ab are not equal

$  bash120.sh aBc abc

aBc and  abc are not equal

 

   上面这个例子使用了case语句,这是个技巧,如果第一个参数等于第二个参数,就会执行后面的输出语句,否则执行另一个分支。

下面的例子较复杂,使用函数判断参数是否为数值,如果是数值返回退出码为0,否则返回1。如果两个参数都是数值然后参加算术运算。

例:bash121.sh

#!/bin/bash

# 定义函数

check(){

  if [ $# -eq 1 ]

  then

    case $1 in

        *[!0-9]* | "")

             return 1

        ;;

        *)

             return 0

        ;;

    esac

  else

    return 1

  fi

}

# 循环

while  true

do

  # 从控制台取得参数

  read -p "Input first parameter: "  var1

  read -p "Input second parameter:  " var2

  # 判断参数不为空

  if [ -n "$var1" ] && [ -n  "$var2" ]

  then

    # 调用函数判断变量是否为数值

    if check $var1

then

  # 判断第二个参数是否为数值

      if check $var2

      then

        echo  "$var1 + $var2 = "$[ $var1 + $var2]

        echo  "$var1 - $var2 = "$[ $var1 - $var2]

        echo  "$var1 * $var2 = "$[ $var1 * $var2]

        echo  "$var1 / $var2 = "$[ $var1 / $var2]

      else

        echo "Parameter input  error!"

      fi    

    else

      echo "Parameter input error!"

    fi

  else

    # 循环结束条件

read -p "Input parameter: " var3

# 判断是否结束循环

    if [ $var3 = "end" ]

then

  # 跳出循环

      break

    fi

  fi

done

 

控制台显示:

$  bash121.sh

Input  first parameter: 100

Input  second parameter: 10

100 +  10 = 110

100 -  10 = 90

100 *  10 = 1000

100 /  10 = 10

Input  first parameter: abc

Input  second parameter: def

Parameter  input error!

Input  first parameter:

Input  second parameter: f

Input  parameter: end

 

   这个例子稍微有些复杂,从控制台读取参数后调用函数进行判断是否是数值,如果是数值类型则进行算术运算,否则提示警告信息,当从控制台输入end时,退出循环脚本终止运行。

14.3.2       函数中的变量

   Java语言一样,在脚本中的变量也有作用域的概念,也就是变量的可见性。要注意的是在函数中定义的变量和脚本中的变量不太相同,在函数中定义的变量既可以是局部变量也可以是全局变量。

1. 全局变量

全局变量在脚本的任何位置都可以访问到,但是在子脚本中是不可见的,除非使用export命令将变量向下传递。在脚本中定义的全局变量在函数中可以访问,在函数中定义的全局变量在脚本中也同样可以访问到。默认情况下,在脚本中定义的变量都是全局变量,同样,在函数中定义的变量默认情况下也是全局变量,在脚本中也可以访问到,下面通过一个例子说明。

例:bash122.sh

#!/bin/bash

# 定义函数

function1(){

   # 函数声明的变量value,默认情况下是全局变量,在脚本中可以访问

   # 在脚本中声明的变量默认情况下是全局变量,在函数中可以访问

   value=$[ $var1 + $var2 ]

}

# 在脚本中声明了两个变量并从控制台接收输入

read -p  "Please input first parameter: " var1

read -p  "Please input second parameter: " var2

# 调用函数

function1

# 在脚本中可以直接使用函数内定义的全局变量value

echo  "The value of Function is $value"

 

控制台显示:

$  bash122.sh

Please  input first parameter: 100

Please  input second parameter: 20

The  value of Function is 120

 

默认情况下在脚本中和函数中定义的变量是全局的变量,这和Java语言是有区别的,在脚本中也可以访问函数中的全局变量。下面通过一个例子说明在函数和脚本中命名全局变量名时需要注意的事项。

例:bash123.sh

#!/bin/bash

# 定义函数

function1(){

  # 函数内定义的变量value与脚本中定义的变量value重名

  # 变量value的值是脚本中定义的两个变量的乘积

  # 需要注意的是,当函数被调用执行后,脚本中同名变量的值会被覆盖

  value=$[ $var1 * $var2 ]

}

# 在脚本中定义变量并赋初值,变量名与函数中的变量同名

# 注意:当函数被调用后,脚本中变量的值会被覆盖

value=100

# 从控制台接收变量值

read -p  "Please input first parameter: " var1

read -p  "Please input seconde parameter: " var2

# 调用函数前脚本中变量的值还是初始值

echo  "Bash Shell script variable value: $value"

# 调用函数

function1

# 脚本中的变量的值被覆盖

echo  "Final value: $value"

 

控制台显示:

$ bash123.sh

Please  input first parameter: 100

Please  input seconde parameter: 20

Bash  Shell script variable value: 100

Final  value: 2000

 

如果脚本和函数中存在同名的变量时需要注意,脚本中的变量值会被函数改变,在编写脚本时要注意,尤其是当脚本中的变量和函数中的变量都是全局变量时更要注意。同样,在编写脚本时根据不同的条件通过函数改变脚本中变量的值。

2. 局部变量

如果没有特殊的需求,在函数中定义的变量都可以声明成局部变量,声明局部变量的语法格式如下。

local var1

local var2="Hello Bash Shell"

 

使用关键字local声明的变量只在函数内可见,在脚本中是不可见的。如果在函数中和脚本中存在同名的变量,Bash Shell会隔离两个同名的变量。如果想在函数和脚本中共享变量可以将函数中的变量声明成全局变量,如果变量不希望被共享或安全性考虑,可以将函数中的变量声明成局部变量。下面通过一个例子说明局部变量的可见性。

例:bash124.sh

#!/bin/bash

# 定义函数

function1(){

  # 在函数中通过关键字local声明局部变量并赋值

  local value1=$[ $var1 * $var2 ]

  # 在函数中声明的全局变量并赋值

  value2=$[ $var1 * var2 ]

}

# 在脚本中声明两个全局变量并赋初值

value1=100

value2=200

# 从控制台接收数据并赋值给两个全局变量

read -p  "Please input first parameter: " var1

read -p  "Please input second parameter: " var2

# 打印初始值

echo  "Initial value of variable value1: $value1"

echo  "Initial value of variable value2: $value2"

# 调用函数

function1

# 打印调用函数后脚本中的变量的最终值

echo  "The final value of the variable value1: $value1"

echo  "The final value of the variable value2: $value2"

 

控制台显示:

$  bash124.sh

Please  input first parameter: 100

Please  input second parameter: 20

Initial  value of variable value1: 100

Initial  value of variable value2: 200

The  final value of the variable value1: 100

The  final value of the variable value2: 2000

 

在函数中使用local关键字声明的变量只在函数中可见,即使和脚本中的变量重名也不会覆盖脚本中变量的值,因为系统会隔离两个变量。所以函数中的局部变量value1的值并不会覆盖脚本中的全局变量value1的值。

14.4          在函数中使用数组

   函数作为一个功能模块通常情况下要完成一定的功能。把要处理的数据作为参数传入函数并经过函数的处理或返回最终结果。之前的例子中函数的参数和返回值都是一个单值。本节的主要内容是调用函数时如何将数组作为参数传递给函数,如何通过执行函数返回的值是一个数组变量。

14.4.1       向函数传递数组参数

   将数组作为单一的参数传递给函数不能达到预期的值,如下例子。

例:bash125.sh

#!/bin/bash

# 定义函数

function1(){

  # 将第一个参数赋值给局部变量

  local array=$1

  # 打印数组的值到控制台,只显示数组的第一个元素

  echo "The value of parameter \$@ in  the function: $@"

  echo "The value of array in the  function: ${array[*]}"

}

# 定义数组变量

array=(a  b c d e f)

# 在脚本中打印数组的内容

echo  "The value of array:${array[*]}"

# 调用函数并将数组作为单一的参数传递给函数

function1  $array

 

控制台显示:

$  bash125.sh

The  value of array:a b c d e f

The  value of parameter $@ in the function: a

The  value of array in the function: a

 

   从输出结果中可以看到,函数中的输出只有数组的第一个值。当然可以在函数中直接访问脚本中的数组变量,但是这种方式不建议使用。将数组作为参数传递给函数的办法是首先将数组分解成单个的值作为参数传递给函数,然后在函数中将这些参数重组为数组变量。下面通过一个例子说明。

例:bash126.sh

#!/bin/bash

# 定义函数

function1(){

  # 在函数中声明局部数组变量并将参数重新组合成一个数组

  local array=(`echo $@`)

  echo "The value of array in the  function: ${array[*]}"

}

# 定义数值变量

array=(zhangsan  lisi wangwu zhaoliu)

 

echo  "The value of array: ${array[*]}"

# 调用函数将数组变量分解成单个的参数传递给函数

function1  ${array[*]}

 

控制台显示:

$  bash126.sh

The  value of array: zhangsan lisi wangwu zhaoliu

The  value of array in the function: zhangsan lisi wangwu zhaoliu

 

   本例和上例的区别是传给函数的参数是将数组变量先分解成一组单一的值然后传给函数。在函数中首先将多个参数重组合并成一个数组变量,注意变量$@的使用方式和脚本中相同。数组在函数中的使用方式与脚本中相同,可以通过循环对数组的值进行遍历,下面通过一个例子说明。

例:bash127.sh

#!/bin/bash

# 定义函数

function1(){

  # 声明一个局部变量

  local sum=0

  # 将作为参数的一组值重组成一个数组变量

  local array=(`echo $@`)

  # 循环遍历数组中的值

  for var in ${array[*]}

  do

    # 对数组中的值进行累加

    sum=$[ $var + $sum ]

  done

  # 将参数的和输出

  echo $sum

}

# 定义一个数组变量并初始化

array=(10 20 30 40 50)

# 将数组分解成多个单一的值并赋值给变量

parameter=${array[*]}

# 调用函数并将一组单一的值作为参数传递给函数,将函数的返回值赋值给一个变量

sum=`function1  $parameter`

# 打印

echo  "The parameter sum: $sum"

 

控制台显示:

$  bash127.sh

The  parameter sum: 150

 

   函数循环遍历数组并将数组中的值累加,最后将累加的值输出。

14.4.2       从函数中返回数组

   从函数中返回数组的处理方式和向函数传递数组的方式相同,将函数中要返回的数组分解成一组单一的值返回,在脚本中再将这一组值重组成一个数组对象,下面通过一个例子说明。

例:bash128.sh

#!/bin/bash

# 定义函数

function1(){

  # 声明局部数组变量并将参数重组成数组变量

  local array1=(`echo $@`)

  # 创建一个长度相同的数组

  local array2=(`echo $@`)

  # 参数的个数,循环的次数

  local count=$#

  # 循环遍历数组,将原来的数组中的每个值乘以3倍并付诸给新数组

  for ((i = 0;i < count;i++))

  do

    # 数组中的值乘以3倍赋值给另一个数组

    array2[i]=$[ array1[i] * 3 ]

  done     

  # 输出结果

   echo ${array2[*]}

}

# 声明数组变量

array=(10  20 30 40 50)

# 将数组分解后传递给函数,将函数的返回值保存到变量

parameter=`function1  ${array[*]}`

# 将分解的一组值重组成数组

arrayfinal=(`echo  $parameter`)

# 循环遍历数组并显示数组的内容

for var  in ${arrayfinal[*]}

do

  echo "The value of array: $var"

done

 

控制台显示:

$  bash128.sh

The  value of array: 30

The  value of array: 60

The  value of array: 90

The  value of array: 120

The  value of array: 150

 

   采用的方式和向函数传递数组参数相同,先分解数组并输出,然后再重组数组。

14.5          函数实现递归

   采用函数可以实现递归调用,实现方式和Java中的实现方式基本相同。下面的例子使用函数实现递归,注意echo命令的用法。

例:bash129.sh

#!/bin/bash

# 实现递归的函数

function1(){

  # 判断参数是否为1,如果是1直接输出1

  if [ $1 -eq 1 ]

  then

    echo 1

  else

# 将参数减一后保存到临时变量

local temp=$[ $1 - 1 ]

# 递归调用函数

local result=`function1 $temp`

# 将结果输出

    echo $[ $result * $1 ]

  fi

}

# 从控制台接收数据

read -p  "Please input parameter: " var

# 调用函数取得返回值

result=`function1  $var`

 

echo  "Result is $result"

 

控制台显示:

$  bash129.sh

Please  input parameter: 5

Result  is 120

 

14.6          创建函数库

   在脚本中创建函数是为了复用,如果相同功能的函数在多个脚本中都要用到,简单的方式只能在每个需要调用函数的脚本中都添加函数代码块, 采用这种方式可以实现每个脚本中对函数的使用,但是这样的方式不是最好的方式。因为这样的方式代码冗余。更好的方式是将公用的函数都写到一个脚本中,相当于一个函数库文件,在需要使用函数时只要将函数脚本引入到当前的环境中,在脚本中就可以直接调用了。这就像Java中的导包。首先创建一个包含函数的的库文件,如下例子。

例:functions01.sh

#!/bin/bash

# 定义求和的函数

add(){

  if [ $# -eq 2 ]

  then

    local result=$[ $1 + $2 ] 2> /dev/null

    echo $result

  fi

}

# 定义求差值函数

sub(){

  if [ $# -eq 2 ]

  then

    local result=$[ $1 - $2 ] 2> /dev/null

    echo $result

  fi

}

# 定义乘积函数

mult(){

  if [ $# -eq 2 ]

  then

    local result=$[ $1 * $2 ] 2> /dev/null

    echo $result

  fi

}

# 定义求商函数

div(){

  if [ $# -eq 2 ]

  then

    local result=$[ $1 / $2 ] 2> /dev/null

    echo $result

  fi

}

 

   我们创建了一个脚本,这个脚本中除了包含四个函数外没有其他的代码,可以将这个脚本作为一个库文件,当其他脚本需要进行算术运算时只要将这个库文件引入就可以在脚本中直接调用库文件中定义的函数,这是通常的做法。如何将库文件引入到脚本运行的环境中呢!大家知道,当在命令行执行一个脚本时会重启一个Shell进程,如果库文件和运行的脚本不在同一个进程中,库文件中的函数在脚本中是不可见的,不同的Shell进程之间是隔离的,所以如果脚本要调用库文件中的函数,就要将库文件和脚本运行在同一个进程中。

   BashShell中,函数的作用域和环境变量的作用域概念是相同的,只有在启动Shell的进程环境中才是可见的,也就是运行库文件会启动一个新的进程,而启动的脚本会启动一个新的进程,在脚本中对于库文件中的函数是不可见的,如下例子,我们创建一个脚本,在脚本中调用库文件中的函数。

例:bash130.sh

#!/bin/bash

# 从控制台接收输入

read -p  "Please input first parameter: " var1

read -p  "Please input second parameter: " var2

# 调用add函数

result=`add $var1 $var2`

echo "$var1 + $var2 = $result"

# 调用sub函数

result=`sub $var1 $var2`

echo "$var1 - $var2 = $result"

# 调用mult函数

result=`mult $var1 $var2`

echo "$var1 * $var2 = $result"

# 调用div函数

result=`div  $var1 $var2`

echo  "$var1 / $var2 = $result"

 

控制台显示:

注:执行函数库脚本文件,注意:会启动一个新的Shell进程

$  functions01.sh

注:执行脚本,当然会启动一个新的进程,和库文件不是同一个进程

$  bash130.sh

Please  input first parameter: 100

Please  input second parameter: 10

注:函数不可见,控制台提示警告信息

./bash130.sh:  line 6: add: command not found

100 +  10 =

./bash130.sh:  line 9: sub: command not found

100 -  10 =

./bash130.sh:  line 12: mult: command not found

100 *  10 =

./bash130.sh:  line 15: div: command not found

100 /  10 =

 

   从上面的例子可以看出,这种方式启动脚本会启动新的Shell进程,两个脚本不能实现共享,包括变量和函数。

 

   第二种执行脚本的方式是不启动新的Shell进程,而是在当前Shell进程中执行脚本。但是需要使用source命令或点操作符。命令格式如下。

source bash130.sh

. bash130.sh

 

   两种方式都可以,但是通常使用的是点操作符。需要注意的是,点操作符和脚本名称之间一定要有空格,如果没有空格,系统会将点操作符当做当前目录。

变量和函数的生命周期是Shell进程的生命周期,Shell进程执行结束,进程中的变量会从内存中被释放掉,修改上面的例子,将函数库脚本文件运行在当前Shell进程中,库文件中的函数会存在与当前Shell进程中的内存中,所以可也访问到。

例:bash131.sh

#!/bin/bash

# 采用点操作符,在当前进程中启动脚本

.  functions01.sh

# 接收数据

read -p  "Please input first parameter: " var1

read -p  "Please input second parameter: " var2

# 调用函数

result=`add  $var1 $var2`

echo "$var1 + $var2 = $result"

 

result=`sub $var1 $var2`

echo "$var1 - $var2 = $result"

 

result=`mult $var1 $var2`

echo "$var1 * $var2 = $result"

 

result=`div $var1 $var2`

echo  "$var1 / $var2 = $result"

 

控制台显示:

$  bash131.sh

Please  input first parameter: 100

Please  input second parameter: 10

100 +  10 = 110

100 -  10 = 90

100 *  10 = 1000

100 /  10 = 10

 

函数和变量的生命周期和Shell进程的生命周期相同,只要当前的Shell进程没有结束,变量和函数就会一直存在与内存。

14.7          在命令行使用函数

   在脚本中,可以通过函数实现特定的功能,这些函数可以被其他脚本调用来完成更为复杂的逻辑。如果在命令行上也能使用函数会更为方便,因为可以通过函数实现系统命令不具备的功能。在命令行执行函数的方式就像执行普通的命令一样。如果在Shell进程中定义了函数,在此Shell进程退出前,都可以调用执行此函数,在同一进程中定义的函数只能在此进程中是可见的。如果想在命令行调用函数,首先要注意的就是函数在当前Shell进程中是可见的。另外,需要了解的是函数的可见性只与执行脚本的进程有直接关系,而环境变量PATH与函数库脚本文件的所在位置有关。如果想在多个脚本中执行函数库脚本文件,这个脚本文件应该在PATH环境变量指定的目录下。

14.7.1       命令行创建函数

   在命令行上可以创建函数,函数创建后在当前Shell进程中都可以直接调用函数,但是需要注意的是只能在当前进程中可见定义的函数,如果在当前进程中启动新的Shell进程运行脚本,在新的进程中对函数是不可见的。在命令行定义函数有两种方式,第一种方式是在一行上定义整个函数,每个命令结束后要用分号,命令解析器会根据分号判断一条命令结束。如下例子。

$ add(){ echo $[ $1 + $2 ];}

$ add 12 12

24

$ function mult(){ echo $[ $1 * $2 ];}

$ mult 12 12

144

 

   上面的两种方式都是可以的,注意:一条命令结束后要使用分号,哪怕只有一条命令也需要加分号。只要当前Shell进程不结束,函数都是可见的,如果启动新的Shell进程,在新的Shell进程中对函数是不可访问的,如下所示。

注:在创建函数的Shell进程中函数是可以访问的,直到Shell进程结束

$ mult 12 12

144

$ add 11 11

22

注:命令bash不会启动一个新的Shell进程,是当前进程的子进程

$ bash

注:在新的进程中函数是不可访问的,即便是子Shell进程

$ add 100 10

bash: add: command not found

注:同上

$ mult 100 10

bash: mult: command not found

注:命令exit终止当前进程,也就是退出子Shell进程

$ exit

Exit

注:从子Shell进程中退出到父进程后,创建的函数又可见了,创建函数的进程

    不结束,创建的函数会一直保存在内存

$ add 100 10

110

注:同上

$ mult 100 10

1000

 

   上面在命令行创建函数的方式对于简单的函数是可以的,但是如果函数的复杂度很高,存在循环、分支等语句,编写函数就比较麻烦,所以对于较复杂的函数不采用这种方式。在命令行创建函数的第二种方式是在多行上编写函数,不用在每条命令后面添加分号,按回车键换行,在函数最后使用大括号结束,但是要注意,大括号最好换行,如下例子。

$ function printFile(){

> for var in /home/yarn/bash01/*

> do

>   if [ $var =  "/home/yarn/bash01/bash10.sh" ]

>   then

>     echo "File $var  is exist"

>   fi

> done

> }

$ printFile

File /home/yarn/bash01/bash10.sh is exist

 

   可以看到第二种方式比第一种方式更清晰,但是不方便修改。函数的使用方式同上例。通过这种方式创建的函数只要Shell进程一结束,创建的函数都将从内存中销毁。另外需要注意的是如果存在同名的函数,后面创建的函数将覆盖之前创建的函数。如果想将创建的函数不会随着Shell进程的结束而销毁,要采用下面的方式。

14.7.2       使用.bashrc文件创建命令行函数

   直接在命令行创建函数的缺点是当Shell进程结束后,定义的函数将会被销毁。如果想将创建的函数在每一个Shell进程中都可以调用,就需要使用.bashrc配置文件,此配置文件是用户级别的配置文件,每个用户都有自己的.bashrc配置文件。更重要的是当用户登录Shell或每当启动一个新的Bash Shell进程时都会首先执行此脚本文件,在此脚本文件中定义的变量和函数在每个Shell进程中都是可见的。但是这些配置只针对特定的用户。使用.bashrc文件定义函数有两种方式,第一种是直接在.bashrc配置文件中定义函数,第二种方式是先创建函数库脚本文件,在.bashrc配置文件中启动脚本。

   配置文件.bashrc

.bashrc

 

# Source global definitions

if [ -f /etc/bashrc ]; then

        . /etc/bashrc

fi

 

# 用户定义的别名和自定义函数

# User specific aliases and functions

# …

 

1.       第一种方式是在.bashrc配置文件中直接定义函数,例子如下。

.bashrc文件:

# .bashrc

 

# Source global definitions

if [ -f /etc/bashrc ]; then

       . /etc/bashrc

fi

 

# User specific aliases and functions

# 定义的函数,通过一个命令直接切换到hadoop配置文件目录

# 不需要使用cd命令和后面的很长的目录

hadoopConfig(){

  cd  /usr/local/hadoop/etc/hadoop

}

# 通过函数可以直接切换到hadoop的主目录

hadoopHome(){

  cd  /usr/local/hadoop

}

# 通过函数可以切换到hadoop的日志文件目录

hadoopLog(){

  cd  /usr/local/hadoop/logs

}

# 通过函数可以切换到hadoop的数据文件目录

hadoopData(){

  cd  /usr/local/hadoop/data

}

 

上面的四个函数简单但很实用,只要通过一个函数就可以直接跳转到指定的目录,而不用输入很长的目录名。注意:当定义好函数后函数还不能直接使用,要通过source命令或点操作符允许配置文件,定义好的函数才能保存到内存,如下命令。

source /home/yarn/.bashrc

. /home/yarn/.bashrc

 

配置文件运行后定义的函数就可以使用了,如下所示。

[yarn@YARN ~]$ pwd

/home/yarn

[yarn@YARN ~]$ hadoopConfig

[yarn@YARN hadoop]$ pwd

/usr/local/hadoop/etc/hadoop

[yarn@YARN hadoop]$ hadoopLog

[yarn@YARN logs]$ pwd

/usr/local/hadoop/logs

[yarn@YARN logs]$ hadoopHome

[yarn@YARN hadoop]$ pwd

/usr/local/hadoop

[yarn@YARN hadoop]$ hadoopData

[yarn@YARN data]$ pwd

/usr/local/hadoop/data

 

自定义函数在使用上就像命令,方便使用。第一种方式需要在.bashrc配置文件中添加大量的代码,如果自定义函数很少,这种方式是可以的,如果自定义函数很多,还可以采用第二种方式,将事先定义好的函数库文件在.bashrc配置文件中启动执行。

2.       将定义好的函数脚本文件在配置文件中启动。

每启动一个Shell进程都会启动.bashrc配置文件,在配置文件中的命令都将执行。首先创建一个函数脚本文件,如下。

hadoopInit.sh

#!/bin/bash

 

# 定义的函数,通过一个命令直接切换到hadoop配置文件目录

# 不需要使用cd命令和后面的很长的目录

hadoopConfig(){

  cd  /usr/local/hadoop/etc/hadoop

}

# 通过函数可以直接切换到hadoop的主目录

hadoopHome(){

  cd  /usr/local/hadoop

}

# 通过函数可以切换到hadoop的日志文件目录

hadoopLog(){

  cd  /usr/local/hadoop/logs

}

# 通过函数可以切换到hadoop的数据文件目录

hadoopData(){

  cd  /usr/local/hadoop/data

}

# 定义一个函数,需要一个参数,将文件或目录拷贝到共享文件夹下

hadoopCp(){

  if [ $# -eq 1 ]

  then

    if [ -f $1 ]

    then

      cp $1  /mnt/hgfs/LinuxSharedFile

    fi

    if [ -d $1 ]

    then

      cp -r $1 /mnt/hgfs/LinuxSharedFile

    fi

  else

    echo  "Input parameter error!"

  fi

}

 

函数库文件编写完成,脚本文件的所在目录是/home/yarn/bash01,在.bashrc配置文件中启动脚本文件,如下例子。

.bashrc文件:

# .bashrc

 

# Source global definitions

if [ -f /etc/bashrc ]; then

       . /etc/bashrc

fi

 

# User specific aliases and functions

# 在当前进程中运行函数库文件

# 定义变量

file=/home/yarn/bash01/hadoopInit.sh

# 判断脚本文件是否存在并且是文件

if [ -f $file ]

then

  if [ -x $file ]

  then

    # 在当前Shell进程中运行脚本文件

. $file

  fi

fi

 

注意通过source命令运行.bashrc配置文件是库文件生效。可以使用hadoopCp命令将文件或目录拷贝到共享文件夹下,如下所示。

[yarn@YARN bash01]$ hadoopCp bash10.sh

[yarn@YARN bash01]$ cd ..

[yarn@YARN ~]$ hadoopCp bash01

[yarn@YARN ~]$ cd /mnt/hgfs/LinuxSharedFile

[yarn@YARN LinuxSharedFile]$ dir

2009                   HadoopPseudo               jdk-8u51-linux-i586.tar.gz      sqoop

2009all                hbase                     logs2                             startlogs.log

2009t                  hdfs01                   mapreduce01                         workflow1

bash01                hive                      mapreduce02                         workflow2

bash10.sh           hive_log                       mr1                               YARN_bak

class20151221      hive-site.xml           mysql-5.5.44-linux2.6-i686.tar.gz   zip

edit              hive-site.xml.local          parquet-avro-1.4.1.jar      zookeeper-3.4.6.tar.gz

hadoop1              hive-site.xml.remort       pdsh-2.26.tar.bz2                   桌面

hadoop2              jdk-6u11-dlj-linux-i586.bin  pig-0.15.0.tar.gz

hadoop-2.7.0.tar.gz   jdk-8u51-linux-i586.rpm      rw

 

可以看到拷贝的一个文件和一个目录都拷贝成功,使用函数可以简化工作。