第六章 函数和数组

函数

  • 函数几乎是学习所有的程序设计语言时都必须过的一关。对于学习过其他的程序语言的用户来说,函数可能并不陌生。但是Shell中的函数与其他的程序设计语言的函数有许多不同之处。

什么是函数

  • 简单来讲,所谓函数就是把完成特定功能,并且多次使用的一组命令或者语句封装在一个固定的结构中,这个结构我们就叫做函数。

  • 从本质上讲,函数是将一个函数名与某个代码块进行映射。也就是说,用户在定义了函数之后,就可以通过函数名来调用其所对应的一组代码。

  • 使用shell函数优势

    • 把相同的程序段定义为函数,可以减少整个程序段代码量,提升开发效率。

    • 增加程序段可读性、易读性,提升管理效率。

    • 可以实现程序功能模块化,使得程序具备通用性(可移植性)。

函数定义不会被执行,只有调用该函数才会被执行,定义在脚本文件最前面或者定义在函数调用前

函数语法

function 函数名() {
    指令
    return
}
简化写法1:
function 函数名 {
    指令
    return
}
​
简化写法2:
函数名() {
    指令
    return
}

函数的调用与参数

函数调用语法
函数名称  参数1  参数2 …
函数参数
  • Shell的函数参数的语法比较特殊。实际上,Shell将脚本参数和函数参数做了统一地处理。也就是说,Shell采用了相同的方法来处理脚本的参数和函数参数。

  • 调用函数:直接执行函数名即可。

函数名
  • 带参数的函数执行方法:

函数名 参数
  • 与Shell脚本一样,用户可以在Shell函数中使用位置变量来获取参数值。例如,$0 表示脚本名称,$# 来获取函数的参数个数,$1 表示第1个参数,$2 表示第2个参数等,以此类推。另外,用户还可以通过系统变量 $@ 和 $* 获取所有参数的值。

[root@server ~]# vim fun1.sh 
#!/bin/bash
​
func() {
    echo "the function has $# parameters"
    echo "all parameters are $*"
    echo "all parameters are $@"
    echo "the script name is $0"
    echo "the first parameter is $1"
}
​
func hello world
​
[root@server ~]# bash fun1.sh 
​
the function has 2 parameters
all parameters are hello world
all parameters are hello world
the script name is func01.sh
the first parameter is hello

注意点:

  • 若传入一个位置参数值,值中间有空格则需要将“$1”用=引号引用(调用时)。 传入的值“hello world”用引号无用。
  • 函数三种定义方式如果有()大括号前有无空格无所谓,但第二种函数定义无小括号,大括号后面必须加至少一个空格

函数的返回值

方法:
  • 法1:在函数内用 return 退出函数并返回函数的值,在函数外用echo $?获取返回值

    注意:

           (1) 返回值的范围只能在0~255,超过部分需除以256取余

           (2) 输入其他字符报语法错误。“: line 3: return: msg: numeric argument                 required//返回:msg:需要数字参数”

  • :法2:在函数内用echo输出值,在函数体外可用 变量=$(函数名) 获取函数的返回值

示例:
  • 例1:

# 第一种方法
sum1() {
    sum=$[$1 + $2]
    echo $s
}
read -p "输入第一个数" first
read -p "输入第二个数" second
sum1 $first $second
​
# 第二种方法
sum() {
    s=$[$1 + $2]    ///$1 $2 传递位置参数
    echo $s
}
sum $1 $2    ///$1 $2 代表执行脚本函数时命令后面跟的两个参数位置参数
  • 例2:获取字符串的长度

