bash脚本编程(一)

写在前面:

    博客书写牢记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


你可能感兴趣的:(随机数,shell,数组)