bash脚本编程总结
shell简介
GNU/Linux shell 是一个特殊的交互式实用程序。它为用户提供了一种启动程序、管理文件系统中的文件和管理运行在Linux系统上的进程的方式。shell的核心是命令提示符。命令提示符是shell用于交互的组成部分。它允许输入文件命令,解释命令,然后在内核中执行命令。
查看当前系统中有哪些shell:
# cat /etc/shells /bin/sh /bin/bash /sbin/nologin /bin/dash /bin/tcsh /bin/csh /bin/zsh
shell脚本:
shell script 程序化脚本,针对shell所写的脚本,将许多指令汇合在一起,去处理复杂的动作。
编写规范:
第一行必须顶格,
#!/bin/bash ## 用来声明此脚本所使用的shell语法
其他程序 #!/usr/bin/python
除每一行的#用来声明使用的shell,其它的以#开头的行均为注释,会被解释器忽略;
shell脚本应该的基本注释信息
1、scripts的功能
2、scripts的版本信息
3、scripts的作者与联络方式
4、scripts的版权宣告方式
5、scripts内的特殊指令,使用绝对路径来调用
6、scripts运作时需要的环境变量预先宣告预设定
7、scripts应该包含详细的注释信息,以便于查阅
shell文件的执行及排错
执行shell文件:
1、相对路径执行脚本,先给予shell 脚本执行权限,然后切换到脚本所在的目录下,直接执行脚本
# cd /home/scripts/ ;chmod +x test.sh ; ./test.sh
2、绝对路径执行
# chmod +x /home/scripts/test.sh; /home/scripts/test.sh
3、以bash程序来执行,使用bash或sh来执行脚本,此时执行脚本不需要提前给予权限。
# sh test.sh ; bash test.sh
脚本执行毁在当前shell下开启一个子shell,然后在子shell中执行该脚本,当脚本执行完之后再关闭相应的子shell
shell文件的基本排错:
1、使用vim 编辑脚本的时候,如果有基本的语法错误会用特殊颜色显示。
2、bash -n scriptsname.sh :不执行脚本,只返回语法错误信息
3、bash -x scriptsname.sh :追踪执行过程 如果有错误就不在往下执行,直接打印出错误。
过程式编程语言:
顺序执行:从上往下依次执行语句
选择执行:测试条件,可能会多个测试条件,某条件满足时,则执行对应的分支
循环执行:将同一段代码反复执行多次;因此,循环必须有退出条件;否则,则陷入死循环;
变量类别:
本地变量
环境变量
export:导出
局部变量
位置变量
$1, ..., $n, ${10}
位置变量偏移:shift[n]
特殊变量:
$?:状态返回值
$#: 传递给脚本参数的个数
$*:引用传递给脚本的所有参数
$@:引用传递给脚本的所有参数
$0: 命令本身
自定义shell进程的状态返回值: exit [n]
bash编程之交互编程 read
# read -p "Plz input a username: " userName
显示菜单:
cat << EOF -------menu------------ cpu) show cpu infomation mem) show memory infomation *) quit -------menu------------ EOF
bash的条件判断式语句:
单分支的if语句:
if 测试条件; then
选择分支
fi
表示条件测试状态返回值为值,则执行选择分支;
测试条件:在bash中是命令 (test EXPR, [ EXPR ] ) 或由 [[ EXPR ]]
在bash运行至if时,其后的命令会被执行,其状态结果则作为判断标准:
0:表示真
1-255:表示假
如果条件包含比较之意,则必须使用
示例:
输入一个用户名,如果用户不在在,则创建之。 #/bin/bash read -p "Plz input a username: " userName if ! id $username &> /dev/null; then useradd $username fi
条件测试:
命令执行成功与否即为条件测试
test EXPR
[ EXPR ]
[[ EXPR ]]
测试类型:根据比较时的操作数的类型
整型测试:整数比较
字符测试:字符串比较
文件测试:判断文件的存在性及属性等
注意:比较运算通常只在同一种类型间进行
整型测试:
-gt: 大于,例如 [$num1 -gt $num2 ] -lt: 小于 -ge: 大于等于 -le: 小于等于 -eq: 等于 -ne: 不等于
字符串测试:
双目:
>: [["$str1" > "$str2" ]]
<:
>=
<=
==
!=
单目:
-n String: 是否不空,不空则为真,空则为假
-z String: 是否为空,空则为真,不空则假
文件测试:
-a FILE: 文件是否存在。 -e FILE: 存在则为真;否则则为假; -f FILE: 存在并且为普通文件,则为真;否则为假; -d FILE: 存在并且为目录文件,则为真;否则为假; -L/-h FILE: 存在并且为符号链接文件,则为真;否则为假; -b: 块设备 -c: 字符设备 -S: 套接字文件 -p: 命名管道 -s FILE: 存在并且为非空文件则为值,否则为假; -r FILE:文件是否具有可读权限 -w FILE:文件是否有写权限 -x FILE:文件是否有可执行权限
字串测试中的模式匹配
[[ "$var" =~ PATTERN ]]
双分支的if语句:
两个分支仅执行其中之一。
注意:如果把命令执行成功与否当作条件,则if语句后必须只跟命令本身,而不能引用。
语法格式:
if 测试条件; then 选择分支1 else 选择分支2 fi
示例:
通过命令行给一个文件路径,如果此文件中存在空白行,则显示空白行的总数;否则,显示无空白行;
bash scripts.sh /etc/issue #/bin/bash if grep "^[[:space]]*$" $1 &> /dev/null; then echo "$1 has $(grep "^[[:space]]*$" $1 | wc -l) blank lines." else echo "No blank lines" fi
多分支的if语句:
多个分支只执行满足某一个条件的语句
语法格式:
if 条件1; then 分支1 elif 条件2; then 分支2 elif 条件3; then 分支3 ... else 分支n fi
示例:
传递一个用户名给脚本:1、如果此用户的id号为0,则显示说这是管理员; 2、如果此用户的id号大于等于500,则显示说这是普通用户;3、否则,则说这是系统用户; #!/bin/bash # if [ $# -lt 1 ]; then echo "Usage: `basename $0` username" exit 1 fi if ! id -u $1 &> /dev/null; then echo "Usage: `basename $0` username" echo "No this user $1." exit 2 fi if [ $(id -u $1) -eq 0 ]; then echo "Admin" elif [ $(id -u $1) -ge 500 ]; then echo "Common user." else echo "System user." fi
case语句:
有多个测试条件时,case语句会使得语法结构更明晰
语法格式:
case 变量引用 in PATTERN1) 分支1 ;; PATTERN2) 分支2 ;; ... *) 分支n ;; esac
PATTERN:类同于文件名通配机制,但支持使用|表示或者;
a|b: a或者b
*:匹配任意长度的任意字符
?: 匹配任意单个字符
[]: 指定范围内的任意单个字符
示例
写一个脚本,对/etc/目录及内部的所有文件打包压缩 1、显示一个菜单,让用选择使用的压缩工具: xz) xz compress tool gz) gzip compress tool bz2) bzip2 compress tool 2、根据用户选择的工具,对/etc执行相应的操作并保存至/backups目录,文件形如/backups/etc-日期时间.tar.压缩后缀 #!/bin/bash # [ -d /backups ] || mkdir /backups cat << EOF xz) xz compress tool gz) gzip compress tool bz2) bzip2 compress tool EOF read -p "Plz choose an option: " choice case $choice in xz) tar -Jcf /backups/etc-`date +%F-%H-%M-%S`.tar.xz /etc/* ;; gz) tar -zcf /backups/etc-`date +%F-%H-%M-%S`.tar.gz /etc/* ;; bz2) tar -jcf /backups/etc-`date +%F-%H-%M-%S`.tar.bz2 /etc/* ;; *) echo "wrong option." exit 1 ;; esac
bash的循环语句:
for 循环语句
遍历有限的元素列表
格式一:
for变量名 in LIST;do 循环体 done
格式二、
for ((初始条件;测试条件;修改表达式)); do 循环体 done
# LIST:列表,中间包括一个或多个元素
# 退出条件:遍历列表结束
# 循环体:依赖于调用变量来实现其变化;
生成列表的方式:
1、手动给个列表:
for i in 1 2 3 4 5;
2、数值列表:
{start..end}
`seq [start [increment]] end` #命令引用
3、$*, $@
4、命令生成列表
示例:
求100以内所有正整数之和:(for,while,until实现对比) ============================= for格式一实现 #!/bin/bash declare -i sum=0 for i in {1..100}; do sum=$[$sum+$i] done echo $sum ---------------------------------------------- for格式二实现 declare -i sum=0 for ((counter=1;$counter<= 100; counter++)); do let sum+=$counter done echo $sum =================================== until循环实现 #!/bin/bash # declare -i count=1 declare -i sum=0 until [ $count -gt 100 ]; do let sum+=$count let count++ done echo $sum ==================================== while循环实现 #!/bin/bash # declare -i count=1 declare -i sum=0 while [ $count -le 100 ]; do let sum+=$count let count++ done echo $sum
while循环:
while适用于循环次数未知,或不便用for直接生成较大的列表时;
语法格式:
while 测试条件; do 循环体 done
# 测试条件为真,进入循环;测试条件为假,退出循环;
# 测试条件一般通过变量来描述,需要在循环体不变量地改变变量的值,以确保某一时刻测试条件为假,进而结束循环;
示例:
提示用户输入一个用户名,如果用户存在,就显示用户的ID号和shell;否则显示用户不存在; 显示完成之后不退出,再次重复前面的操作,直到用户输入q或quit为止; read -p "Plz enter a username: " userName while [ "$userName" != 'q' -a "$userName" != 'quit' ]; do if id $userName &> /dev/null; then grep "^$userName\>" /etc/passwd | cut -d: -f3,7 else echo "No such user." fi read -p "Plz enter a username again: " userName done
while循环:遍历文本文件
语法格式:
while read 变量名; do 循环体 done < /path/to/somefile
注意:
变量名,每循环一次,记忆了文件中一行文本
同时也可以使用管道
示例:
显示其ID号为偶数的用户的用户名、ID号和SHELL #!/bin/bash while read line; do userID=`echo $line | cut -d: -f3` if [ $[$userID%2] -eq 0 ];then echo $line | cut -d: -f1,3,7 fi done < /etc/passwd
until循环
未知次数的循环,由测试条件决定是否退出循环;
until语句和while语句正好相反,while语句判断条件为真时,进行循环操作;
而until则是判断条件为假时,进行循环操作
语法格式:
until 测试条件; do 循环体 done
# 如果测试结果为“假”,则进入循环;退出条件为,测试条件为真;
# 测试条件一般通过变量来描述,需要在循环体不变量地改变变量的值,以确保某一时刻测试条件为真,进而结束循环;
示例:
查看用户登录 #!/bin/bash read -p "Plz enter a username: " userName until who | grep "\<$userName\>" &> /dev/null; do sleep 5 echo "not here" done echo "here" ============================================================ while 循环实现 #!/bin/bash # read -p "Plz enter a username: " userName while true; do if who | grep "\<$userName\>" &> /dev/null; then break fi echo "not here." sleep 5 done echo "$userName is logged on."
循环控制:
continue: 提前进入下一轮循环
用于条件语句中,仅在某些个特殊场景提前进入;
break [n]:跳出当前循环
用于条件语句中,
bash的函数:
如果你要编写大型的Shell脚本程序,那么模块化必须的,定义函数来模块化脚本程序是个不错的选择。
# 可调用:使用函数名,函数定义必须在所有调用之前
# 函数名出现的地方,会被自动替换为函数;
语法格式一:
function 函数名 {
函数体
}
语法格式二:
函数名( ) {
函数体
}
函数的返回值:
函数的执行结果返回值:代码的输出
函数中的打印语句:echo,print
函数中调用的系统命令执行后返回的结果
执行状态返回值:
函数体中最后一次执行的命令状态结果(查看:echo$?)
自定函数执行状态的返回值:return #
函数可以接受参数:
在函数体中调用函数参数的方式同脚本中调用脚本参数的方式:位置参数
$1, $2, ...
$#, $*, $@
示例:
编写服务脚本script.sh {start|stop|restart|status}
1) start: 创建/var/lock/subsys/script.sh
2) stop: 删除此文件
3) restart: 先删除文件,再创建文件
4) status: 如文件存在,显示running,否则,显示stopped
#!/bin/bash # # chkconfig: 2345 67 34 # srvName=$(basename $0) lockFile=/var/lock/subsys/$srvName start() { if [ -f $lockFile ];then echo "$srvName is already running." return 1 else touch $lockFile [ $? -eq 0 ] && echo "Starting $srvName OK." return 0 fi } stop() { if [ -f $lockFile ];then rm -f $lockFile &> /dev/null [ $? -eq 0 ] && echo "Stop $srvName OK" && return 0 else echo "$srvName is not started." return 1 fi } status() { if [ -f $lockFile ]; then echo "$srvName is running." else echo "$srvName is stopped." fi return 0 } usage() { echo "Usage: $srvName {start|stop|restart|status}" return 0 } ## 程序主体 case $1 in start) start ;; stop) stop ;; restart) stop start ;; status) status ;; *) usage exit 1 ;; esac
bash的数组:
是一种数据结构,有相关的数据项组成,每个数据项,称为数组的元素,且可采用索引的方式取得个元素的值。
数组:
数组名+索引
数组元素
索引的表示方式:
数字索引:a[index]
a[0], a[1]
bash 4.0的关联数组
a[hello], a[hi]
declare -a
declare -A
支持稀疏格式:
仅一维数组
数组的赋值:
一次对一个元素赋值:单个赋值,以index作为索引号,索引号从0开始
a[0]=$RANDOM a[1]=345
一次对全部元素赋值:使用小括号为数组赋值默认以空格分隔
a=(red blue yellow green)
按索引进行赋值:
a=([0]=green [3]=red [2]=blue [6]=yellow)
命令替换:
用户输入:
read -a ARRAY
数组的访问:
用索引访问:
ARRAY[index]
数组的长度:
${#ARRAY[*]}
${#ARRAY[@]}
从数组中挑选某元素:
${ARRAY[@]:offset:number}
切片:
offset: 偏移的元素个数
number: 取出的元素的个数
${ARRAY[@]:offset}:取出偏移量后的所有元素
${ARRAY[@]}: 取出所有元素
数组复制:
要使用${ARRAY[@]}
$@: 每个参数是一个独立的串
$*: 所有参数是一个串
示例:复制一个数组中下标为偶数的元素至一个新数组中
#!/bin/bash declare -a mylogs logs=(/var/log/*.log) echo ${logs[@]} for i in `seq 0 ${#logs[@]}`; do if [ $[$i%2] -eq0 ];then index=${#mylogs[@]} mylogs[$index]=${logs[$i]} fi done echo ${mylogs[@]}
生成10个随机数,升序排序 #!/bin/bash for((i=0;i<10;i++));do rnd[$i]=$RANDOM done echo -e "total=${#rnd[@]}\n${rnd[@]}\nBegin to sort" for((i=9;i>=1;i--));do for((j=0;j<i;j++));do if [ ${rnd[$j]} -gt ${rnd[$[$j+1]]} ] ;then swapValue=${rnd[$j]} rnd[$j]=${rnd[$[$j+1]]} rnd[$[$j+1]]=$swapValue fi done done echo ${rnd[@]}