bash及shell脚本编程基础
bash特性之多命令执行:使用分号分隔,命令之间无关系;
]# cmd
方式一:]# cmd1 `cmd2`:命令引用实现多命令;
方式二:]# cmd1|cmd2|cmd3|...:管道实现多命令;
方式三:]# cmd1;cmd2;cmd3;...:分号实现多命令;
逻辑组合:操作的是命令的运行状态结果即退出码;
]# cmd1 && cmd2 && ...
]# cmd1 || cmd2 ||...
]# !cmd1
退出码:
0:表示为true,真,success,成功;
1-255:表示为failure,假,错误;
逻辑运算:主要操作的是命令的运行状态结果即退出码;
可认为有一种判断的机制在里面;判断取决于是与运算还是或运算还取决于第一个操作的结果;
运算数:true(1),false(0)
COMMAND运行状态结果:
0:TRUE,成功;
1-255:FALSE,错误;
与:见false(0)为false(0);相当于乘法;
true && true = true
true && false = false
第一个操作数为true,其结果取决于第二个操作数;
false && true = false
false && false = false
第一个操作数为false,其结果至此可判定为false;
例如:
]# ls /var && cat /etc/fstab
]# lls /var && cat /etc/fstab
或:见true(1)为true(1);相当于加法;
true || true = true
true || false = true
第一个操作数为true,其结果至此可判定为ture;
false || true = true
false || false = false
第一个操作数为false,其结果取决于第二个操作数;
例如:
]# id hive || useradd hive:如果用户不存在,则添加用户;
]# id hive
非:取反
! true = false
! fase = true
例如:
]# ! id hive && useradd hive:如果用户不存在,则添加用户;
优先级:非 (高)<--与 <--或(低)
运行脚本:
(1)赋予执行权限,并直接运行此程序文件;
chmod +x /PATH/TO/SCRIPT_FILE
/PATH/TO/SCRIPT_FILE
也可直接把脚本文件放在PATH环境变量中;例如把脚本文件放在/bin目录下;
(2)直接运行解释器,以脚本文件为参数;
bash /PATH/TO/SCRIPT_FILE
-x:调试执行;
-n:判断语法错误;
bash特性之变量:
引号有三种类型:'',"",``
引号:字符串引用符号;
''单引号:强引用;其内部的变量不会替换;
""双引号:弱引用;其内部的变量会被替换;
``反引号:命令引用符号;
例如:
]# echo '$PATH'
显示结果:$PATH
]# echo "$PATH"
显示结果:/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
]# echo `ls /root`
显示结果:anaconda-ks.cfg install.log install.log.syslog
变量引用:${NAME},要使用花括号
变量:是内存空间,有名称,名称即为变量名,对应的内存空间中的数据即为变量的值;
变量赋值:NAME=VALUE
=:赋值符号;
把VALUE存储到NAME指向的内存空间中;
编程语言:
强类型:严格区分变量(内存空间)中的数据类型;
弱类型:不区分变量中存储的数据类型,统一为字符型;
bash:统统默认为字符型数据,变量无需事先声明;
变量为什么有类型?
存储空间、存储格式、参与的运算、...
变量命名:只能使用字母、数字和下划线;且不能以数字开头;
变量名:见明知义,不能使用程序保留字(如if,then,case,fi,esac,for,while,until,break,continue等等);
变量引用:${NAME},$NAME
变量替换:把变量引用符号出现的位置替换为其指向的内存空间中的数据;
bash的变量种类:
根据作用域划分:作用域:生效范围,可引用到的范围;
变量目的:
(1)变量用于存数据,可重复多次使用这个数据;
(2)可多次修改变量里面的数据,以达到演进、迭代目的;
本地变量
环境变量
局部变量(函数中)
位置参数变量:
特殊变量:$?,, $@, $#
retval=$?
保存的是命令执行的状态结果;
exit结束shell进程;
pstree命令:显示进程树;
tree命令:显示目录树;
本地变量:作用域为当前shell进程;不包括其子进程;
定义本地变量:
本地变量赋值:NAME=VALUE
本地变量引用方式:$NAME,${NAME}
""
查看变量:set
撤销变量:unset NAME
注意:此处非为变量引用,因此不能使用$;
所有的本地变量在shell进程终止时,会被自动撤销;
例如:
]# pstree:查看shell进程树;
]# name="obama":定义本地变量;仅当前shell进程有效;
]# echo $name
切换到另外的进程查看name变量:
]# echo $name:内容为空;本地变量在同级进程或子进程都无效;
]# set:查看本地变量;
]# unset name:撤销本地name变量;此处非为变量引用,因此不能使用$;
]# animal="goat"
]# echo "There are some $(animal)s."
环境变量:作用域为当前shell进程及其子进程;但不包括同级进程;
环境变量声明和赋值:
declare -x NAME[=VALUE]
-r:定义为只读变量;
export NAME[=VALUE]
可以把本地变量声明环境变量;
环境变量引用方式:
${NAME},$NAME
注意:bash内嵌了很多环境变量,名称为全大写字母;例如:HOME,UID,PWD,SHELL,PATH,HISTSIZE等等;
主要用于工作的特殊环境;
查看环境变量命令:
export,declare -x
env,printenv
撤销环境变量:
unset NAME
例如:
]# declare -x animal:声明环境变量;
]# /bin/bash:环境变量在当前shell进程及其子进程有效;
]# echo ${animal}:花括号可省略;
]# export:查看环境变量;
]# declare -x:查看环境变量;
]# env:查看环境变量;显示格式不同;
]# printenv:查看环境变量;显示格式不同;
]# unset namimal:撤销环境变量;
例如:
]# ls /etc
]# retval=$?:把环境变量的值存储在变量中,方便使用;
]# lll /etc
]# echo $?:查看环境变量状态返回值;
]# echo $retval:查看之前的环境变量状态返回值,这就是变量的意义;
只读变量:就是常量,无法修改,无法撤销;生命周期同当前shell;
定义只读变量:不支持重新赋值,也不支持撤销;
(1)declare -r NAME
(2)readonly NAME
例如:
]# username="Lu Youjiao"
]# declare -r username:定义只读变量;
]# username="Ou Yangfeng":显示不能更改只读变量;
]# unset username:无法撤销只读变量;
bash脚本编程之算术运算
+,-,*,/,%(莫,判断单双数),**
shell变量是一种很弱的变量,默认情况下,一个变量保存一个串,shell不关心这个串是什么含义,所以弱要进行数学运算,必须使用一些命令例如let,declare,expr,双括号等;
算术运算
+,-,*,/,%(取模,判断单双数),**
shell变量是一种很弱的变量,默认情况下,一个变量保存一个串,shell不关心这个串是什么含义,所以弱要进行数学运算,必须使用一些命令例如let,declare,expr,双括号等;
算术运算格式语法:
(1)let VAR=$num1 op $num2(算术运算表达式)
(2)VAR=$[算术运算表达式]
(3)VAR=$((算术运算表达式))
(4)VAR=$(expr argu1 argu2 argu3)
注意:有些时候乘法符号需要转义;
练习:脚本完成,添加3个用户,求三用户的ID之和;
id user1 &> /dev/null || useradd user1
id user2 &> /dev/null || useradd user2
user1_id=$(id -u user1)
user2_id=$(id -u user2)
id_sum=$[$user1_id+$user2_id]
echo "the id sum: $id_sum"
增强型变量赋值:
+=,-=,*=,/=,%=
自身等于自身+数值,使用let命令;
例如:
]# declare -i i=1
]# i=$[$i+1]
]# echo $i
此时就可用增强型赋值:
]# let i+=1
变量做某种算术运算后回存至此变量中;
let i=$i+#
let i+=#
#:代表数字
自增:
VAR=$[$VAR+1]
let VAR+=1
let VAR++
自减:
VAR=$[$VAR-1]
let VAR-=1
let VAR--
练习:
1、脚本实现,计算/etc/passwd文件中第15个用户和第18个用户的ID号之和;
分解:做a,b2个id号的运算,id_sum=$[$id1+$id2]
a=$(head -15 /etc/passwd | tail -1 | cut -d: -f3)
b=$(head -18 /etc/passwd | tail -1 | cut -d: -f3)
sum=$[$a+$b]
echo "the a is:$a"
echo "the b is :$b"
echo "teh sum is :$sum"
2、计算/etc/rc.d/init.d/functions和/ect/inittab文件中的空白行数之和;
a=$(egrep "^[[:space:]]*$" /etc/rc.d/init.d/functions|wc -l)
b=$(egrep "^[[:space:]]*$" /etc/rc.d/init.d/network|wc -l)
sum=$[$a+$b]
echo "a is :$a"
echo "b is $b"
echo "sum is :$sum"
bash脚本编程之条件测试:
判断某需求是否满足,需要由测试机制来实现;
如何编写测试表达式以实现所需的测试:
例如:判断某文件是否有空白行、判断某用户是否存在
(1)执行命令,并利用命令状态返回值来判断;
0:成功
1-255:失败
$?:存储上一条命令执行状态返回值;
例如:
判断用户centos是否登录系统:
who|grep "^centos\>"
echo $?:显示上一个命令状态返回值;
取得命令状态返回值,0表示登录,1-255表示没登录;
(2)测试表达式
test EXPRESSION
[ EXPRESSION ]
` EXPRESSION `
字符比较时,用双中括号
注意:EXPRESSION两端必须要有空白字符,否则语法错误;
bash脚本编程之测试类型:
数值测试
字符串测试
文件测试
数值测试:数值之间的比较
-eq:是否等于;例如:[ $num1 -eq $num2 ],test 2 -eq 3或[ 2 -eq 3 ]
-ne:是否不等于;
-gt:是否大于;
-ge:是否大于等于;
-lt:是否小于;
-le:是否小于等于;
例如:
]# test 2 -gt 3
]# [ $A -eq $B ]
练习:
centos用户id是否比ubuntu用户的ID大?
a=$(id -u centos)
b=$(id -u ubunto)
[ $a -gt $b ] && echo "centos is bigger" || echo "ubuntu is bigger"
字符串测试比较
==:是否等于,等于为真;
!=:是否不等,不等于为真;
>:是否大于,大于为真;
<:是否小于,小于为真;
=~:左侧字符串是否能被右侧的PATTERN所匹配(左边是否包含右边),包含为真;
例如:
]# name=uubuntuu
]# [[ "$naem" =~ buntu ]]
]# echo $?
练习:
判断主机名是否包含magedu,如果不包含,则修改为magedu;
]# a=$(hostname)
]# [[ "$name" =~ magedu ]] || hostname magedu
字符串是否为空:
-z "STRING":判断指定的字符串是否为空;
空则为真,不空则为假
-n "STRING":判断指定的字符串是否不空;
不空则为真,空则为假;
注意:
(1)字符串比较要加引号,表示引用;即不做变量替换可用单引号,做变量替换要用双引号;
(2)字符串比较时,要使用` `双中括号;
练习:
主机名如果为空,或者为localhost.locadomain或者包含localhost或者包含linux则统统将其设为www.magedu.com
]# [ -z "$name" -o "$name"=="localhost.locadomain" -o "$name"=~"localhost" -o "$name"=~"linux" ] && hostname www.magedu.com
注意:使用-o逻辑时,字符测试比较符不能有空格且字符要用引号,不能用双中括号。
文件测试:
存在性测试
-a FILE
-e FILE:文件的存在性测试,如果存在,则为真;
例如:
]# [ -e /etc/passwd ]
文件的存在性及文件类型测试:既能测试存在性又能测试类别
-b FILE:文件是否存在并且为块设备文件;
-c FILE:文件是否存在并且为字符设备文件;
-d FILE:文件是否存在并且为目录文件;
-f FILE:文件是否存在并且为普通文件;
-h FILE或-L FILE:文件是否存在并且为符号链接文件;
-p FILE:文件是否存在并且为命名管道文件;
-S FILE:文件是否存在并且为套接字文件;
例如:
]# [ -c /dev/null ]
文件权限测试:
-r FILE:文件是否存在并且对当前用户可读;
-w FILE:文件是否存在并且对当前用户可写;
-x FILE:文件是否存在并且对当前用户可执行;
例如:
[ -w /etc/passwd ] && echo ok
特殊权限测试:
-u FILE:文件是否存在并且拥有SUID权限;
-g FILE:文件是否存在并且拥有SGID权限;
-k FILE:文件是否存在并且拥有sticky权限;
例如:
[ -u /usr/bin/passwd ]
文件是否有内容:
-s FILE:是否有内容,有则为真:
例如:
[ -s /etc/fstab ]
文件时间戳是否变化:
-N FILE:文件自从上一次读操作之后,是否被改过;
文件从属关系测试:
-O FILE:当前用户是否为文件的属主;
-G FILE:当前用户是否为文件的属组;
双目测试:
FILE1 -ef FILE2:文件1与文件2是否指向同一个文件系统上相同inode的硬链接;
FILE1 -nt FILE2:文件1是否新于文件2;
FILE1 -ot FILE2:文件1是否旧于文件2;
组合测试条件:
逻辑运算:
第一种方式:
COMMAND1 && COMMAND2:与运算;
COMMAND1 || COMMAND2:或运算;
! COMMAND(取反)
例如:
[ -O FILE ] && [ -r FILE ]
第二种方式:
expresssion1 -a expression2
expresssion1 -o expression2
! expresssion或-not expresssion
例如:
[ -O FILE -a -x FILE ]:当前引用是否为file文件的属主,且是否有执行权限;
练习:将当前主机名称保存至hostName变量中;
主机名如果为空,或者为localhost.localdomain,则将其设置为www.magedu.com;
hostName=$(hostname)
[ -z "$hostName" -o "$hostName"=="localhost.localdomain" -o "$hostName"=="localhost" ] && hostname www.magedu.com
脚本的状态返回值:
默认是脚本中执行的最后一条命令的状态返回值;
自定义状态退出状态码;
exit [n]:n为自己指定的状态码;
注意:shell进程遇到exit时,即会终止,因此,整个脚本执行即为结束;
所以,exit不能随意添加使用,要使用条件测试判断后,确定是否添加exit退出;
埋点调试;使用$?能判断是在哪个位置退出的,定位问题常用;
练习:
脚本实现,一行命令实现,magedu用户是否存在,不存在,则添加,如果存在,以状态码为5的方式退出;
id mageedu &> /dev/null || useradd mageedu && exit 5
或:id mageedu &> /dev/null && exit 5 || useradd mageedu
bash脚本编程之向脚本传递参数
位置参数变量
命令行:*.sh argu参数1 argu参数2 argu参数3...
脚本里引用方式:$1 $2 $3...
myscript.sh agru1 argu2...
引用方式:
$1:保存argu参数1;
$2:保存argu参数2;
...
${10},${11}...
轮替:
shift [n]:位置参数轮替,n为数字默认为1;表示为一次轮替前几个参数;
$1,$2...就叫位置参数变量,每次运行脚本时,通过传递参数变化来改变这些参数变量的值;
例如:
my_shell.sh ubuntu centos linux
引用方式:
脚本中使用$1,$2,$3
useradd $1
useradd $2
useradd $3
解释:$1=ubuntu,$2=centos,$3=linux
shift #:表示删掉指定参数;把后面的参数往前补充;
练习:
脚本实现,通过命令传递两个文本路径给脚本,计算其空白行数(^$)之和;
a=$(egrep "^$" $1|wc -l)
b=$(egrep "^$" $2|wc -l)
sum=$[$a+$b]
echo "a is :$a"
echo "b is $b"
echo "sum is :$sum"
shift #:会自动踢出命令参数;
特殊变量:
$0:保存脚本文件路径本身;
$#:保存脚本参数的个数;
$*:保存所有参数;把每个参数当做一个个独立参数显示;
$@:保存所有参数;把每个参数合在一起,当做一个字符串参数显示;
$0:表示命令行给定脚本文件路径;
例如:命令行]# bash /tmp/parameter_blanksum.sh
脚本内容:echo $0
显示结果为:/tmp/parameter_blanksum.sh
如果命令行是:bash parameter_blanksum.sh
脚本内容不变,显示结果为:parameter_blanksum.sh
$#:表示脚本参数的个数;
例如:
]# bash parameter_blanksum.sh /etc/init.d/functions /etc/init.d/network
脚本内容:echo $#
显示结果为:2
$*:表示参数为多个字符串;
$@:表示参数为一个字符串;
例如:命令行]# bash parameter_blanksum.sh /etc/init.d/functions /etc/init.d/network
脚本内容:echo $*
echo $@
显示结果为:/etc/init.d/functions /etc/init.d/network
/etc/init.d/functions /etc/init.d/network
但是$*表示为2个分开的路径,而$@表示为一整行为一个字符串。
练习:
1、判断/etc/passwd是否为文件,如果为文件,则输出/etc/passwd is files,该路径通过命令传递的方式传入,当传入的命令个数大于1,则报错;
[ $# -gt 1 ] && echo "something wrong " && exit 100
[ -f $1 ] && echo "/etc/passwd is files"
2、在root目录下,写脚本,可以一次性添加3名用户,通过传递参数的方式,进行用户添加,当传递的参数不符合3个的时候,报错;
当三名用户添加完成后,需要将脚本进行权限加固(锁机制,不能再执行),将脚本权限改成属主可读可写可执行;
! [ $# -eq 3 ] && echo "please give me three username" && exit 1
useradd $1 && a=1
useradd $2 && b=1
useradd $3 && c=1
sum=$[$a+$b+$c]
[ $sum -eq 3 ] && echo "$1 $2 $3" ||exit 2
chmod 700 $0
echo $1 is added
echo $2 is added
echo $3 is added
echo "this scripts mode is 700"
或:
[ $# -gt 3 ] || [ $# -lt 3 ] && echo "something wrong,give three user" && exit 1
id $1 &> /dev/null && echo "$1 exist" || useradd $1
id $2 &> /dev/null && echo "$2 exist" || useradd $2
id $3 &> /dev/null && echo "$3 exist" || useradd $3
echo "three users $1,$2,$3 are added success"
chmod 700 $0
echo "this script mode is 700"
过程式编程语言的代码执行顺序:
顺序执行:逐条运行;
选择执行:
代码存在一个分支:条件满足时才会执行;
两个或以上的分支:只会执行其中一个满足条件的分支;
循环执行:
某代码片段(循环体)要执行0、1或多个来回(循环);
顺序执行
条件选择:
(1)&&,||
(2)if语句(单分支,双分支,多分支)
(3)case语句
循环执行:for,while,until
bash脚本编程之if语句选择分支
条件判断:
shell执行是顺序执行的
选择分支
单分支的if语句
if 测试条件;then(如果条件为真,则执行下面语句)
代码分支
fi
或
if 测试条件
then
代码分支
fi
双分支的if语句
if 测试条件;then
条件为真时执行的分支
else
条件为假时执行的分支
fi
多分支的if语句
if 测试条件;then
条件为真时候执行的分支
elif 测试条件;then
elif 测试条件;then
elif 测试条件;then
else
条件为假时候执行的分支
fi
例如:
通过参数传递给一个用户名给脚本,此用户如果不存在则创建用户;
if ! grep "^$1\>" /etc/passwd &> /dev/null;then
useradd $1
echo $1|passwd --stdin $1 &> /dev/null
echo "add a user $1 finished"
else
echo "$1 is exist"
fi
示例:通过参数传递一个用户名给脚本,此用户不存在时,则添加;(判断表达方法:一种是命令,另一种是表达式放在中括号中或用test表示,判断用户是否存在用id或grep)
~]# vim useradd.sh
if ! grep "^$1\>" /etc/passwd &> /dev/null;then
useradd $1
echo $1 | passwd --stdin $1 &> /dev/null
echo "add user $1 finished"
fi
~]# ./useradd.sh hbase
执行结果为:add user hbase finished
加入了判断参数是否存在的判断if语句:
~]# vim useradd.sh
if [ $# -lt 1 ];then
echo "at least one username"
exit 2
fi
if ! grep "^$1\>" /etc/passwd &> /dev/null;then
useradd $1
echo $1 | passwd --stdin $1 &> /dev/null
echo "add user $1 finished"
fi
~]# ./useradd.sh hbase
变为双分支判断if语句:
~]# vim useradd.sh
if [ $# -lt 1 ];then
echo "at least one username"
exit 2
fi
if grep "^$1\>" /etc/passwd &> /dev/null;then
echo "user $1 exists"
else
useradd $1
echo $1 | passwd --stdin $1 &> /dev/null
echo "add user $1 finished"
fi
~]# ./useradd.sh hbase
练习:
1、通过命令参数,给定两个数字,输出其中较大的数值;
if [ $1 -gt $2 ] ;then
echo $1 is bigger
else
echo $2 is bigger
fi
或:
if [ ! $# -eq 2 ];then
echo "give two number"
exit 2;
fi
if [ $1 -gt $2 ];then
echo "the bigg is $1"
elif [ $1 -lt $2 ];then
echo "the bigg is $2"
else
echo "$1=$2"
fi
2、通过命令参数,给定两个文本文件名,如果某文件不存在,则结束脚本,如果都存在返回每个文件的行数,并echo其中行数较多的文件名;
[ $# -ne 2 ] && echo "give two file" && exit 1
if ! [ -e $1 ];then
echo "$1 is not find"
exit 2
elif ! [ -e $2 ];then
echo "$2 is not find"
exit 3
fi
f1=$(cat $1|wc -l)
f2=$(cat $2|wc -l)
echo "$1 line is $f1"
echo "$2 line is $f2"
if [ $f1 -gt $f2 ];then
ehco "$f1 is more"
elif [ $f1 -eq $f2 ];then
echo "f1 and f2 the same"
else
echo "$f2 is more"
fi
或:
if ! [ $# -eq 2 ];then
echo "give 2 files"
exit 20;
elif ! [ -e $1 ];then
echo "$1 is not exist"
elif ! [ -e $2 ];then
echo " $2 is not exist"
exit 30;
else
f1=$(cat $1|wc -l)
f2=$(cat $2|wc -l)
echo "f1=$f1"
echo "f2=$f2"
fi
if [ $f1 -gt $f2 ];then
echo "file1 line is more :$f1"
echo "$(basename $1) line is more"
elif [ $f1 -lt $f2 ];then
echo "file2 line is more :$f2"
echo "$(basename $2) line is more"
else
echo "f1-$f1 and f2-$f2 is same"
fi
3、通过命令行参数给定一个用户名,判断ID号是偶数还是奇数;
多分支if语句
选择执行:
(1)&&,||
(2)if语句
(3)case语句
if语句:三种格式
单分支的if语句
if CONDITION;then
if-ture-分支
fi
双分支的if语句
if CONDITION;then
if-true-分支
else
if-false-分支
fi
多分支的if语句
if CONDITION1;then
条件1为真分支
elif CONDITION2;then
条件2为真分支
elif CONDITION3;then
条件3为真分支
...
elif CONDITIONn;then
条件nweizhen分支
else
所有条件均不满足时的分支
fi
注意:即便多个条件可能同时满足,分支只会执行其中一个,首先测试为真的条件分支执行后,就退出;
示例:通过脚本参数,传递文件路径给脚本,判断此文件的类型;
if [ $# -lt 1 ];then
echo "at lease one path"
exit 1
fi
if ! [ -e $1 ];then
echo "no such file"
eixt 2
fi
if [ -f $1 ];then
echo "common file"
elif [ -d $1 ];then
echo "directory file"
elif [ -L $1 ];then
echo "symbolic link file"
elif [ -b $1 ];then
echo "block special file"
elif [ -p $1 ];then
echo "pipe file"
elif [ -S $1 ];then
echo "socket file"
elif [ -c $1 ];then
echo "character special file"
case语句其实就是简化版的多分支if语句,但未所有if语句都能转化为case语句;
注意:if语句可以嵌套使用;
练习:
1、写脚本实现如下功能:
(1)传递参数给脚本,此参数为用户名;
(2)根据其id来判断用户类型;
0:管理员
1-999:系统用户
1000+:登录用户
(3)如不存在,可添加此用户;
[ $# -lt 1 ] && echo "at least one username" && exit 1
! id $1 &> /dev/null && echo "no such user" && exit 2
userid=$(id -u $1)
if [ $userid -eq 0 ];then
echo "root"
elif [ $userid -ge 1000 ];then
echo "login user"
else
echo "system user"
fi
2、写脚本实现如下功能:
(1)列出如下菜单给用户
disk)show disks info(fdisk -l /dev/sda)
mem)show memory info(free -m)
cpu)show cpu info(使用cat /proc/cpuinfo或lscpu)
*)quit
(2)提示用户给出自己的选择,然后显示对应其选择的相应系统信息;
cat << EOF
disk) show disk info
mem) show memory info
cpu) show cpu info
*) QUIT
EOF
read -p "please choice : " option
if [[ "$option" == "disk" ]];then
fdisk -l /dev/[sh]d[a-z]
elif [[ "$option" == "mem" ]];then
free -m
elif [[ "$option" == "cpu" ]];then
lscpu
else
echo "your choice fault option"
fi
bash脚本编程之for循环
循环执行:将一段代码重复执行0、1或多次;
进入循环条件:只有条件满足时才进入循环;
退出循环条件:每个循环都应该有退出条件,有机会退出循环;
bash脚本有三种循环方式:
for循环
while循环
until循环
for循环有2种格式:
(1)遍历列表
(2)控制变量
变量赋值有三种方式:
(1)VAR=VALUE,直接赋值;
(2)通过read,实现变量赋值;
(3)for循环中,用列表赋值给变量;
遍历列表:
for VARAIBLE in LIST;do
循环体
done
进入条件:只有列表有元素,即可进入循环;
退出条件:列表中的元素遍历完成;
LIST的生成方式:
(1)直接给出;
(2)整数列表;
(a){start..end}自动展开;
(b)seq #:从1列出到#的数字;
seq start end:从开始数字列出到结束数字;
seq start step end:从开始数字,根据步长,列出结束数字;
seq命令:显示一系列数字
seq [start [increment]] last
(3)能返回一个列表的命令;
例如:ls命令
(4)glob通配符机制;
例如:/etc/p*
(5)变量引用
例如:$@,$*
...
例如:
seq 10:列出1 2 3 4 5 6 7 8 9 10
seq 5 10:列出5 6 7 8 9 10
seq 1 2 10:列出1 3 5 7 9
seq 2 2 10:列出2 4 6 8 10
例如:循环添加三个用户aaa,bbb,ccc;
for username in aaa bbb ccc;do
if id $username &>/dev/null;then
echo "$username exists"
esle
useradd $username && echo "add user $username finished"
fi
done
例如:在tmp目录下创建10个文件f1-10;
for filename in {1..10};do
touch /tmp/f$filename
done
注意:在如何时候,要考虑判断条件;如上例,应该先判断文件是否存在;
例如:求100内所有正整数之和;
declare -i sum=0
for i in {1..100};do
sum=$[$sum+$i]
done
echo $sum
例如:计算100内的奇数和;
declare -i sum=0
for i in $(seq 1 2 100);do
sum=$[$sum+$i]
done
echo $sum
例如:计算100内的偶数和;
declare -i sum=0
for i in $(seq 2 2 100);do
sum=$[$sum+$i]
done
echo $sum
示例:判断/var/log目录下的每个文件的类型;
file /var/log/*即可判断,但要求用循环实现;
for filename in /var/log/*;do
if [ -f $filename ];then
echo "this is common file"
elif [ -d $filename ];then
echo "this is directory file"
elif [ -L $filename ];then
echo "this is softlink"
elif [ -b $filename ];then
echo "this is block file"
elif [ -c $filename ];then
echo "this is character file"
elif [ -S $filename ];then
echo "this is socket file"
elif [ -p $filename ];then
echo "thisi is pipe file"
else
echo "unknow file type"
fi
done
练习:
1、分别求100内偶数之和,奇数之和;
计算100内的奇数和;
declare -i sum=0
for i in $(seq 1 2 100);do
sum=$[$sum+$i]
done
echo $sum
计算100内的偶数和;
declare -i sum=0
for i in $(seq 2 2 100);do
sum=$[$sum+$i]
done
echo $sum
2、计算当前系统上所有用户的id之和;
declare -i sum=0
for i in $(cut -d: -f3 /etc/passwd);do
echo "\$sum=$sum,\$i=$i"
sum=$[$sum+$i]
done
echo $sum
3、通过脚本参数传递一个目录给脚本,然后计算此目录下所有文本文件的行数之和;并说明此类文件的总数;
! [ $# -eq 1 ] && exit 1
! [ -d $1 ] && echo "please give a comment file" && exit 2
declare -i filesum=0
declare -i sum=0
declare -i A=0
for i in $(ls $1);do
if [ -f $i ];then
a=$(wc -l $i|cut -d" " -f1)
A+=1
sum=$[$sum+$a]
echo "file line sum=$a,files sum $A,all file line sum is $sum"
else
echo "this file not text type"
fi
done
for循环格式:
for VARAIBLE in LIST;do
循环体
done
LIST列表生成方式有多种:直接给出,{#..#},或glob(/tpm/test/*)等任何能够返回列表的命令都可以;
for循环的特殊用法:
for ((控制变量初始化;条件判断表达式;控制变量的修正语句)); do
循环体
done
注意:条件判断可直接使用<,>;
控制变量初始化:仅在循环代码开始运行时执行一次;
控制变量修正语句:会在每轮循环结束会先进行控制变量修正运算,而后再做条件判断;
示例:求100内整正数之和
declare -i sum=0
for ((i=1;i<=100;i++));do
let sum+=$i
done
echo "sum: $sum"
练习:打印99乘法表
for ((j=1;j<=9;j++));do
for ((i=1;i<=j;i++));do
echo -e -n "${i}X${j}=$[${i}*${j}]\t"
done
echo
done
bash脚本编程之while循环和until循环
while循环:
while CONDITION;do
循环体
循环扩展变量修正表达式(条件修正表达式)
done
进入条件:CONDITION参数为“真”;
退出条件:CONDITION参数为“假”;
until循环:
until CONDITION;do
循环体
循环扩展变量修正表达式(条件修正表达式)
done
进入条件:CONDITION参数为“假”;
退出条件:CONDITION参数为“真”;
until就相当于在while条件前取反(!)的效果;
例如:求100内正整数和;
比for优势在于,如果数值比较多,for的列表会占用内存,while则使用的是变量,占用内存空间很小;
for循环:
for i in {1..100};do
sum=$[$sum+$i]
done
echo $sum
while循环:
declare -i sum=0
declare -i i=1
while [ $i -le 10 ];do
sum=$[$sum+$i]
let i++
done
echo $sum
until循环:
declare -i sum=0
declare -i i=1
until [ $i -gt 100 ];do
let sum+=$i
let i++
done
echo $sum
练习:分别使用for、while、until各自实现;
1、求100内所有偶数之和、奇数之和;
2、创建10个用户,分别为user101-user110;密码同用户名;
3、打印九九乘法表;
4、打印逆序九九乘法表;
1x1=1
1x2=2,2x2=4
1x3=3,2x3=6,3x3=9
循环嵌套:
外循环控制乘数,内循环控制被乘数;
for j in {1..9};do
for i in $(seq 1 $j);do
echo -n -e "${i}x${j}=$[${i}*${j}]\t"
done
echo
done
进入条件:
for:列表元素非空;
while:条件测试结果为真;
until:条件测试结果为假;
退出条件:
for:列表元素遍历完成;
while:条件测试结果为假;
until:条件测试结果为真;
循环控制语句:
continue:提前结束本轮循环,而直接进入下一轮循环判断;
while CONDITION1; do
CMD1
...
if CONDITIONS2; then
continue
fi
CMDn
...
done
示例:求100内偶数和;
declare -i evensum=0
declare -i i=0
while [ $i -le 100 ];do
let i++
if [ $[$i%2] -eq 1 ];then
continue
fi
let evensum+=$i
done
echo "evensum : $evensum"
break:提前跳出循环
while CONDITION1; do
CMD1
...
if CONDITIONS2; then
break
fi
done
break #:在循环嵌套时,指明跳出哪层循环;
sleep命令:
delay for a specified amount of time
sleep #
#:为数字默认单位是s秒钟,可用m分钟,h小时,d天为单位;
创建死循环:
while true;do
循环体
done
退出方式:
某个测试条件满足时,让循环体执行break命令;
until false;do
循环体
done
示例:求100内奇数和
declare -i oddsum=0
declare -i i=1
while true; do
let oddsum+=$i
let i+=2
if [ $i -gt 100 ];then
break
fi
done
练习:每隔3秒钟到系统上获取已经登录的用户的用户信息,其中,如果aaa用户登录系统,则记录于日志中,并退出;
while true;do
if who | grep "^aaa\>" &>/dev/null;then
break
fi
sleep 3
done
echo "$(date +"%F %T")aaa loggend on " >> /tmp/users.log
或改写为:
until who |grep "^aaa\>" &>/dev/null;do
sleep 3
done
echo "$(date +"%F %T") aaa longed on" >> /tmp/user.log
while循环的特殊用法(遍历文件的行):
while read VARIABLE;do
循环体;
done < /PATH/FROM/SOMEFILE
依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将其值赋值给VARIABLE变量;
示例:找出ID号为偶数的用户,显示器用户名、ID及默认shell;
while read line;do
userid=$(echo $line |cut -d: -f3)
username=$(echo $line |cut -d: -f1)
usershell=$(echo $line |cut -d: -f7)
if [ $[$userid%2] -eq 0 ];then
echo "$username,$userid,$usershell"
fi
done < /etc/passwd
bash脚本编程之用户交互
脚本参数
用户交互:通过键盘输入数据
read [OPTION] ... [name ...]
-p 'PROMPT':输出提示符,设置提示信息;;
-t TIMEOUT:设定超时退出时间;
name:是在脚本里设定的变量;
bash -n /path/to/some_script
检测脚本中的语法错误
bash -x /path/to/some_script
调试执行
例如:脚本实现,使用read添加用户,密码同用户名;
read -p "plase give a username and passwd: " a b
useradd $a
echo $a
echo $b
echo "$b "| passwd $a --stdin &> /dev/null
例如:脚本交互,输入指定用户名,创建用户并设定密码;
read -p "Enter a username: " name
[ -z "$name" ] && ehco " a username is needed " && exit 2
read -p "enter a passwd for $name,[passwd]: " passwd
[ -z "$passwd" ] && passwd="passwd"
if id $name &> /dev/null;then
echo "$name exists"
else
useradd $name
echo "$passwd " | passwd --stdin $name &> /dev/null
echo "add user $name finished"
fi
练习:
1、判断给定是两个数值,谁大谁小
给定数值的方法;命令参数,命令交互
read -p "give two number: " a b
! [ $# -eq 2 ] && echo "give two number" && exit 1
if [ $a -gt $b ];then
echo " $a is bigger"
else
echo "$b is bigger"
fi
2、提示用户输入一个字符串,如果输入的是quit,则退出,否则显示其输入的字符串内容;
read -p "give a string: " a
if ! [ $a == quit ];then
echo "show string is $a"
else
exit 1
fi
3、背景:公司新员工,要开通系统账号和统计出新员工的信息(通过交互方式);
让用户指定一个用户名和密码,如果用户名之前存在,先进行删除,之后则为用户添加系统账号;
完成后,需要统计员工的手机号,email,qq,年龄,收集后存储到该用户的家目录中;
以上完成后,询问该用户是否需要给用户单独创建一个工作目录为/data/username,默认是需要,如果不需要,则输入n或N;
read -p "input a password for useradd: " password
echo $password|passwd --stdin $username &> /dev/null
echo "user's password is add finished"
read -p "input personal infomation:tel): " tel
echo "$username tel:$tel" >> /home/$username/userinfo.txt
read -p "input personal infomation:email): " email
echo "$username email:$email" >> /home/$username/userinfo.txt
read -p "input personal infomation:qq): " qq
echo "$username qq:$qq" >> /home/$username/userinfo.txt
read -p "input personal infomation:age): " age
echo "$username age:$age" >> /home/$username/userinfo.txt
read -p "you if create personal work DIR (y/n,N)default: y: " dir
if [ "$dir" == "y" ];then
mkdir -p /data/$username
elif [ "$dir" == "n" -o "$dir" == "N" ];then
echo "you choice nocreate personal work DIR,bye"
exit 2
fi
bash脚本编程之条件选择case语句
case语句:
if中的多分支in语句:
if CONDITION1;then
分支1(CONDITION1为真时执行的语句)
elif CONDITION2;then
分支2(CONDITION2为真时执行的语句)
...
else CONDITION;then
分支n
fi
示例:显示菜单给用户,
disk) show disk info
mem) show memory info
cpu) show cpu info
*) QUIT
要求:(1)提示用户给出自己的选择;
(2)正确的选择给出相应信息,否则,提示重新选择正确的选项;
cat << EOF
cpu) display cpu information
mem) display memory infomation
disk) display disks infomation
quit) quit
==============================
EOF
read -p "Enter your choice: " option
while [ "$option" != "cpu" -a "$option" != "mem" -a "$option" != "disk" -a "$option" != "quit" ];do
echo "cpu,mem,disk,quit,please see "
read -p "Enter your choice again: " option
if [ "$option" == "cpu" ];then
lscpu
elif [ "$option" == "mem" ];then
free -m
elif [ "$option" == "disk" ];then
fdisk -l /dev/[hs]d[a-z]
else
echo "quit"
exit 0
fi
done
注意:用一个变量与多个值比较时,就可使用case语句替换成if多分支语句;
case语句的语法格式:
case $VARAIBLE in
PAT1)
分支1
;;
PAT2)
分支2
;;
...
*)
分支n
;;
esac
表示变量VARAIBLE与多个模式比较PAT1、PAT2、...,每个分支用双分号结尾,多分支执行特点是只要第一个满足条件,就不执行下面的语句,即使下面的条件也满足也不执行了;双分号可单独成行,也可在分支语句的最后;PAT只支持glob风格的通配符;仅适用于一般都是变量与PAT做等值比较或模式匹配比较时,可替代if多分支语句;
示例:上例用case实现
cat << EOF
cpu) display cpu information
mem) display memory infomation
disk) display disks infomation
quit) quit
==============================
EOF
read -p "Enter your choice: " option
while [ "$option" != "cpu" -a "$option" != "mem" -a "$option" != "disk" -a "$option" != "quit" ];do
echo "cpu,mem,disk,quit,please see "
read -p "Enter your choice again: " option
done
case $option in
cpu)
lscpu;;
mem)
free -m;;
disk)
fdisk -l /dev/[hs]d[a-z];;
*)
echo "quit"
exit 0;;
esac
case支持glog风格的通配符:
*:任意长度的任意字符;
?:任意单个字符;
[]:范围内任意单个字符;
a|b:a或b;
示例:脚本实现服务框架(服务脚本不能以.sh结尾)
$lockfile,/var/lock/subsys/SCRIPT_NAME
(1)此脚本可接受start,stop,restart,status四参数之一;
(2)如果参数非此四参数,则提示使用帮助后退出;
(3)start则创建lockfile,并显示启动;stop则输出lockfile,并显示停止;restart则先删除此文件再创建文件,然后显示重启完成;status则如果lockfile操作则显示running,否则显示为stopped;
# 下面2语句为服务脚本格式
# chkconfig: - 50 50
# description: test service script
#
prog=$(basename $0)
lockfile=/var/lock/subsys/$prog
case $1 in
start)
if [ -f $lockfile ];then
echo "$prog is running yet"
else
touch $lockfile
[ $? -eq 0 ] && echo "start $prog finished"
fi
;;
stop)
if [ $lockfile ];then
rm -f $lockfile
[ $? -eq 0 ] && echo "stop $prog finished"
else
echo "$prog is not running"
fi
;;
restart)
if [ -f $lockfile ];then
rm -f $lcokfile
touch $lockfile
echo "restart $prog finished"
else
touch -f $lockfile
echo "start $prog finished"
fi
;;
status)
if [ -e $lockfile ];then
echo "$prog is running"
else
echo "$prog is stopped"
fi
;;
*)
echo "Usage :$prog {start|stop|restart|status}"
exit 1
esac
]# cp exercise.sh /etc/init.d/exercise
]# chkconfig --add exercise
]# chkconfig --list exercise
]# chmod +x /etc/init.d/exercise
服务运行以后都会在/var/lock/subsys/下,创建一个锁文件,名称同服务名称;
bash脚本编程之函数:function(功能)
在过程式编程里:实现代码重用
模块化编程
结构化编程
把一段独立功能的代码当做一个整体,并为之取一个名字;命名的代码段,此即为函数;
注意:定义函数的代码段不会自动执行,在调用时才执行;所谓调用函数,在代码中给定函数名即可;
函数名出现的任何位置,在代码执行时,都会被自动替换为函数代码;
语法一:
function function_name {
...函数体
}
语法二:
function_name() {
...函数体
}
函数的生命周期:每次被调用时创建,返回时终止;函数也有状态返回值,其状态返回值默认为函数体中运行的最后一条命令的状态结果;就像脚本中使用exit #表现为自定义退出状态结果;而函数用return表示自定义状态返回值;
return [0-255]
0:成功;
1-255:失败;
示例:给定一(或二)个用户名,取得用户的ID号和默认shell;
没用函数:
if id "$1" &> /dev/null;then
grep "^$1\>" /etc/passwd|cut -d: -f3,7
else
echo "no such user"
fi
使用函数:
userinfo() {
if id "$username" &> /dev/null;then
grep "^$username\>" /etc/passwd|cut -d: -f3,7
else
echo "no such user"
fi
}
username=$1
userinfo
username=$2
userinfo
示例:改写脚本服务框架(服务脚本不能以.sh结尾)
# chkconfig: - 50 50
# description: test service script
#
prog=$(basename $0)
lockfile=/var/lock/subsys/$prog
start() {
if [ -f $lockfile ];then
echo "$prog is running yet"
else
touch $lockfile
lockfile=/var/lock/subsys/$prog
start() {
if [ -f $lockfile ];then
echo "$prog is running yet"
else
touch $lockfile
[ $? -eq 0 ] && echo "start $prog finished"
fi
}
stop() {
if [ $lockfile ];then
rm -f $lockfile
[ $? -eq 0 ] && echo "stop $prog finished"
else
echo "$prog is not running"
fi
}
restart() {
if [ -f $lockfile ];then
rm -f $lcokfile
touch $lockfile
echo "restart $prog finished"
else
touch -f $lockfile
echo "start $prog finished"
fi
}
status() {
if [ -e $lockfile ];then
echo "$prog is running"
else
echo "$prog is stopped"
fi
}
usage() {
echo "Usage :$prog {start|stop|restart|status}"
exit 1
}
case $1 in
start)
start ;;
stop)
stop;;
restart)
stop
start ;;
status)
suatus;;
*)
usage
exit 1;;
esac
函数返回值:
函数的执行结果返回值:
(1)使用echo或printf命令进行输出;
(2)函数体中调用的命令的执行结果;
函数的退出状态码:
(1)默认取决于函数体中执行的最后一条命令的退出状态码;
(2)自定义:使用return #
命令都有两种返回值,一种为执行结果返回值,一种为执行的状态结果返回值称为退出状态码;在脚本中实现命令替换,命令替换的地方就调用命令执行的结果,函数也一样,函数替换是调用函数出现的地方,就是函数代码执行的结果;printf命令:不能换行,但输出可定义格式化输出;在awk命令时再介绍;
函数可以接受参数:
传递参数给函数:
在函数体中,可以使用$1, $2, ...引用传递给函数的参数;还可在函数中使用$*或$@引用所有参数,$#引用传递的参数的个数;
在调用函数时,在函数名后面以空白符分隔给定参数列表即可,例如,testfunction arg1 agr2 agr3 ...
示例:添加10个用户,使用函数实现,用户名作为参数传递给函数;
]# bash -x exercise.sh aaa
addusers() {
if id $1 &> /dev/null;then
return 5
else
useradd $1
retval=$?
return $retval
fi
}
for i in {1..10};do
addusers ${1}${i}
retval=$?
if [ $retval -eq 0 ];then
echo "add user ${1}${i} finished"
elif [ $retval -eq 5 ];then
echo "user ${1}${i} exist"
else
echo "unkown error"
fi
done
练习:
1、脚本函数实现ping主机,来测试主机的在线状态;
主程序:实现测试172.16.1.1-172.16.67.1范围内个主机的在线状态;
分别使用for,while和until循环实现;
普通方式:
declare -i uphosts=0
declare -i downhosts=0
for i in {1..67};do
if ping -W 1 -c 1 172.16.$i.1 &>/dev/null;then
echo "172.16.$i.i is up"
let uphosts+=1
else
echo "172.16.$i.1 is down"
let downhosts+=1
fi
done
echo "Up hosts: $uphosts,Down hosts: $downhosts"
改版2:函数+while循环方式:
declare -i uphosts=0
declare -i downhosts=0
declare -i i=1
hostping() {
if ping -W 1 -c 1 $1 &>/dev/null;then
echo "$i is up"
let uphosts+=1
else
echo "$1 is down"
let downhosts+=1
fi
}
while [ $i -le 67 ];do
hostping 172.16.$i.1
let i++
done
echo "Up hosts: $uphosts,Down hosts: $downhosts"
改版3:使用return,在主程序中统计ping的主机数量;
declare -i uphosts=0
declare -i downhosts=0
declare -i i=1
hostping() {
if ping -W 1 -c 1 $1 &>/dev/null;then
echo "$i is up"
return 0
else
echo "$1 is down"
return 1
fi
}
while [ $i -le 67 ];do
hostping 172.16.$i.1
[ $? -eq 0 ] && let uphosts++ || let downhosts++
let i++
done
echo "Up hosts: $uphosts,Down hosts: $downhosts"
2、脚本函数实现,打印nn乘法表;
变量作用域:
局部变量:作用域是函数的生命周期;在函数结束时被自动销毁;
定义局部变量的方法:local VARIABLE=VALE
本地变量:作用域是运行脚本的shell进程生命周期;因此,其作用范围就是当前shell脚本程序文件;
例如:
name=tom
setname() {
name=jerry
echo "Function: $name"
}
setnaem
echo "Shell: $name"
此时函数的name和shell的name都是jerry,变量name为本地变量,可在函数中调用;
显示结果为:
Function:jerry
Shell: jerry
如果在以上语句中改为:local name=jerry
显示结果为:
Function:jerry
Shell: tom
bash脚本编程之函数递归:
函数直接或间接调用自身;
做阶乘或斐波那契数列时会用到函数递归;
例如:10!=10*9!=10*9*8!=10*9*8*7!...=10*9*8*7*6*5*4*3*2*1!
函数阶乘 传递参数为n
n*(n-1)!=n*(n-1)*(n-2)!=...
例如:斐波那契数列
每一个数值都是前2个数的和;求第n阶的数是多少就用函数;
1 1 2 3 5 8 13 21 ...
例如:传递参数为一个数字,实现数字的阶乘;
fact() {
if [ $1 -eq 0 -o $1 -eq 1 ];then
echo 1
else
echo $[$1*$(fact $[$1-1])]
fi
}
fact $1
例如:传递参数为一个数字,实现斐波那契数列
fab() {
if [ $1 -eq 1 ];then
echo -n "1 "
elif [ $1 -eq 2 ];then
echo -n "1 "
else
echo -n "$[$(fab $[$1-1])+$(fab $[$1-2])] "
fi
}
for i in $(seq 1 $1);do
fab $i
done
ech
bash脚本编程之数组
函数、case语句
case语句语法格式:
case $VARIABLE in
PAT1)
分支1
;;
PAT2)
分支2
;;
...
*)
分支n
;;
esac
PATTERN:为Glob通配符;
函数:能完成结构化编程,实现代码重用;
function function_name{
函数体
}
function_name() {
函数体
}
函数定义后,被调用才能执行;
函数调用:给定函数名即可调用,也可给函数传递参数,使用位置参数$1,$2,...
局部变量:local VARIABLE
生命周期为调用函数时,函数体内;
数组:
程序=指令+数据
bash脚本中指令:就是整个编程中的语法关键字,加上各种系统上的命令,加上定义的函数;
数据:存在变量、文件中、或数据组织结构中;
变量:存储单个元素的内存空间;
数组:存储多个元素的连续的内存空间;
数组名:整个数组只有一个名字;数组在第一个内存空间中存储的数字就是数组名指向的位置;
数组索引:编号从0开始;引用数组种的数据方式:
数组名[索引]
${ARRAY_NAME[INDEX]}
注意:bash-4及之后的版本,支持自定义索引格式,而不仅是0,1,2,...数字格式;
自定义索引格式的属组,称为关联数组;
声明数组:
declare -a NAME:声明索引数组;
declare -A NAME:声明关联数组;
查看声明帮助:
]# help declare
数组中的数据就是存在内存中的一段连续的内存空间,分别存储多个连续的数据,每个数据都是独立的被管理单元,只不过没有独立的名称,要通过数组名称来索引使用,数组名称加上一个下标可理解为数据在数组中的标识符;
数组中元素的赋值方式:
(1)一次只赋值一个元素:
ARRAY_NAME[INDEX]=value
例如:
]# animals[0]=pig
]# animals[1]=cat
]# echo $animals
]# echo ${animals[1]}
(2)一次赋值全部元素:
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)
会自动赋值为0,1,2,3,...
例如:
]# weekdays=("Monday" "Tuseday" "Wednesday")
]# echo ${weekdays[2]}
(3)只赋值特定元素;
ARRAY_NAME=([0]="VAL1" [3]="VAL4" ...)
为稀疏格式的数组;
注意:bash支持稀疏格式的数组;
例如:
]# sword=([0]="Yitian" [3]="Tulong")
]# echo ${sword[1]}:显示为空;
]# echo ${sword[3]}:显示为Tulong;
(4)read -a ARRAY_NAME
例如:
]# read -a Linux
sed awk find grep
]# echo ${Linux[1]}:显示为awk;
关联数组:
]# declare -A world:声明关联数组;
]# world[us]"America"
]# echo "${world[us]}":显示为America;
]# world[uk]"unite kingdom"
]# echo "${world[uk]}":显示为unite kingdom;
引用数组中的元素:${ARRAY_NAME[INDEX]}
注意:引用时,只给数组名,表示引用下标为0的元素;
${ARRAY_NAME[*]}:引用数组中的所有元素;
${ARRAY_NAME[@]}:同上;
数组的长度(数组中元素的个数):
${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}
例如:
]# echo "${#Linux[*]}:显示数组元素个数4个;
]# echo "${#animals[@]}:显示数组元素个数2个;
]# echo "${#animals}:显示数组第一个元素的字符个数为3个;
]# echo "${animals[*]}:显示数组中所有元素;可生成列表;
示例:生成10随机数,并找出其中最大值和最小值;
declare -a rand
declare -i max=0
for i in {0..9};do
rand[$i]=$RANDOM
echo ${rand[$i]}
[ ${rand[$i]} -gt $max ] && max=${rand[$i]}
done
echo "Max: $max"
练习:
1、生成10个随机数,由小到达排序;
2、定义数组,数组中的元素是/var/log目录下所有以.log结尾的文件;统计其下标为偶数的文件中的行数之和;
declare -a files
files=(/var/log/*.log)
declare -i lines=0
for i in $(seq 0 $[${#files[*]}-1]);do
if [ $[$i%2] -eq 0 ];then
let lines+=$(wc -l ${files[$i]} |cut -d' ' -f1)
fi
done
echo "lines: $lines"
引用数组中的所有元素:
${ARRAY_NAME[*]}
${ARRAY_NAME[@]}
也可只返回有限的几个元素;
数组元素切片:${ARRAY_NAME[@]:offset:number}
offset:元素偏移量;
number:取元素的个数;省略number时,表示取偏移量之后的所有元素;
例如:
]# files=(/etc/[Pp]*)
]# echo ${files[*]}
显示结果:
/etc/pam.d /etc/passwd /etc/passwd- /etc/pinforc /etc/pkcs11 /etc/pki /etc/plymouth /etc/pm /etc/polkit-1 /etc/popt.d /etc/postfix /etc/ppp /etc/prelink.conf.d /etc/printcap /etc/profile /etc/profile.d /etc/protocols /etc/pulse
]# echo ${files[@]:2:3}:表示从上面的数组中跳过2个,取3个;
显示结果:
/etc/passwd- /etc/pinforc /etc/pkcs11
]# echo ${files[@]:5}
显示结果:
/etc/pki /etc/plymouth /etc/pm /etc/polkit-1 /etc/popt.d /etc/postfix /etc/ppp /etc/prelink.conf.d /etc/printcap /etc/profile /etc/profile.d /etc/protocols /etc/pulse
向非稀疏格式的数组中追加元素:
ARRAY_NAME[${#ARRAY_NAME[*]}]=
${#ARRAY_NAME[*]}]:表示取出数组中的元素个数;
删除数组中的某元素:
unset ARRAY[INDEX]
关联数组:
declare -A ARRAY_NAME
ARRAY_NAME=([index_name1]="value1" [index_name2]="value2" ...)
bash脚本编程之bash的内置字符串处理工具:
字符串切片:(基于位置取字串)
${var:offset:number}:取字符串的子串;
${var: -length}:取字符串的最右侧的几个字符;
注意:冒号后必须有一个空格;
例如:
]# name=jerry
]# echo ${name:2}
显示结果:rry
]# echo ${name:2:2}
显示结果:rr
]# echo ${name: -4}
显示结果:erry
基于模式取字串:
${var#*word}:
其中word是只读的分隔符;功能:从左向右,查找var变量所存储的字符串中,第一次出现的word分隔符,删除字符串开头至此分隔符之间的所有字符;
${var##*word}:(可用于取路径基名、端口号)
其中word是只读的分隔符;功能:从左向右,查找var变量所存储的字符串中,最后一次出现的word分隔符,删除字符串开头至此分隔符之间的所有字符;
例如:
]# mypath="/etc/init.d/functions"
]# echo ${mypath#*/}:显示内容:etc/init.d/functions;
]# echo ${mypath##*/}:显示内容:functions;(即取路径基名)
${var%word*}:(可用于取路径名)
其中word是只读的分隔符;功能:从右向左,查找var变量所存储的字符串中,第一次出现的word分隔符,删除此分隔符至字符串尾部之间的所有字符;
${var%word*}:
其中word是只读的分隔符;功能:从右向左,查找var变量所存储的字符串中,最后一次出现的word分隔符,删除此分隔符至字符串尾部之间的所有字符;
例如:
]# echo ${mypath%/*}:显示内容:/etc/init.d;(即取路径名)
]# echo ${mypath%%/*}:显示内容:为空;
]# mypath="etc/init.d/functions"
]# echo ${mypath%%/*}:显示内容:etc;
]# url="http://www.magedu.com:80"
]# echo ${url##*:}:显示内容:80;(取端口号)
]# echo ${url%%:*}:显示内容:http;
查找替换:
${var/PATTERN/SUBSTI}:
查找var所表示的字符串中,第一次被PATTERN所匹配到的字符串,并将其替换为SUBSTI所表示的字符串;
${var//PATTERN/SUBSTI}:
查找var所表示的字符串中,所有被PATTERN所匹配到的字符串,并将其全部替换为SUBSTI所表示的字符串;
行首/尾锚定:
${var/#PATTERN/SUBSTI}:
查找var所表示的字符串中,行首被PATTERN所匹配到的字符串,并将其替换为SUBSTI所表示的字符串;
${var/%PATTERN/SUBSTI}:
查找var所表示的字符串中,行尾被PATTERN所匹配到的字符串,并将其替换为SUBSTI所表示的字符串;
注意:PATTERN中使用glob风格的通配符;
例如:
]# userinfo="root:x:0:0:root admin:/root:/binb/chroot"
]# echo ${userinfo/root/ROOT}:显示内容:ROOT:x:0:0:root admin:/root:/binb/chroot;替换第一次;
]# echo ${userinfo//r??t/ROOT}:显示内容:ROOT:x:0:0:ROOT admin:/ROOT:/binb/chROOT;替换所有;
]# echo ${userinfo/#r??t/ROOT}:显示内容:ROOT:x:0:0:root admin:/root:/binb/chroot;行首替换;
]# echo ${userinfo/%r??t/ROOT}:显示内容:root:x:0:0:root admin:/root:/binb/chROOT;行尾替换;
查找删除:
${var/PATTERN}:查找var字符串中,只删除第一次被PATTERN匹配到的字符串;
${var//PATTERN}:查找var字符串中,删除所有被PATTERN所匹配到的字符串;
${var/#PATTERN}:查找var所表示的字符串中,只删除行首被PATTERN所匹配到的字符串;
${var/%PATTERN}:查找var所表示的字符串中,只删除行尾被PATTERN所匹配到的字符串;
字符大小写转换:
${var^^}:把var中的所有小写字符转换成大写字符;
${var,,}:把var中的所有大写字符转换成小写字符;
例如:
]# url="http://www.magedu.com:80"
]# echo ${url^^}:显示内容:HTTP://WWW.MAGEDU.COM:80;转为大写:
]# myurl=${url^^}
]# echo ${myurl,,}:显示内容:http://www.magedu.com:80;转为小写;
变量赋值:
${var:-VALUE}:如果var变量为空,或未设置,则返回VALUE,否则,返回var变量值;
${var:=VALUE}:如果var变量为空,或未设置,则返回VALUE并将VALUE赋值给var变量,否则,返回var变量值;
例如:
变量hi为空,没有定义值;
]# echo ${hi:-world}:显示内容:world;
]# hi="hello"
]# echo ${hi:-world}:显示内容:hello;
]# echo ${hi:=world}:显示内容:hello;
]# unset hi:撤销hi变量的值;
]# echo ${hi:=world}:显示内容:world;
]# echo $hi:此时hi变量被赋值了;
${var:+VALUE}:如果var变量不空,则返回VALUE;否则,也没什么意义;
${var:?ERRO_INFO}:如果var变量为空,或未设置,则返回ERRO_INFO为错误提示;否则,返回var的值;
练习:脚本完成如下功能;(一定完成,后续课程实验要用到)
(1)提示用户输入一个可执行的命令的名称;
(2)获取此命令所依赖到的所有库文件列表;
(3)复制命令至某目录(例如/mnt/sysroot,即把此目录当做根)下的对应的路径中;
bahs,/bin/bash ==> /mnt/sysroot/bin/bash
usradd,/usr/sbin/useradd ==> /mnt/sysroot/usr/sbin/usradd
(4)复制此命令依赖到的所有库文件至目录目录下的对应路径下;
/lib64/ld-linux-x8664.so2 ==> /mnt/sysroot/lib64/ld-linux-x8664.so.2
进一步:
每次复制完成一个命令后,不要退出,而是提示用户继续输入要复制的其它命令,并重复完成如上功能,直到用户输入“quit”退出脚本;
写一个脚本:前面的练习解答
使用ping命令探测,172.16.1.1-172.16.67.1范围内的所有主机是否在线,在线显示为up,不在线显示为down,分别统计主机数量;
分别使用for,while和until循环实现;
普通方式:
declare -i uphosts=0
declare -i downhosts=0
for i in {1..67};do
if ping -W 1 -c 1 172.16.$i.1 &>/dev/null;then
echo "172.16.$i.i is up"
let uphosts+=1
else
echo "172.16.$i.1 is down"
let downhosts+=1
fi
done
echo "Up hosts: $uphosts,Down hosts: $downhosts"
改版2:函数+while循环方式:
declare -i uphosts=0
declare -i downhosts=0
declare -i i=1
hostping() {
if ping -W 1 -c 1 $1 &>/dev/null;then
echo "$i is up"
let uphosts+=1
else
echo "$1 is down"
let downhosts+=1
fi
}
while [ $i -le 67 ];do
hostping 172.16.$i.1
let i++
done
echo "Up hosts: $uphosts,Down hosts: $downhosts"
改版3:使用return,在主程序中统计ping的主机数量;
declare -i uphosts=0
declare -i downhosts=0
declare -i i=1
hostping() {
if ping -W 1 -c 1 $1 &>/dev/null;then
echo "$i is up"
return 0
else
echo "$1 is down"
return 1
fi
}
while [ $i -le 67 ];do
hostping 172.16.$i.1
[ $? -eq 0 ] && let uphosts++ || let downhosts++
let i++
done
echo "Up hosts: $uphosts,Down hosts: $downhosts"
练习:脚本实现,探测C类、B类、A类网络中的所有主机是否在线;
cping() {
local i=1
while [ $i -le 245 ];do
if ping -W 1 -c 1 $1.$i &>/dev/null;then
echo "$1.$i is up"
else
echo "$1.$i is down"
fi
let i++
done
}
#cping 192.168.0
bping() {
local j=0
while [ $j -le 255 ];do
cping $1.$j
let j++
done
}
#bping 172.16
aping() {
local x=0
while [ $x -le 255 ];do
bping $1.$x
let x++
done
}
aping 10
扩展:可改造以上脚本,提示用户输入任何网络地址或网络地址,获取其网络,并扫描其网段;
都可ping这个地址,先判断ip地址类别,然后调用相应的函数,如输入的是10.0.0.1,把ip地址第一段切出来进行比较是否为A(1-127)、B(128-191)、C(192-223))类网,然后再各自范围内调用相应的函数实现;
bash脚本编程之信号捕捉
trap命令:就在脚本第一行,张网捕捉信号即可;
trap 'COMMAND' SIGANLS
-l:列出信号;
常可以进行捕捉信号:HUP、INT
COMMAND:可以使用分号分隔多个命令,还可以是一个函数;
注意:trap命令不能捕捉信号为:15-SIGTERM(terminal)和9-SIGKILL(kill);
因为捕捉信号的主要目的在于可以定义一旦信号到达以后,作出什么操作,可以不是默认操作;所以kill信号等不能随意被捕捉;
查看信号列表:
]# trap -l
]# kill -l
查看信号类型解释:
]# man 7 signal
例如:
trap 'echo "quit"; exit 1' INT
for i in {1..254};do
ping 172.16.$i.1
done
示例:使用trp捕捉,函数实现
ping172.16.1-254.1把结果保存在/tmp/目录下创建的临时目录下,然后退出时,再删掉那些临时文件;
此脚本,不能删除最后一次没有捕捉到的临时文件;
declare -a hosttmpfiles
trap 'mytrap' INT
mytrap() {
echo "quit"
rm -f ${hosttmpfiles[@]}
exit 1
}
for i in {1..50};do
tmpfile=$(mktemp /tmp/ping.XXXXX)
if ping -W 1 -c 1 172.16.$i.1 &>/dev/null;then
echo "172.16.$i.1 is UP" | tee $tmpfile
else
echo "172.16.$i.1 is down" | tee $tmpfile
fi
hosttmpfiles[${#hosttmpfiles[*]}]=$tmpfile
done
在bash中使用ACSII颜色:
格式:\033[前#景颜#色;背#景颜#色mSTRING\033[0m
\033[#;#;#mSTRING\033[0m
多种控制符可组合使用,彼此间用分号隔开;
\033[31mhello\033[0m
\033[:表示控制键Ctrl;
\033[0m:表示控制结束;
31m:表示前景色;
左侧数字:(可同时设置前景、背景色)
3:表示前景色;
4:表示背景色;
右侧数字:表示颜色;
1:红色;
2:绿色;
3:金色;
4:蓝色;
5:紫色;
6:青色;
7:灰色;
例如:\033[3mhello\033[0m
#m:表示字体
1:粗体;
4:加下划线;
5:闪烁;
7:前背景反色;
8:隐藏;
例如:
]# echo -e "\033[31mhello\033[0m":前景色为红色;
]# echo -e "\033[41mhello\033[0m":背景色为红色;
]# echo -e "\033[41;32mhello\033[0m":前景为绿色,背景为红色;
]# echo -e "\033[7mhello\033[0m":前背景反色;
]# echo -e "\033[4mhello\033[0m":加下划线;
]# echo -e "\033[42;35;5mhello\033[0m":背景绿色,前景紫色,闪烁;
内置环境变量:PS1
命令行提示符格式;
可自定义命令行提示符格式:
export PS1='[\033[31m\u\033[0m@\033[32m\h\033[0m \033[35m\W\033[0m]\$'
但是设置后,有副作用;
例如:
]# echo $PS1
显示内容:[\u@\h \W]\$
dialog命令:(需要yum install安装)
display dialog boxes from shell scripts
dialog common-options box-options
box-options:
有三个参数:text、height、width
--calendar:日历框;
--dselect:选择目录框;
--editbox:编辑框;
--form:表单;
--gauge:进度条;
--infobox:信息框;
--inputbox:输入空;
--inputmenu:输入菜单;
--menu:菜单;
--msgbox:消息框;
--passwordbox:密码框,显示为*号;
]# yum info dialog:查看dialog命令是否有可用安装包;
]# yum install dialog:安装dialog程序包;
例如:
]# dialog --msgbox hell 17 30:输出消息框高17,宽30;
dialog命令可实现窗口化编程;有些是调用C库中的curses实现的;
研究:
dialog命令,各窗体控件的使用方式;
如何获取用户选择或键入的内容?dialog命令默认输出信息被定向到错误输出流;
a=$(dialog),结果应该会保存在变量中,但无法赋值给变量,需要在dialog命令应用选项--stdout;
转载于:https://blog.51cto.com/linsj/1759915