一、select循环

select 循环主要用于创建菜单,以数字显示菜单的选项,用户输入被保存在内置变量REPLY 中。select通常和case一起用,并且是一个死循环,注意要设置退出循环的选项。

与for 循环类似,可以省略in list ,此时使用位置参量

语法:

select 变量name  in 变量列表
do
    action
done
[root@linux blog2]# bash select.sh
1) begin
2) stop
3) continue
#? 1
This program starts to run
#? 2
This program will exit
#? 3
Program continue run
#? 4
Error happended
[root@linux blog2]# cat select.sh
#!/bin/bash
select choice in "begin" "stop" "continue"
do
case $choice in
"begin")
    echo "This program starts to run"
;;
"stop")
    echo "This program will exit"
;;
"continue")
    echo "Program continue run"
;;
*)
    echo "Error happended"
    exit 1
;;
esac
done
[root@linux blog2]#

二、shell函数

在所有的编程语言中,函数都是一个非常重要的内容,函数可以实现某一个单一的功能并实现重用减少代码的冗余。

shell中的函数和其他语言有些差别。函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程,它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分,Shell函数在当前Shell中运行。因此在当前Shell中,函数可以对shell中变量进行修改。

shell的函数格式:

第一种:
function func_name () {
    函数体
}
第二种:
func_name () {
    函数体
}
第三种:
function func_name {
    函数体
}

函数的使用环境:

  • 可在交互式环境下定义函数

  • 可将函数放在脚本文件中作为它的一部分

  • 可放在只包含函数的单独文件中

函数可直接通过函数名调用,函数名出现的地方,会被自动替换为函数代码。

函数的生命周期:被调用时创建,返回时终止。

函数的返回值:

函数有多种返回方法

  • 使用echo或printf命令进行输出

  • 函数体中调用命令的输出结果

  • 使用return返回函数的状态码,和其他的面向对象语言不同,shell的return只能返回数字,无法返回字符串和函数本身

交互环境下定义函数:

[root@linux blog2]# printchar() { echo "This is a function"; } ###第一个大括号后要有空格,命令结束后要有分号
[root@linux blog2]# printchar 
This is a function
[root@linux blog2]#

注意,函数名不要和命令名相同,会优先执行函数,如果函数名和别名重复且别名是某个带参数的命令,将无法创建这个函数。定义的函数将一直保留到用户从系统退出,或执行了如下所示的unset命令,类似本地变量。

脚本中定义函数:

脚本中的函数是脚本的一部分,无法被其他脚本调用,注意脚本中的函数里的变量将对脚本中的同名变量产生影响,要使用局部变量让变量只在函数体中有效。

[root@linux blog2]# bash func1.sh
shell var
the first func var
the first func var
[root@linux blog2]# cat func1.sh
#!/bin/bash
var="shell var"
echo $var
func() {
    var="the first func var"
}
func
echo $var
foo() {
    local var="the second func var"
}
foo
echo $var

函数文件:

可以在系统开机自动加载的文件中看到定义环境的函数,它们将在开机加载到内存中

可以将经常使用的函数存入函数文件,然后将函数文件载入shell。

一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数。

若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件。

[root@linux blog2]# cat funcfile.sh
#!/bin/bash
func1() {
    echo "this is the first func"
}
func2() {
    echo "this is the second func"
}
[root@linux blog2]# . funcfile.sh

执行set查看当前环境

linux脚本编程之函数和select循环_第1张图片

[root@linux blog2]# func1 
this is the first func
[root@linux blog2]# func2
this is the second func
[root@linux blog2]# unset func2
[root@linux blog2]# func2
-bash: func2: command not found
[root@linux blog2]# func1
this is the first func

函数参数:

传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可;例如“testfuncarg1 arg2 ...”

在函数体中当中,可使用$1, $2, ...调用这些参数;还可以使用$@, $*, $#等特殊变量

可以使用函数的返回值来定义一个变量

[root@linux blog2]# bash varfunc.sh hello
hello
[root@linux blog2]# cat varfunc.sh 
#!/bin/bash
func() {
    echo "$1"
}
var=$(func $1)   ###不能返回函数本身或其他复杂的对象,功能有限
echo $var
[root@linux blog2]#

函数递归:

