【前言】
之前写过一篇文章叫做 shell脚本的基础入门,既然已经入门了,那今天就来说说shell编程的进阶。
我们知道,shell脚本可以用来帮助我们更快的提高工作效率,而在工作中,很多时候的工作的简单而复杂的。什么叫做简单而复杂?简单是说他的操作性很简单,只是一行命令或者两行命令搞定,而复杂是说可能由于工作的需要性,有时候这一条或者两行命令需要我们重复执行十遍百遍,如果说人工去一遍一遍的执行,那你也就基本可以放弃这个岗位了,所以我们需要借助脚本来帮助我们实现。
举个简单的经典面试题吧:计算1+2+…+100的值。
想想我们之前是怎么做的?在之前的学习中,我是这么做的
[root@localhost ~]# echo {1..100}|tr ' ' '+ '|bc
5050
虽然这也是一种方法,显然这并不是面试官出题的意图,体现不了你的真实水平,那么我们就引入今天的正题:shell编程高级进阶。
【进入正题】
过程性的编程语言分为三类:
顺序执行
选择执行
循环执行
所谓顺序执行,即在一个shell脚本中,按照你所写的命令一条一条 的按照顺序执行:
[root@localhost app]# vim test.sh
#!/bin/bash
echo i am $USER
useradd Tom #创建用户
id Tom #显示用户信息
[root@localhost app]# ./test.sh #执行脚本
i am root
uid=1021(Tom) gid=1021(Tom) groups=1021(Tom)
以上这种普遍的脚本就是顺序执行,但是在一些情况下,我们需要让脚本进行自行判断,符合某种条件则执行某种操作,这种就是选择执行。首先来看if …..else…..语句。
if..else语句
单分支
if 条件;then
条件为真的分支代码
fi
双分支
if 条件;then
条件为真的分支代码
else
条件为假的分支代码
fi
多分支
if 条件1;then
条件为真的分支代码
elif 条件2;then
条件为真的分支代码
else
条件为假的分支代码
fi
判断条件:每一次遇到为“真”的条件,执行其分支语句,然后结束整个进程。当然if语句是可以嵌套的。
练习:实现如下功能:使用一个用户名做为参数,如果指定参数的用户存在,就显示其存在,否则添加之;并给用户设置随机生成的8位密码,初始化登陆时提示用户修改密码,显示添加的用户的id号等信息。
[root@localhost bin]# vim createuser.sh
#/bin/bash
psd=`cat /dev/random |tr -dc '[0-9A-Za-z]'|head -c 8` ------>将随机生成的8位密码存储到变量中
read -p "please input a username: " usr
id $usr &> /dev/null ------>判断用户是否存在
if [ $? -eq 0 ];then ------>如存在则显示用户已存在,不存在则执行else的分支代码
echo $usr has exist
else
useradd $usr
echo $psd | passwd --stdin $usr &>/dev/null
echo $usr:$psd >> /app/passwdrandom ------>将用户名及密码输出到一个文件中
chage -d 0 $usr ------>强制用户下次登陆时修改密码
echo "user $usr is created!"
id $usr
fi
unset psd usr ------>删除变量
[root@localhost bin]# ./createuser.sh
please input a username: xiaowang
user xiaowang is created!
uid=1022(xiaowang) gid=1022(xiaowang) groups=1022(xiaowang)
case语句
case 变量引用 in
条件1)
分支1
;;
条件2)
分支2
;;
esac
练习:编写脚本/root/bin/yesorno.sh,提示用户输入yes或no,并判断用户输入的是yes还是no,或是其它信息。
[root@localhost bin]# vim yesorno.sh
read -p "please input yes or no: " ans
var1=` echo $ans |tr -t '[a-z]' '[A-Z]'` ------>将用户输入转换为大写字母,以防用户输入含有大写字母
case $var1 in
Y|YES)
echo you input yes
;;
N|NO)
echo you input no
;;
*)
echo please input a right answer
;;
esac
[root@localhost bin]# ./yesorno.sh
please input yes or no: no
you input no
[root@localhost bin]# ./yesorno.sh
please input yes or no: yes
you input yes
[root@localhost bin]# ./yesorno.sh
please input yes or no: haha
please input a right answer
选择执行常用的就是以上两种了,都是比较简单易理解的,接下来看循环执行,自身觉得比较烧脑的来了~~~
循环执行是为了节省人力,将执行的命令进行循环操作以达到目的,首先介绍for循环。
for 循环
先来看for循环的语法:
for: for NAME [in WORDS … ] ; do COMMANDS; done
for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done 两种格式写法
NAME:指变量
[in WORDS … ]:执行列表
do COMMANDS:执行操作
done:结束符
执行条件:依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束。
还记得上面我们说的计算1+2+…+100的值吗?我们这次使用循环来计算~~~
[root@localhost bin]# sum=0;for i in {1..100};do let sum=sum+i;let i++;done;echo sum is $sum
sum is 5050 #写入脚本也同样适用
[root@localhost bin]# sum=0;for ((i=1;i<=100;i++));do let sum+=i;done;echo sum is $sum
sum is 5050
for循环有多种写法,以上这种是数字循环,还有以下几种:
字符循环
#!/bin/bash
for i in `ls /app/`
do
echo $i is file name
done
#!/bin/bash
list="aa bb cc dd"
for i in $list
do
echo $i
done
路径循环
for file in /app/*
do
echo "$file is file name"
done
练习:打印99乘法表
思路:使用两层循环,内层的循环变量引用外层的循环变量。
[root@localhost bin]# vim for7_9x9.sh
for i in {1..9}
do
for j in `seq 1 $i`;do
echo -e "$i*$j=$[i*j] \c\t"
done
echo
done
unset i j
[root@localhost bin]# ./for7_9x9.sh
1*1=1
2*1=2 2*2=4
3*1=3 3*2=6 3*3=9
4*1=4 4*2=8 4*3=12 4*4=16
5*1=5 5*2=10 5*3=15 5*4=20 5*5=25
6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36
7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49
8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64
9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81
while 循环
语法格式:
while: while COMMANDS; do COMMANDS; done
while ((: while(( exp1; exp2; exp3 )); do COMMANDS; done 两种格式写法
判断条件:当判断条件为真时,则会一直执行;判断条件为假,则会终止循环。
那么有一种语法就可以实现死循环:
while true ;do echo true;done
由于true
的返回值永远为真,那么使用while就会一直循环。
练习:编写脚本,求100以内所有正奇数之和。
[root@localhost bin]# vim while1_jishusum.sh
sum=0
i=1
while [ $i -le 100 ];do
if [ $[$i%2] -eq 1 ];then
let sum=sum+i
fi
let i++
done
echo 100以内所有正奇数之和是 $sum
unset i sum
[root@localhost bin]# ./while1_jishusum.sh
100以内所有正奇数之和是 2500
练习:打印国际棋盘
思路:国际棋盘为八行八列,以两个空格为一个盘格,打印空格底色实现棋盘效果。
[root@localhost bin]# vim whileqipan.sh
#!/bin/bash
i=1
while ((i<=8));do
j=1
while ((j<=8));do
varnum=$[$[i+j]%2] ------>计算行数和列数之和取余的值
if [ $varnum -eq 0 ];then
echo -n -e "\033[41m \033[0m\033" ------>打印两个红色
elif [ $varnum -eq 1 ];then
echo -n -e "\033[47m \033[0m\033" ------>打印两个白色
fi
let j++
done
let i++
echo
done
unset i j
*until循环
until和while的格式上是相同的,但是用法上正好相反。它的判断条件是当条件判断为真时退出循环。
*continue
continue为循环控制语句,用于循环体中,表示结束某一个符合条件的循环,结束的只是当前轮的循环。
*break
break用法同continue相同,不同的是它结束的是整个循环。
*select 用法
select 常用于菜单,常与case组合达到菜单的效果。
使用PS3,显示用户界面提示符,相等于read -p 的效果
$REPLY变量的select的内置变量,用来引用用户输入。
[root@localhost bin]# vim menu47.sh
PS3="请选择菜单编号:"
select menu in 米饭 炒菜 面条 馒头 退出
do
case $REPLY in
1|4)
echo this price is 20
;;
2|3)
echo this price is 12
;;
5)
echo byebye
exit 2
;;
*)
echo no this number !
echo $menu
esac
done
[root@localhost bin]# menu47.sh
1) 米饭
2) 炒菜
3) 面条
4) 馒头
5) 退出
请选择菜单编号:1
this price is 20
请选择菜单编号:4
this price is 20
请选择菜单编号:2
this price is 12
请选择菜单编号:5
byebye
[root@localhost bin]#
【函数】
函数类似于shell脚本,里面存放了一些指令,与脚本不同的是,这些函数可以统一放在一个文件里,在脚本中可以调用这些函数,重复使用,效率较高。
函数的格式:
function function_name () {
一些指令,即函数体
}
关键字function表示定义一个函数,可以省略,后面是函数的名字,()是格式要求,函数体中具体写一些需要完成的指令。
调用:调用一个函数直接调用定义的函数名即可,与shell命令的用法相同。
返回值:
函数的运行进程与当前shell是用一个进程,因此在函数体重不能使用exit退出函数体,这个关键字hi导致系统退出当前shell,因此函数有一个专用的返回命令return。在函数体中使用return,返回值在0~255之间,可以使用$?查看返回值。
局部变量local:
既然我们上面说函数的运行进程与当前shell是用一个进程,那么我们思考一个问题:如果我们在函数体中定义一个变量,而这个变量恰好与函数体外的变量同名,如果我们在函数体中对于该变量重新定义,那是不是会修改函数体外的变量?例如:
[root@localhost app]# name=xiaoli
[root@localhost app]# echo $name
xiaoli
[root@localhost app]# fun1 () { name=haha;echo my name is $name; }
[root@localhost app]# fun1 ------>调用func1函数
my name is haha
[root@localhost app]# echo $name
haha ------>变量已经被重新赋值
那么就提到了局部变量local的定义。使用local关键字可以定义函数体内的局部变量而不影响函数体外的全局变量:
[root@localhost app]# name=xiaoli
[root@localhost app]# echo $name
xiaoli
[root@localhost app]# fun1 () { local name=haha;echo my name is $name; } ------>定义局部变量
[root@localhost app]# fun1
my name is haha
[root@localhost app]# echo $name
xiaoli ------>全局变量未被影响
查看函数:
declare -f 查看所有定义的函数
删除函数:
unset -f
练习:函数调用
[root@localhost app]# vim test.sh
#!/bin/bash
func_eg () {
echo This is first arg
}
func_eg ------>调用func_eg 函数
echo This is second arg
[root@localhost app]# ./test.sh
This is first arg ------>函数执行结果
This is second arg ------>脚本执行结果
函数同样也接受位置参数:
[root@localhost app]# vim test.sh
#!/bin/bash
func_eg2 () {
echo first arg is $1
echo second arg is $2
echo third arg is $3
echo All args are $*
}
func_eg2 35 87 18
[root@localhost app]# ./test.sh
first arg is 35
second arg is 87
third arg is 18
All args are 35 87 18
函数递归:函数可以实现直接或者间接的调用自身
最经典的例子就是汉诺塔的例子了,这里就不多实验了,有兴趣的同学欢迎百度~~
【数组】
变量是指存储单个元素的内存空间
数组是指存储多个元素的连续内存空间,相当于多个变量的集合。
在数组里的每个元素都有一个下标,即索引。数组中的索引分为普通索引和关联索引。
普通索引也称数组索引,即从编号0开始,递归增加。
关联索引是指不使用数组0开始的索引号,对索引采用自定义的格式,可以是abc单个字母,也可以是一个单词或者字符。
对于普通数组来说,如果索引号不连续,则称为数组的稀疏格式。
声明普通数组:declare -a (也可以不声明)
声明关联数组:declare -A (必须声明)
数组赋值:
一次赋值一个变量
array_name[1]=sunday
一次赋值全部变量
array_name=(1 2 3 4)
只赋值给特定变量
array_name=([0]="a" [3]="b")
调用数组:
${ARRAY_NAME[INDEX]}
注意:省略[INDEX]表示引用下标为0的元素
[root@centos6 ~]# test=(a b c d)
[root@centos6 ~]# echo ${test} ------> 省略下标号则默认引用下标为0的元素
a
[root@centos6 ~]# echo ${test[2]} ------>注意这里,因为我们的下标号是从0开始,所以下标为2的元素实际是第三个元素哦
c
[root@centos6 ~]# echo ${test[0]}
a
引用数组所有元素:两种方法
${array_name[*]}
${array_name[@]}
显示数组中元素的个数:
${#array_name[*]}
${#array_name[@]}
[root@centos6 ~]# echo ${test[*]}
a b c d
[root@centos6 ~]# echo ${test[@]}
a b c d
[root@centos6 ~]# echo ${#test[*]}
4
[root@centos6 ~]# echo ${#test[@]}
4
利用上面所说,我们可以对数组追加元素:
[root@centos6 ~]# test[${#test[*]}]=e ------>通过引用数组元素的个数实现向数组中追加元素
[root@centos6 ~]# declare -a ------>查看定义的所有数组
declare -a BASH_ARGC='()'
declare -a BASH_ARGV='()'
declare -a BASH_LINENO='()'
declare -a BASH_SOURCE='()'
declare -ar BASH_VERSINFO='([0]="4" [1]="1" [2]="2" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu")'
declare -a DIRSTACK='()'
declare -a FUNCNAME='()'
declare -a GROUPS='()'
declare -a PIPESTATUS='([0]="0")'
declare -a test='([0]="a" [1]="b" [2]="c" [3]="d" [4]="e")'
------>定义成功
练习:生成10个随机数保存于数组中,并找出其最大值和最小值
#!/bin/bash
declare -a rand
declare -i max=0
declare -i min=32767
for i in {0..9}
do
rand[$i]=$RANDOM
echo ${rand[$i]}
if [ ${rand[$i]} -gt $max ] ;then
let max=${rand[$i]}
else
let min=${rand[$i]}
fi
done
echo The max number is $max
echo The min number is $min
unset i min max
[root@centos6 app]# ./test.sh
15754
26316
31421
6902
5087
13183
2063
7195
20252
23494
The max number is 31421
The min number is 23494
数组切片
格式:${ARRAY[@]:offset:number}
offset: 要跳过的元素个数
number: 要取出的元素个数
[root@centos6 app]# echo ${test[*]}
a b c d e
[root@centos6 app]# echo ${test[*]:2:3} ------>跳过2个元素,取3个元素
c d e