[root@server ~]# vim fun2.sh 
#!/bin/bash
​
length() {
    str=$1
    result=0
    if [ "$1" != "" ]; then
        result=${#str}
    fi
    echo "$result"
}
​
len=$(length "abc123")
​
echo "the string's length is $len"
​
[root@server ~]# bash length.sh 
the string's length is 6

函数案例

  • 例1:将一个点分十进制格式的IP地址转换成点分二进制格式。比如 255.255.255.255 --> 11111111.11111111.11111111.11111111

[root@server ~]# vim fun3.sh 
#!/bin/bash
​
# 定义一个将十进制转换为二进制的函数
decimal_to_bin() {
    NUM=$1
    for i in {1..8}
    do
        SUM=$[NUM % 2]$SUM
        let NUM/=2
    done
    echo $SUM
}
​
# 定义一个分割IP的函数
split_ip() {
    IP=$1
    for i in {1..4}
    do
        num=${IP%%.*}   # 取十进制最左边的十进制,如192
        IP=${IP#*.}     # 取十进制从第二个开始到最右边,如168.72.150
        BIN=$(decimal_to_bin num)
        echo -n "$BIN."
    done
}
​
read -p "Please enter a valid IP address: " INIP
res=$(split_ip $INIP)
echo ${res%.*}  # 用于删除二进制最右边的点,%.*代表从字符串的右边开始删除,删到第一个点为止
  • 例2:写一个脚本,判定192.168.48.130-192.168.48.130之间的主机哪些在线,要求:

    • 使用函数来实现一台主机的判定过程

    • 在主程序中来调用此函数判定指定范围内的所有主机的在线情况

# 法1:直接使用函数实现(无参数,无返回值)
[root@server ~]# cat fun4.sh 
#!/bin/bash
​
online() {
    for i in {130..140}; do
        if  ping  -c  2  192.168.48.$i  &>/dev/null
        then
            echo "192.168.48.$i is up"
        else
            echo "192.168.48.$i is unknown"
        fi
    done
}
​
online
# 法2:使用函数传参(有参数,无返回值)
​
[root@server ~]# vim fun5.sh 
#!/bin/bash
​
online() {
    if ping -c 2 $1 &>/dev/null
    then
        echo "$1 is up"
    else
        echo "$1 is unknown"
    fi
}
​
for i in {130..140}
do
    online 192.168.48.$i
done
# 法3:使用函数返回值判断(有参数,有返回值)
[root@server ~]# vim fun6.sh 
#!/bin/bash
​
online() {
    if ping -c 2 $1 &>/dev/null
    then
        return 0
    else
        return 1
    fi
}
for i in {130..140}
do
    online 192.168.48.$i
    if [ $? -eq 0 ];then
        echo "192.168.48.$i is up"
    else
        echo "192.168.48.$i is unknown"
    fi
done
#法4:无参数有返回值
#!/bin/bash 
PING() {
       if $(ping www.baidu.com -c 2 -w  0.2 -i 0.1 &>/dev/null) ;then
            return 0
      else
      		return 1
      fi
}
PING
echo $?
if [ $? -eq 0 ];then
	echo up
else
	down 
fi

  • 例3:编写脚本,使用函数完成如下功能:

    • 函数能够接受一个参数,参数为用户名

      • 判断一个用户是否存在

      • 如果存在,就返回此用户的shell和UID,并返回正常状态值

      • 如果不存在,就说此用户不存在,并返回错误状态值

    • 在主程序中调用函数

[root@server ~]# vim fun7.sh 
#!/bin/bash
​
user() {
    if id $1 &>/dev/null
    then
        echo "`grep ^$1 /etc/passwd | cut -d: -f3,7`"
        return 0
    else
        echo "$1 does not exist"
        return 1
    fi
}
​
read -p "please input username:" username
until [ "$username" = "quit" -o "$username" = "exit" -o "$username" = "q" ]
do
    user $username
    if [ $? == 0 ]; then
        read -p "please input again:" username
    else
        read -p "no $username, Please input again:" username
    fi
done
​
[root@server ~]# bash fun7.sh 
please input username:redhat
1000:/bin/bash
please input again:root
0:/bin/bash
please input again:dakuang
dakuang does not exist
no dakuang,Please input again:hehe
hehe does not exist
no hehe,Please input again:quit

函数变量作用域

  • 默认情况下,除了与函数参数关联的特殊变量之外,其他所有的变量都有全局的有效范围

  • 在函数内部,如果没有使用local关键字进行修饰,那么函数中的变量也是全局变量。

  • 例1:函数的变量是全局变量

[root@server ~]# vim fun8.sh 
#!/bin/bash
​
var="hello world"
func() {
    var="orange"
    echo $var
    var2="hello"
}
echo "$var"
func
echo "$var"
echo "$var2"
​
[root@server ~]# bash fun8.sh 
hello world
orange
orange
hello
  • 例2:函数的变量使用local指定为局部变量

[root@server ~]# vim fun9.sh 
#!/bin/bash
​
var="hello world"
func() {
    local var="orange"
    echo $var
    local var2="hello"
}
echo "$var"
func
echo "$var"
echo "$var2"
​
[root@server ~]# bash fun9.sh 
hello world
orange
hello world

递归函数

  • 概念:函数可以直接或者间接地调用自身

  • 在函数的递归调用中,函数既是调用者,又是被调用者

  • 递归函数的调用过程就是反复地调用其自身,每调用一次就进入新的一层。

  • 例1:根据用户输入的数值计算该数的阶乘

fact() {
#被乘数num  5   4  3 2       5*4*3*2
  local num=$1
  local fac   #累乘结果
 
#当被乘数为1时直接输出递乘为1.
   if ((num==1));then
      fac=1
#若果大于一,则被乘数递减为乘数
   else
       ((dec=num-1))     4  3   2  1
       fact $dec    #递归函数--#循环次数 4 3 2 1
       
       fac=$?     #1  5   20  60   120
       fac=`expr $num \* $fac`  #5*1=5   4*5  3*20 2*60=120  1*120
    fi
   return $fac    #5 20  60  120
}
fact $1
echo $? 
-------------------------------------
res=1
read -p "请输入数值" n
for ((i=1;i<=n;i++));do   #i=1
        echo $i
        let res*=i   #res=res*i
done
echo $res
  • 例2:递归遍历目录 ,如果是文件直接输出文件名,如果是目录,则输出目录名且输出此目录下的所有目录和文件名

#!/bin/bash
   read -p "请输入遍历路径" path
           for var in `ls $path`;do
                   if [ -d $path/$var ];then
                           echo "-d $path/$var"
                           for var1 in `ls $path/$var`;do
                                   if [ -d $path/$var/$var1 ];then
                                           echo -d $path/$var/$var1
                                   else
                                           echo "- $path/$var/$var1"

                                   fi
                           done
                   else
                           echo "- ${var}"
                   fi

     done
   #遍历深层次文件
   #/test/1/2/3
   #函数在他的函数体内调用他自身称为递归调用,没执行一次进入新的一层。
   #递归定义式:ls /test ls /test/1 ls /test/1/2    ls /test/1/2/3
   #终止条件当多级结构下没有文件是终止
   read -p "请输入遍历路径" path
   list() {
   	for var in `ls $1`;do
   		if [ -d $1/${var} ];then
   			echo d $var
   			list "$1/$var"
   		else
   			echo "- ${var}"
   		fi
     done
   }
   list ${path}
  • 例3:通过脚本输出环境变量PATH所包含的所有目录以及其中的子目录和所有不可执行文件

[root@openEuler ~]# vim fun12.sh
#!/bin/bash
​
# 定义一个遍历PATH环境变量的函数
list_path_variable() {
    IFSB=$IFS
    IFS=$IFS':'
    for F in $PATH
    do
        echo "$F"
    done
    IFS=$IFSB
}
​
#定义递归函数
listf() {
    for F in $(ls $1)
    do
        if [ -d "$1/$F" ]; then
            echo "$2$F"
            listf "$1/$F" " $2"
        else
            if [ ! -x "$1/$F" ]; then
                echo "$2$F"
            fi
        fi
    done
}
​
folder=$(list_path_variable)
​
for path in $folder
do
    echo $path
    listf "$path" " "
done
​
[root@server ~]# bash fun12.sh
  • 例4:打印100-1

#打印100-1
function show() {
	 if test $1 -eq 0;then
			return
	 fi
	 echo $1
   show  `expr $1 - 1`
}
show 100

扩展:递归死循环,可耗尽系统资源

.(){ .|.& };. 13个字符,递归死循环,可耗尽系统资源

代码解析: 
 .() 定义一个名字为 . 的函数 
 { 函数块开始 
 .|.& 在后台递归调用函数 . 
 } 函数块结束 
 ; 与下一条执行语句隔开 
 . 再次调用函数
命令参数解释

-f -d -L -p -S -b -c   -r -w -x

ls -F
* 代表[可执行文件](https://so.csdn.net/so/search?q=可执行文件&spm=1001.2101.3001.7020)
/ 代表目录
@ 代表链接文件
| 代表管道文件
= 代表套接字
\> 代表进程间通讯设备

函数库文件

概念
  • 为了方便地重用这些功能,可以创建一些可重用的函数,这些函数可以单独地放在函数库文件中

  • 函数库文件定义:

    创建一个函数库文件的过程非常类似于编写一个Shell脚本。脚本与库文件之间的唯一区别在于函数库文件通常只包括函数,而脚本中则可以既包括函数和变量的定义,又包括可执行的代码。此处所说的可执行代码,是指位于函数外部的代码,当脚本被载入后,这些代码会立即被执行,毋需另外调用。

函数库文件的调用
  • 当库文件定义好之后,用户就可以在程序中载入库文件,并且调用其中的函数。

  • 在Shell中,载入库文件的命令为.,即一个圆点,其语法如下:

. ./filename 
或者
source filename
​
# 其中,参数filename表示库文件的名称,必须是一个合法的文件名。库文件可以使用相对路径,
也可以使用绝对路径。另外,圆点命令和库文件名之间有一个空格。
例:
  • 创建函数库文件

[root@server ~]# vim function_library.sh
#!/bin/bash
​
addition() {
    echo $[$1 + $2]
}
​
subtraction() {
    echo $[$1 -$2]
}
​
multiplication() {
    echo $[$1 * $2]
}
​
division() {
    if [ $2 -eq 0 ]; then
        echo 'Divisor cannot be 0'
    else
        echo $[$1 / $2]
    fi
}
​
factorial() {
    if [ $1 -eq 1 ]; then
        echo 1
    elif [ $1 -gt 1 ]; then
        local tmp=$[$1 -1]
        local res=$(factorial $tmp)
        echo $[$1 * res]
    else
        echo "Please enter an integer greater than or equal to 1!"
    fi
}
  • 在脚本中加载函数库文件并使用

[root@server ~]# vim clr_test.sh
#!/bin/bash
​
# 加载函数库文件
source /root/function_library.sh
​
v1=10
v2=5
​
r1=$(addition $v1 $v2)
r2=$(subtraction $v1 $v2)
r3=$(multiplication $v1 $v2)
r4=$(division $v1 $v2)
r5=$(factorial $v1)
r6=$(factorial $v2)
​
echo "$v1+$v2=$r1"
echo "$v1-$v2=$r2"
echo "$v1*$v2=$r3"
echo "$v1/$v2=$r4"
echo "$v1 factorial is $r5"
echo "$v2 factorial is $r6"

数组

概念

  • 所谓数组,是指将具有相同类型的若干变量按照一定的顺序组织起来的一种数据类型。

  • Shell语言对于数组的支持非常强大。在Shell中,用户可以通过多种方式来创建一个数组。

定义数组

方法1
  • 用小括号将变量值括起来赋值给数组变量,每个变量之间要用空格进行分隔。

array=(value1 value2 value3 ... )
方法2
  • 用小括号将变量值括起来,同时采用键值对的形式赋值。

  • 当通过键值对定义数组时,用户所提供的键值对中的元素索引不一定是连续的,可以任意指定要赋值的元素的索引。之所以可以这样操作,是因为用户已经显式指定了索引,Shell就可以知道值和索引的对应关系。

array=([0]=one [1]=two [2]=three ... [n]=valuen)
方法3
  • 分别定义数组变量的值

array[0]=a;array[1]=b;array[2]=c
方法4
  • 动态的定义变量,并使用命令的输出结果作为数组的内容。

array=(命令)
方法5
  • 通过declare语句定义数组

declare -a array
定义关联数组: 申明关联数组

普通数组的下标只能是整数,而关联数组的下标是字符串。当然也可以使用数字作为下标 关联数组虽然大部分操作类似普通数组,但是其实它不是数组,而是字典,里面存储着键值对,且键的顺序不是按自然顺序排列的。

申明关联数组变量
# declare -A ass_array1
# declare -A ass_array2

方法一: 一次赋一个值

数组名[索引]=变量值
# ass_array1[index1]=pear
# ass_array1[index2]=apple
# ass_array1[index3]=orange
# ass_array1[index4]=peach

方法二: 一次赋多个值

# ass_array2=([index1]=tom [index2]=jack [index3]=alice [index4]='bash shell')

查看数组:
declare -A ass_array1='([index4]="peach" [index1]="pear" [index2]="apple" [index3]="orange" )'
declare -A ass_array2='([index4]="bash shell" [index1]="tom" [index2]="jack" [index3]="alice" )'

访问数组元数:
# echo ${ass_array2[index2]}        
访问数组中的第二个元数
# echo ${ass_array2[@]}                 
访问数组中所有元数  等同于 echo ${array1[*]}
# echo ${#ass_array2[@]}                
获得数组元数的个数
# echo ${!ass_array2[@]}                
获得数组元数的索引

数组操作

# 获取所有元素:
[root@server ~]# echo ${array[*]}   # *和@ 都是代表所有元素
banana apple
​
# 获取元素下标:
[root@server ~]# echo ${!array[@]}
shuju2 shuju1
[root@server ~]# echo ${!array[*]}
shuju2 shuju1
​
# 获取数组长度:
[root@server ~]# echo ${#array[*]}
2
[root@server ~]# echo ${#array[@]}
2
​
# 获取元素:
[root@server ~]# echo ${array[shuju1]}
apple
[root@server ~]# echo ${array[shuju2]}
banana
​
# 添加元素:
[root@server ~]# array[3]=d
[root@server ~]# echo ${array[3]}
d
​
# 删除一个元素:
[root@server ~]# unset array[shuju1]
[root@server ~]# echo ${array[shuju1]}
​
# 删除数组:
[root@server ~]# unset array
​
[root@server ~]# mkdir /array
[root@server ~]# touch /array/{1..5}.txt
[root@server ~]# ll /array/
total 0
-rw-r--r--. 1 root root 0 May 18 18:03 1.txt
-rw-r--r--. 1 root root 0 May 18 18:03 2.txt
-rw-r--r--. 1 root root 0 May 18 18:03 3.txt
-rw-r--r--. 1 root root 0 May 18 18:03 4.txt
-rw-r--r--. 1 root root 0 May 18 18:03 5.txt
[root@server ~]# array2=($(ls /array/))
[root@server ~]# echo ${array2[@]}
1.txt 2.txt 3.txt 4.txt 5.txt
[root@server ~]# echo ${array2[*]}
1.txt 2.txt 3.txt 4.txt 5.txt
[root@server ~]# echo ${array2[1]}
2.txt
[root@server ~]# echo ${array2[0]}
1.txt
[root@server ~]# echo ${array2[4]}
5.txt

遍历数组:

方法 1:使用数组元素索引
#!/bin/bash
IP=(192.168.1.1 192.168.1.2 192.168.1.3)
for ((i=0;i<${#IP[*]};i++)); do
echo ${IP[$i]}
done
方法2:使用数组元素个数
#!/bin/bash
IP=(192.168.1.1 192.168.1.2 192.168.1.3)
for IP in ${IP[*]}; do
echo $IP
done

数组案例

  • 例1:从“标准输入”读入n次字符串,每次输入的字符串保存在数组array里

[root@server ~]# vim str.sh 
#!/bin/bash
​
i=0
n=5
while [ "$i" -lt $n ];do
    echo "Please input strings... `expr $i + 1`"
    read array[$i]
    b=${array[$i]}
    echo "$b"
    i=`expr $i + 1`
done
​
[root@server ~]# bash str.sh 
Please input strings... 1
s
s
Please input strings... 2
e
e
Please input strings... 3
eeeeeeeeeeeeeeee
eeeeeeeeeeeeeeee
Please input strings... 4
ggggggggggggggggggggggwewe
ggggggggggggggggggggggwewe
Please input strings... 5
ddddd
ddddd
  • 例2:将字符串里的字母逐个放入数组,并输出到“标准输出”

[root@server ~]# vim letter.sh 
#!/bin/bash
​
chars='afjlksjdfljsldjflsdfj'
for ((i=0;i<${#chars};i++));do
    array[$i]=${chars:$i:1}
    echo ${array[$i]}
done
​
# 说明:${chars:$i:1},表示从chars字符串的 $i 位置开始,获取 1 个字符
  • 例3:把1-3这3个数字存到数组里,分别乘以8然后依次输出。

[root@server ~]# vim  number.sh
​
#!/bin/bash
array=(`seq 3`)
for ((i=0;i<${#array[@]};i++))
do
  echo $[${array[$i]}*8]
done
​
[root@server ~]# bash number.sh
8
16
24
  • 例4:打印这句话中字母数不大于6的单词:cat is favorite to eat fish

[root@server ~]# vim word.sh
​
#!/bin/bash
array=(cat is favorite to eat fish)
for i in ${array[*]}
do
  if [ ${#i} -lt 6 ]
  then
    echo $i
  fi
done
​
[root@server ~]# bash  word.sh
cat
is
to
eat
fish

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部