函数可以直接或间接调用自身,但是递归有层次的限制

[root@linux blog2]# bash rec.sh 6
720
[root@linux blog2]# bash rec.sh 3
6
[root@linux blog2]# bash rec.sh 4
24
[root@linux blog2]# bash rec.sh 100
0
[root@linux blog2]# bash rec.sh 20
2432902008176640000
[root@linux blog2]# bash rec.sh 30
-8764578968847253504
[root@linux blog2]# bash rec.sh 25
7034535277573963776
[root@linux blog2]# bash rec.sh 26
-1569523520172457984
[root@linux blog2]# cat rec.sh 
#!/bin/bash
func() {
    if [ $1  -le 1 ];then
        echo 1
    else
        echo $[${1}*$(func $[${1}-1])]
    fi
}
func $1

练习:

1、斐波那契数列又称黄金分割数列,因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2)
写一个函数,求n阶斐波那契数列

[root@linux blog2]# bash fibonacci.sh 16
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
[root@linux blog2]# cat fibonacci.sh 
#!/bin/bash
func(){
    if [ $1 -eq 1 ];then
        echo 0
    elif [ $1 -eq 2 ];then
        echo 1
    else
        echo $[$(func $[${1}-1])+$(func $[${1}-2])]
    fi
}
for i in `seq 1 $1`;do
    func $i
done

上面利用递归函数的方法去算每一个数,由于每个数都是从0累加算出来的,如果超过15个数后将耗费大量的运算时间,这并不是一个合格的算法............

[root@linux blog2]# bash fibonacci2.sh 20
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
[root@linux blog2]# cat fibonacci2.sh 
#!/bin/bash
declare -a a=(0 1)
for ((i=2;i<$1;i++));do
    let a[i]=a[i-1]+a[i-2]
done
echo ${a[@]}|tr ' ' '\n'

运用数组,将每次计算的结果存起来,当计算下一个数时只需一次计算而无需从头开始,几乎没有等待时间。


2、汉诺塔(又称河内塔)问题是源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。利用函数,实现N片盘的汉诺塔的移动步骤

[root@linux day03]# bash hanlota.sh 3 A B C
Move 1 from A to C
Move 2 from A to B
Move 1 from C to B
Move 3 from A to C
Move 1 from B to A
Move 2 from B to C
Move 1 from A to C
[root@linux day03]# bash hanlota.sh 4 A B C
Move 1 from A to B
Move 2 from A to C
Move 1 from B to C
Move 3 from A to B
Move 1 from C to A
Move 2 from C to B
Move 1 from A to B
Move 4 from A to C
Move 1 from B to C
Move 2 from B to A
Move 1 from C to A
Move 3 from B to C
Move 1 from A to B
Move 2 from A to C
Move 1 from B to C
[root@linux day03]# cat hanlota.sh 
#!/bin/bash
func1() {
    echo "Move $1 from $2 to $3"
}
func2() {
    if [ $1 -eq 1 ];then
        func1 1 $2 $4
    else
        func2 $[$1-1] $2 $4 $3   #####第一步,执行递归函数
        func1 $1 $2 $4      ######第二步,只需移动一次,仅执行func1即可
        func2 $[$1-1] $3 $2 $4######第三步,执行递归函数
    fi
}
func2 $1 $2 $3 $4

汉诺塔问题是一个典型的递归问题。可以将移动汉诺塔的问题分为三步走。例如:我要移动n个圆盘从A柱移到C柱,将B柱看作一个辅助的柱子。可以将A看作源柱,B看做辅助柱,C看成目标柱

第一步:先将A柱上的n-1个小圆盘从A利用C移到B上,这时的源柱、辅助柱和目标柱的顺序为A-C-B

第二步:只需移动一个盘,即将A上最大的盘从A利用B移动到C上,这时的源柱、辅助柱和目标柱的顺序为A-B-C

第三步:将B柱上的n-1个圆盘从B利用A移动到C柱,这时的源柱、辅助柱和目标柱的顺序为B-A-C

依次类推,如果递归执行上面的步骤将一直到n=1,这时就简单了,直接将圆盘从源柱移动到目标柱即可。

在上面的脚本中我先定义了移动一次圆盘的动作函数func1。接着定义了实现上面三个步骤的函数func2。然后调用func2即可。