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;