写在前面:
博客书写牢记5W1H法则:What,Why,When,Where,Who,How。
本篇主要内容:
● bash脚本格式
● 知识回顾
变量
数据类型
算数运算
赋值
随机数
变量类型定义
数值
只读
一维数组
● 条件测试
[ ]与[[ ]]
数值测试
字符串测试
文件测试
● 脚本执行结果返回值
● 位置参数变量
● 特殊变量
● 选择执行
if语句
● 用户交互、错误排查
read
bash -n
bash -x
bash脚本格式:
(1)第一行,顶格,定义解释器#!/bin/bash
(2)以#开头的行为注释信息,写明脚本作用版本历史版本等信息
(3)代码区块前段应该以#注明代码作用,方便后期阅读修改
(4)注意缩进,适度添加空白行
(5)bash中尽量定义PATH和locale变量,以保证命令引用和输出正确。即定义PATH= LANG=
知识回顾:
变量:
局部变量:定义在脚本方法中,也只在其中有效。(鸟哥的书中本地变量和局部变量是同一个含义,都是自定义的变量)
本地变量:只在当前shell有效,不传递给子shell。直接定义的自定义变量。
环境变量:在当前shell有效,并会传递给子shell。用export定义或修改为环境变量。
位置参数变量:在shell中可通过$#引用的参数字符
特殊变量:shell预先定义的有一定含义的字符。如$?返回上一条命令的执行结果返回值;$#返回参数个数等。
数据类型:
字符型、数值型。shell是弱类型编程语言,默认会将所有变量都当做字符型。
算数运算:
+,-,*,/,%,**
let VAR=EXPRESSION
VAR=$[ EXPRESSION ]
VAR=$(( EXPRESSION ))
VAR=$( expr ARGU1 运算符 ARGU2 ... )
注意:有些时候乘法符号需要转义,如最后一种算数运算式。
增强型赋值:
变量进行算数运算后,又将结果回存至变量中:
let I=$I+#与下面等同
let I+=#
自增:
VAR=$[$VAR+1]
let VAR+=1
let VAR++
自减:
VAR=$[$VAR-1]
let VAR-=1
let VAR--
随机数:
$RANDOM会返回0-32767之间的整数。
利用$RANDOM取一定数值内的随机数。
如取0-99之间随机数:$RANDOM*99/32767即可啦。
变量类型定义:
declare [-aixr] VAR
数值:
declare -i SUM=100+200;echo $SUM 定义SUM为数值类型
declare -x VAR 将VAR变为环境变量,等同于export;恢复用 +x
只读:
declare -r VAR 将VAR定义为只读,同readonly
一维数组(array):
数组定义:
(1)declare -a ARR=(VALUE0 VALUE1 VALUE2 ...) :使用declare定义并赋值数组
(2)declare -a ARR=($( COMMAND )) :使用命令引用为数组赋值
(3)declare -a ARR=([0]=VALUE0 [2]=VALUE2 ...) :单独为每个数组单元赋值
(4)read -a ARR :使用read命令读取用户输入的数组,空格间隔数组中单元
数组引用:
echo ${ARR[@]} :引用所有数组单元
echo ${ARR[#]} :#为数字,引用某一数组单元
echo ${#ARR[@]} :#就是#,引用数组长度
echo ${array[@]:n:m} :引用数组第n-m个单元
echo ${!ARR[@]} :引用数组的所有下标
实例:
#获取/etc/passwd文件总第18行和第15行用户的UID,并计算其UID的和。
[root@localhost shell]# cat userIDsum.sh #!/bin/bash # read /etc/passwd file,then get 15th and 18th users UID,get SUM. # by fred # v 0.0.1 # 2016.3.14 # get UID for line 18 and line 15 UID_18=`cut -d: -f3 /etc/passwd | head -n 18 | tail -n1` UID_15=`cut -d: -f3 /etc/passwd | head -n 15 | tail -n1` # get SUM SUM=$[ $UID_15 + $UID_18 ] # print SUM echo $SUM
#计算/etc/rc.d/init.d/functions和/etc/inittab文件的空白行数之和
[root@localhost shell]# cat spaceline_SUM.sh #!/bin/bash # get spacelines SUM of file /etc/rc.d/init.d/functions and file /etc/rc.d/init.d/network # by fred # v 0.0.1 # 2016.3.14 # set filepath FILE1='/etc/rc.d/init.d/functions' FILE2='/etc/rc.d/init.d/network' # get spacelines of files FILE1_SPACELINE=`grep "^[[:space:]]*$" $FILE1 | wc -l` FILE2_SPACELINE=`grep "^[[:space:]]*$" $FILE2 | wc -l` # get sum SUM=$[ $FILE1_SPACELINE + $FILE2_SPACELINE ] echo "$SUM"
条件测试:
判断某需求是否满足,并有选择的执行其他代码内容。
(1)查看命令结果返回值:$?
0:成功
1-255:失败
(2)测试表达式
test EXPRESSION 或 [ EXPRESSION ]
[[ EXPRESSION ]]
注意:两端空格不能省略
[ ]与[[ ]]
参阅"help ["命令,可以得到以下内容:
"["是内嵌命令test的同义词,必须以字符"]"结尾,以匹配开始的"["。
"[["会根据EXPRESSION的估值返回状态0或1。EXPRESSION按照"test"的相同条件组成,也可以使用下列操作符连接:
( EXPRESSION ):返回EXPRESSION的值
! EXPRESSION:去反
EXPR1 && EXPR2:与
EXPR1 || EXPR2:或
总结:主要有一下不同点:
类别 | [ ] | [[ ]] |
获取帮助 | man test | man bash 1700行左右 |
A=~B,A包含B | 不支持 | 支持=~ |
&&、|| 表示与或 | 不支持 | 支持 |
-a -o 表示与或 | 支持 | 不支持 |
-N,文件在最后读之后被修改modified | 不支持 | 支持 |
其他,请自行 man |
数值测试:数值比较
-eq:等于
-ne:不等
-gt:大于
-lt:小于
-ge:大于等于
-le:小于等于
补充:man 1 test可以看到相关内容
字符串测试:
-v VARNAME:变量已定义则为真
-z string:字符串长度为0
string或-n string:字符串长度非0
string1 == string2或string1 = string2:=在test和[ ]语句中使用。
string1 != string2
string1 < string2:如果 string1 在当前语言环境的字典顺序中排在 string2 之前则为真。
string1 =~ string2:string1中包含string2则为真
注意:字符串测试选择使用[[ ]]或[],如“=~”的测试就只能在[[ ]]中正确执行;字符串最好在“”内,防止字符串为空时导致的错误。字符串测试符号与字符串之间的空格必须保留。
文件测试:
存在与否:
-a:文件存在
-e:文件存在
类型:
-b:块设备
-c:字符设备
-d:目录
-f:普通文件
-h或-L:软链接
-p:命名管道
-S:套接字
权限:
-r:可读
-w:可写
-x:可执行
特殊权限:
-u:特殊权限SUID已设置
-g:特殊权限SGID已设置
-k:特殊权限sticky已设置
文件内容:
-s:文件大小非0
从属:
-G:已设置可用属组
-O:已设置可用属主
时间戳:
-N:最后读取后有内容修改
双目测试:
file1 -ef file2:两文件指向同一文件系统的相同inode,即为同一文件的硬链接
file1 -nt file2:fiel1比file2新,特指mtime。或file1存在,file2不存在
file1 -ot file2:file1比file2老,特指atime。或file1不存在,file2存在
组合测试:
逻辑运算:
(1)&& || !
(2)-a -o !
实例:
#但当前主机名为空或为localhost.localdomin时,将其设置为www.fredme.com
[root@magedu ~]# cat hostname.sh #!/bin/bash # if hostname is null or "localhost.localdomin",then set hostname "www.fredme.com" # by fred # 0.0.1 # 2016.3.15 # get hostname hostName=`hostname` # check if hostName is null or "localhost.localdomin" ,then set it [ -z "$hostName" -o "$hostName" == "localhost.localdomin" ] && hostname "www.fredme.com" && echo "hostname is chaged to " && hostname || echo "hostname is not null or \"localhost.localdomin\"" [root@magedu ~]# hostname localhost.localdomin [root@magedu ~]# ./hostname.sh hostname is chaged to www.fredme.com [root@magedu ~]# hostname www.fredme.com [root@magedu ~]# ./hostname.sh hostname is not null or "localhost.localdomin"
脚本的执行结果状态返回值:
如果未使用exit指定脚本的状态返回值,那脚本的状态返回值就是脚本中最后一条命令的执行结果返回值。
如果使用exit [n]指定(n为0-255之间),则脚本会立即终止退出,并将n作为脚本执行结果返回值。
注意:将exit放入()内,则只退出当前语句。
如下:
[root@magedu shell]# cat exittest1.sh #!/bin/bash [ -z $1 ] && echo "NULL" && exit 66 echo $? echo "continue" [root@magedu shell]# ./exittest1.sh ;echo $? NULL 66
# ↑↑ 正确执行,并返回状态值
[root@magedu shell]# cat exittest2.sh #!/bin/bash [ -z $1 ] && ( echo "NULL" && exit 66 ) && echo "line1 continue" echo $? echo "continue" [root@magedu shell]# ./exittest2.sh NULL 66 continue
# ↑↑ 逻辑判断语句后面的语句不再执行,而下一行则继续
[root@magedu shell]# cat e.sh #!/bin/bash ( echo "nihao" exit 0 echo "hello" ) echo "ouhayi" [root@magedu shell]# ./e.sh nihao ouhayi
# ↑↑ 这个例子同上,exit语句只是跳过了exit语句后()内的语句,后面的照常执行。
位置参数变量:
在运行脚本时,在其后跟上参数,即可在脚本内以$1 ...来调用。
SCRIPTS.sh ARG1 ARG2 ...
轮转:
shift [n]:n为数字,将第#个位置参数变成第#-n个,小于1的参数被丢弃。
特殊变量:
$0:本脚本文件路径,文件名
$#:脚本参数个数
$@:所有参数
$*:所有参数
实例:
#用位置参数变量为脚本传递2个文本文件,并计算空白行之和。
[root@magedu tmp]# cat spaceline_SUM.sh #!/bin/bash # users give 2 files OPTION,then show the spaceline sum. # exit 2: ARGS err # exit 3: File not found or not readable # check 2 ARGS is given [ $# -gt 2 ] && echo "2 ARGS must be given.no MORE or less" && exit 2 [ $# -lt 2 ] && echo "2 ARGS must be given.no more or LESS" && exit 2 # get ARGS FILE1=$1 FILE2=$2 # check files exsit and regular file ,then get spacelines number [ -f $FILE1 -a -r $FILE1 ] && NUM1=`grep '^[[:space:]]*$' $FILE1 | wc -l` || echo "\"$FILE1\" not found or not readable" [ -f $FILE2 -a -r $FILE2 ] && NUM2=`grep '^[[:space:]]*$' $FILE2 | wc -l` || echo "\"$FILE2\" not found or not readable" # if NUM1 and NUM2 is set,show SUM,else exit [ -n "$NUM1" -a -n "$NUM2" ] && SUM=$[ $NUM1 + $NUM2 ] && echo "There is $SUM spacelines in $FILE1 and $FILE2" || exit 3 [root@magedu tmp]# mknod device b 8 0 [root@magedu tmp]# cp /etc/fstab file1 [root@magedu tmp]# cp /etc/init.d/functions file2 [root@magedu tmp]# ls -l file1 file2 device brw-r--r--. 1 root root 8, 0 Mar 15 12:04 device -rw-r--r--. 1 root root 595 Mar 15 12:05 file1 -rw-r--r--. 1 root root 13948 Mar 15 12:05 file2 [root@magedu tmp]# ./spaceline_SUM.sh file1000 2 ARGS must be given.no more or LESS [root@magedu tmp]# ./spaceline_SUM.sh file1 device "device" not found or not readable [root@magedu tmp]# ./spaceline_SUM.sh file1 file2 There is 71 spacelines in file1 and file2
选择执行语句:
顺序执行:逐条执行
选择执行:
一个或多个分支,满足条件则执行
单分支if语句;
if 测试条件;then
代码分支
fi
双分支if语句;
if 测试条件;then
代码分支
else
代码分支
fi
多分支if语句:
if 测试条件;then
代码分支
elif 测试条件;then
代码分支
...
else
代码分支
fi
循环执行:代码片段(循环体)执行0次或多次
后篇介绍
实例:
#参数传递用户名,若用户不存在,则添加。
[root@magedu tmp]# cat useradd.sh #!/bin/bash # get USERNAME form ARGS, if USERNAME not exist ,add it # exit 2: ARG err # # check ARG numbers [ $# -gt 1 ] && echo "You can ONLY give 1 ARG" && exit 2 [ $# -lt 1 ] && echo "1 ARG NUST given" && exit 2 # get USERNAME USERNAME=$1 # check if USERNAME not exist ,if not,add it. if id $USERNAME &> /dev/null ;then echo "\"$USERNAME\" is already exist." else useradd $USERNAME echo "$USERNAME added successful." fi [root@magedu tmp]# ./useradd.sh 1 ARG NUST given [root@magedu tmp]# ./useradd.sh fred "fred" is already exist. [root@magedu tmp]# ./useradd.sh fredme fredme added successful. [root@magedu tmp]# id fredme uid=1006(fredme) gid=1006(fredme) groups=1006(fredme)
用户交互,错误排查:
read
Read a line from the standard input and split it into fields.
read [option]... [name ...]
-p prompt:输出提示信息
-t timeout:设置超时时间
-i text:用户未输入,将变量赋值为text
bash -n SCRIPTS:检测脚本中的语法错误
bash -x SCRIPTS:调试执行脚本
实例:
#编写脚本实现以下功能:
./man.sh [page] COMMAND
执行脚本,后跟一个参数,显示此参数的man手册
后跟2个参数,直接显示对应章节,章节无效提示用户。
如果有多个man手册章节,则提示用户输入章节名,回车后显示对应章节
若只有一个man手册章节,则直接显示此章节
[root@www tmp]# cat man.sh #!/bin/bash # 显示man手册,格式为 man.sh [page] COMMAND,主要功能如下 # 若只给定COMMAND,查询COMMAND的章节存在,则显示章节并提示用户输入选择,不存在则提示并退出; # 若给定page和comman,判断用户给定的page页是否存在,存在,则直接显示,不存在提示退出。 # 若未给定任何参数,提示退出。 # by fred PATH="/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin" # 检查参数个数,1个参数直接赋值给KEY,即当做命令关键字KEY;2个参数分别赋值给章节数PAGE,和命令关键字KEY,其他则直接提示退出脚本 if [ $# -eq 2 ];then KEY=$2 PAGE=$1 elif [ $# -eq 1 ];then KEY=$1 else echo "Usage: $0 [PAGE] COMMAND" exit 1 fi # 获取命令man 章节号码,并写入到LIST属组中。注意whatis的输出中会存在非关键字的条目,相关条目也会输出,如what is passwd会输出sslpasswd的man章节,用grep过滤下! declare -a LIST=(`whatis -w -r "^$KEY\>" 2> /dev/null | grep "^$KEY\>" | awk '{print $2}' | tr -d '()'`) # 获取man章节条数,并赋值给NUM变量 NUM=${#LIST[@]} # 查找命令关键字章节条数,若为0,则直接提示退出脚本 if [ $NUM -eq 0 ];then echo "There is no Manual Page about \"$KEY\"." exit 16 fi # 运行到这,证明命令章节至少有1条。下面判断用户是否给出章节page,以及page是否有效 # 若用户给定page,则判断给定的page是否存在,使用YES变量标记。若存在,则继续执行;不存在,则提示并退出脚本 YES=0 if [ $YES -eq 0 -a -n "$PAGE" ];then for P in ${LIST[@]};do [ "$P" == "$PAGE" ] && YES=1 && break done if [ $YES -ne 1 ];then echo "No manual entry for \"$KEY\" in section \"$PAGE\"." exit 2 fi fi # 运行到此处有2种情况,1.用户给定page,并且page章节存在;2.用户未给定page。 # 1.用户已给定page,并且给定的page章节存在 if [ $YES -eq 1 -a -n "$PAGE" ];then man $PAGE $KEY # 针对用户未给定page,输出章节列表并提示用户选择 else echo "Manual pages found: " whatis -w $KEY | grep "^$KEY\>" read -i "${LIST[1]}" -t 5 -p "Please input page number to open: [Enter: default; n/N quit] " PAGE [ "$PAGE" == "N" -o "$PAGE" == "n" ] && exit 0 echo "man $PAGE $KEY" man $PAGE $KEY fi