1、第10章 bash 脚本编程,本章内容要点,Shell 脚本的编制、执行和调试 Shell 脚本的成分和编码规范 Shell 变量替换扩展、数值计算、输入输出 变量分类,位置参数变量和特殊参数变量 条件测试(文件测试、字符串测试、整数测试) 分支流程控制(if、case) 循环流程控制(for、while、until、select) 函数的定义和调用、返回值 使用循环分析命令行参数,本章学习目标,熟悉Shell 脚本的编码规范,掌握执行和调试方法 掌握对变量进行整数运算和间接引用的方法 理解位置参数变量和特殊参数变量的用途 掌握条件测试的使用 、() 掌握各种流程控制语句的使用 if、case
2、 for、while、until、select、break、continue 掌握函数的定义、调用和传递返回值的方法 掌握命令行参数、位置参数的操作(shift、getopts),Shell编程基础,2018年10月13日,4,Shell脚本和Shell编程,当命令不在命令行中执行,而是从一个文件中执行时,该文件就称为 Shell 脚本。 Shell 脚本是纯文本文件。 Shell 脚本通常以 .sh 作为后缀名,但不是必须。 Shell 脚本是以行为单位的,在执行脚本的时候会分解成一行一行依次执行。 Shell 是一种功能强大的解释型编程语言 通常用于完成特定的、较复杂的系统管理任务 She
3、ll 脚本语言非常擅长处理文本类型的数据,2018年10月13日,5,Shell脚本的成分,程序元素 保留字、运算符、表达式 变量、数组、输入输出 控制结构(顺序、分支、循环、子程序调用) Shell功能 【参见第2、3章内容】 执行命令(内置命令、外部命令、自编程序) 重定向、管道、命令替换、命令聚合 通配符、注释符、 Shell环境,2018年10月13日,6,Shell 脚本的建立与执行,Shell 脚本的建立 使用文本编辑器编辑脚本文件 $ vi script-file 为脚本文件添加可执行权限 $ chmod +x script-file Shell 脚本的执行 在子Shell中执行
4、 $ bash script-file $ script-file 在当前Shell中执行 $ source script-file $ . script-file,2018年10月13日,7,PATH 环境变量的默认值不包含当前目录, 若脚本文件在当前目录,应使用 ./script-file,PATH 环境变量的默认值包含 /bin 目录, 用户可以将自己的脚本文件存放在 /bin 目录, 之后即可直接调用脚本文件名执行脚本了,Shell 脚本的编码规范,以 #! 开头:通知系统用何解释器执行此脚本 #!/bin/bash #!/bin/ksh 以注释形式说明如下的内容: # 脚本名称 #
5、脚本功能 # 作者及联系方式 # 版本更新记录 # 版权声明 # 对算法做简要说明(如果是复杂脚本),2018年10月13日,8,Shell脚本举例(1),2018年10月13日,9,#!/bin/bash # This is the first Bash shell program # Scriptname: greetings.sh echo echo -e “Hello $LOGNAME, c“ echo “its nice talking to you.“ echo -n “Your present working directory is: “ pwd # Show the name
6、 of present directory echo echo -e “The time is date +%T!. nBye“ echo,Shell脚本举例(2),2018年10月13日,10,#!/bin/bash # Script Name:/etc/cron.daily/ntpdate # 使用NTP的客户端命令ntpdate与远程NTP服务器进行同步 # 也可以用局域网内的NTP服务器替换 pool.ntp.org /usr/sbin/ntpdate -s pool.ntp.org # 更改硬件时钟时都会记录在/etc/adjtime文件中 # 使hwclock根据先前的记录来估算硬
7、件时钟的偏差, # 并用来校正目前的硬件时钟 /sbin/hwclock -adjust # 将系统时钟同步到硬件时钟 /sbin/hwclock systohc,脚本调试方法,在 bash 调用脚本时使用参数 $ bash -x -n -v scriptName 在脚本中使用 bash 内置的 set 命令使整个或部分脚本处于调试模式 开启:set -x -n -v 结束:set +x +n +v,2018年10月13日,11,脚本调试 1,2018年10月13日,12,sh x 脚本名,该选项可以使用户跟踪脚本的执行,此时 shell 对脚本中每条命令的处理过程为:先执行替换,然后显示,再
8、执行它。 shell 显示脚本中的行时,会在行首添加一个加号 “ + ”。,sh v 脚本名,在执行脚本之前,按输入的原样打印脚本中的各行。,sh n 脚本名,对脚本进行语法检查,但不执行脚本。如果存在语法错误,shell 会报错,如果没有错误,则不显示任何内容。,脚本调试举例 1,对脚本进行语法检查 $ bash -n greetings.sh 显示脚本中每个原始命令行及其执行结果 $ bash -v greetings.sh 以调试模式执行脚本 $ bash -x greetings.sh,2018年10月13日,13,脚本调试 2 set命令,在脚本内使用set命令开启调试选项 set
9、-x :显示由shell执行的命令及其参数 set -v :显示由shell读入的命令行 set -n :读取命令但不执行他们,用于语法检查 在脚本内使用set命令关闭已开启的调试选项 set +x set +v set +n,2018年10月13日,14,脚本调试举例 2,$ bash greetings.sh ./greetings.sh $ source greetings.sh,2018年10月13日,15,#!/bin/bash # This is the first Bash shell program # Scriptname: greetings.sh set -x # Tur
10、n ON debug mode # echo echo -e “Hello $LOGNAME, c“ echo “its nice talking to you.“ echo -n “Your present working directory is: “ pwd # Show the name of present directory echo set +x # Turn OFF debug mode # echo -e “The time is date +%T!. nBye“ echo,Shell脚本的类型,非交互式脚本 不需要读取用户的输入, 也不用向用户反馈某些信息 每次执行都是可预
11、见的, 因为它不读取用户输入, 参数是固定的 可以在后台执行 交互式脚本 脚本可以读取用户的输入, 实时向用户反馈信息(输出某些信息) 这样的脚本更灵活, 每次执行时的参数可由用户动态设定 用户界面更友好,但不适用于自动化任务(如cron任务),2018年10月13日,16,学习Shell编程的前提,掌握一种文本编辑器的使用(Vi) 熟悉 Linux 文件系统的布局 学习 Shell 的各种功能 重定向、管道、命令替换、命令聚合 学习各种管理和监视命令的使用 用户管理、权限管理、进程管理、包管理 系统监视、网络监视 学习各种文本文件工具的使用 cat、grep、tr、sed、awk 正则表达式
12、,2018年10月13日,17,系统的配置文件几乎都是纯文本文件,变量和表达式,2018年10月13日,18,Shell 变量操作,变量替换扩展 变量测试 变量的字符串操作 变量的间接引用 变量的数值计算 $expression $(expression) expr let declare -i,2018年10月13日,19,输入 变量赋值 name=value readonly 从标准输入读取 read 输出 echo printf,变量替换扩展变量测试,2018年10月13日,20,变量测试举例,2018年10月13日,21,color=blue newcolor=$color:-grey
13、,unset color echo “The sky is $color:-grey today“ echo $color,echo “The sky is $color:=grey today“ echo $color,echo “The sky is $color:?error today“ echo $color,echo “The sky is $color:+blue today“ echo $color,变量替换扩展 字符串计数、截取,2018年10月13日,22,m 的取值从 0 到 $#var-1,注:pattern 中可以使用通配符。,字符串变量替换扩展举例1,2018年10
14、月13日,23,str=I love linux. I love UNIX too.,echo $#str 30 echo $#str:13 I love UNIX too. echo $#str:7:5 linux,echo $str#I love linux. I love UNIX too. echo $str#I*. I love UNIX too. echo $str#I*,变量替换扩展 字符串替换,2018年10月13日,24,注: (1)old 中可以使用 通配符。 (2)var 可以是 或 *,表示对每个位置参数进行替换,字符串变量替换扩展举例2,2018年10月13日,25,
15、str=I love linux. I love UNIX too.,echo $str/love/like I like linux. I love UNIX too. echo $str/love/like I like linux. I like UNIX too. echo $str/I*linux/I like FreeBSD I like FreeBSD. I love UNIX too. echo $str/#I love/“Jaime“ Jaime linux. I love UNIX too. echo $str/I love/“Jaime“ Jaime linux. Jai
16、me UNIX too. echo $str/%too./also. I love linux. I love UNIX also.,字符串变量替换扩展举例3,2018年10月13日,26,set 1v1 1v2 1v3 1v4,echo $ 1v1 1v2 1v3 1v4 echo $/1/a av1 av2 av3 av4 echo $/1/a ava av2 av3 av4 echo $/%1/a 1va 1v2 1v3 1v4,变量的间接引用,通过 str2 的值来引用 str1 的值,2018年10月13日,27,str1=“Hello World“ str2=str1 echo $
17、str2, bash2.0以上才支持 newstr=$!str2 echo $newstr Hello World 或 echo $!str2 Hello World,eval newstr=$str2 echo $newstr Hello World 或 eval echo $str2 Hello World,?,变量的间接引用(续),2018年10月13日,28,通过 x 的值来引用 CENTOS_URL 的值,x=“CENTOS“ CENTOS_URL=“http:/ bash2.0以上才支持 newstr=$x_URL echo $newstr CENTOS_URL echo $!ne
18、wstr http:/ newstr=$x_URL echo $newstr 或 eval echo $x_URL,Shell内置命令eval,2018年10月13日,29,listpage=“ls -l | more“ eval $listpage,eval $(ssh-agent),eval newstr=$str2 eval echo $x_URL,eval arg1 arg2 . argN,对参数进行两次扫描和替换将所有的参数连接成一个表达式,并计算或执行该表达式参数中的任何变量都将被展开,Shell 变量的分类,用户自定义变量 由用户自己定义、修改和使用 Shell 环境变量 由系统
19、维护,用于设置用户的Shell工作环境 只有少数的变量用户可以修改其值 位置参数变量(Positional Parameters) 通过命令行给程序传递执行参数 可用 shift 命令实现位置参数的迁移 专用参数变量(Special Parameters) Bash 预定义的特殊变量 用户不能修改其值,2018年10月13日,30,位置参数变量,是一组特殊的内置变量 跟在脚本名后面的用空格隔开的每个字符串 $1 表示第1个参数值,$9 表示第9个参数值 $10 表示第10个参数值, $11 表示第11个参数值, 位置参数的用途 从 shell 命令/脚本 的命令行接受参数 在调用 shell
20、函数时为其传递参数,2018年10月13日,31,专用参数变量,命令行参数相关 $* 将所有位置参量看成一个字符串(以空格间隔) 。 $ 将每个位置参量看成单独的字符串(以空格间隔)。“$*” 将所有位置参量看成一个字符串(以$IFS间隔)。“$” 将每个位置参量看成单独的字符串(以空格间隔) 。 $0 命令行上输入的Shell程序名。 $# 表示命令行上参数的个数。 进程状态相关 $? 表示上一条命令执行后的返回值 $ 当前进程的进程号 $! 显示运行在后台的最后一个作业的 PID $_ 在此之前执行的命令或脚本的最后一个参数,2018年10月13日,32,位置参数和专用参数举例,执行脚本
21、$ ./vartest.sh 1 2 3 4 5 a b c d e f g,2018年10月13日,33,#!/bin/bash # ScriptName: vartest.sh # To test Positional Parameters & Special Parameters. echo “Hello,$USER,the output of this script are as follows:“ echo “The script name is : $(basename $0)“ echo “The first param of the script is : $1“ echo “
22、The second param of the script is : $2“ echo “The tenth param of the script is : $10“ echo “All the params you input are : $“ echo “All the params you input are : $*“ echo “The number of the params you input are: $#“ echo “The process ID for this script is : $“ echo “The exit status of this script i
23、s : $?”,$、$*和环境变量IFS,2018年10月13日,34,执行脚本 $ ./ifsargs.sh 1 2 3 4 5 a b c d e f g,#!/bin/bash # ScriptName: ifsargs.sh # Set the IFS to | # IFS=|echo “Command-Line Arguments Demo“ echo “* All args displayed using $ positional parameter *“ echo $ echo “* All args displayed using $* positional parameter
24、 *“ echo $*echo * All args displayed using “$“ positional parameter * echo “$“ #* double quote added *# echo * All args displayed using “$*“ positional parameter * echo “$*“ #* double quote added *#,位置参数和 shift 命令,将位置参量列表依次左移n次,缺省为左移一次 一旦位置参量列表被移动,最左端的那个参数就会从列表中删除 经常与循环结构语句一起使用,以便遍历每一个位置参数,2018年10月1
25、3日,35,shift n,#!/bin/sh # ScriptName: pp_shift.sh # To test Positional Parameters & Shift. echo “The script name is : $0“ echo $1=$1,$2=$2,$3=$3,$4=$4 - $#=“$#“ echo $: “$“ shift # 向左移动所有的位置参数1次 echo $1=$1,$2=$2,$3=$3,$4=$4 - $#=“$#“ echo $: “$“ shift 2 # 向左移动所有的位置参数2次 echo $1=$1,$2=$2,$3=$3,$4=$4 -
26、 $#=“$#“ echo $: “$“,$ ./pp_shift.sh 1 b 3 d 4 f,退出/返回状态,$?:返回上一条语句或脚本执行的状态 0:成功 1255:不成功 exit 命令 exit 命令用于退出脚本或当前Shell n 是一个从 0 到 255 的整数 0 表示成功退出,非零表示遇到某种失败 返回值 被保存在状态变量 $? 中,2018年10月13日,36,exit n,常见的返回状态码,0: 执行正确 1: 通用错误 126: 命令或脚本没有执行权限 127: 命令没找到,2018年10月13日,37,$ echo $ # 显示当前进程的 PID 9245 $ ech
27、o $? # 显示在此之前执行的命令的返回值 0 $ bash # 调用子Shell $ echo $ # 显示当前进程的 PID 9474 $ exit 1 # 指定返回值并返回父Shell $ echo $? # 显示上一个Shell/脚本的返回值 1 $ list # 执行不存在的命令 bash: list: command not found $ echo $? 127 $ touch bbb.sh $ ./bbb.sh # 执行不具有执行权限的命令 bash: ./bbb.sh: Permission denied $ echo $? 126,read,从键盘输入内容为变量赋值 re
28、ad -p “信息“ var1 var2 . 若省略变量名,则将输入的内容存入REPLY变量 结合不同的引号为变量赋值 双引号 ” ”:允许通过$符号引用其他变量值 单引号 :禁止引用其他变量值,$视为普通字符 反撇号 :将命令执行的结果输出给变量 更多read的用法参见 http:/bash.cyberciti.biz/guide/Getting_User_Input_Via_Keyboard,2018年10月13日,38,read 举例,2018年10月13日,39,#!/bin/bash # This script is to test the usage of read # Scrip
29、tname: ex4read.sh echo “= examples for testing read =“ echo -e “What is your name? c“ read name echo “Hello $name“ echo echo -n “Where do you work? “ read echo “I guess $REPLY keeps you busy!“ echo read -p “Enter your job title: “ echo “I thought you might be an $REPLY.“ echo echo “= End of the scri
30、pt =“,只读变量,举例,2018年10月13日,40,只读变量,readonly variable,是指不能被清除或重新赋值的变量。,lrjcentos1 $ myname=Osmond lrjcentos1 $ echo $myname Osmond lrjcentos1 $ readonly myname lrjcentos1 $ unset myname -bash: unset: myname: cannot unset: readonly variable lrjcentos1 $ myname=“Osmond Liang“ -bash: myname: readonly var
31、iable lrjcentos1 $,同时输出多行信息,使用 echo使用 here file,2018年10月13日,41,echo “ Line1 Line2 Line3 “,cat _END_ Line1 Line2 Line3 _END_,多行内容中不能出现双引号,否则 echo 提前结束若确实需要使用双引号,需使用转义字符: “,_END_可以是任意字符串,只要上下一致即可多行内容中不能出现内容为_END_开始的行,否则 cat 提前结束,整数运算,Bash 变量没有严格的类型定义 本质上 Bash 变量都是字符串 若一个字面常量或变量的值是纯数字的,不包含字母或其他字符, Bash
32、可以将其视为长整型值,并可做算数运算和比较运算。 Bash 也允许显式地声明整型变量 declare -i 变量名,2018年10月13日,42,算数运算符,2018年10月13日,43,注:按位运算是以二进制形式进行的。,算术运算扩展,2018年10月13日,44,$expression $(expression),num1=$4+1; echo $num1 num1=$($num1*2-3); echo $num1,注意 $,$(),$,$() 的不同作用,用 $,$() 进行整数运算时,括号内变量前的美元符号 $ 可以省略。,(num2=2+3*2-1001%5); echo $num2
33、 num2=$(2+3*2-1001%5); echo $num2 echo $(2+3*2-1001%5),Shell内置命令let,let 内置命令用于算术运算,2018年10月13日,45,num2=1; echo $num2 let num2=4+1; echo $num2 let num2=$num2+1; echo $num2,赋值符号和运算符两边不能留空格!如果将字符串赋值给一个整型变量时,则变量的值为 0如果变量的值是字符串,则进行算术运算时设为 0,let num2=4 + 1 let “num2=4 + 1“ # 用引号忽略空格的特殊含义,用 let 命令进行算术运算时,最
34、好加双引号。,expr,通用的表达式计算命令 表达式中参数与操作符必须以空格分开。 表达式中的运算可以是算术运算,比较运算,字符串运算和逻辑运算。,2018年10月13日,46,man expr,expr 5 % 3,expr 5 * 3 # 乘法符号必须被转义,expr 2 + 5 * 2 - 3 % 2,expr ( 2 + 5 ) * 2 3 # 括号必须被转义,浮点数运算,bash 只支持整数运算 可以通过使用 bc 或 awk 工具来处理浮点数运算,2018年10月13日,47,n=$(echo “scale=3; 13/2“ | bc ) echo $n,m=awk BEGINx=
35、2.45;y=3.123; printf “%.3fn“, x*y echo $m,printf 命令,2018年10月13日,48,printf 可用来按指定的格式输出变量,printf format 输出参数列表,printf “%-12.5fn“ 123.456,format 以%开头,flag,field width,precision,格式符,-:左对齐 +:输出符号 0:空白处添0 空格:前面加一空格,字段宽度,小数点后输出位数,printf 命令(续),2018年10月13日,49,printf 命令的格式说明符,format 中还可以使用,printf 命令举例,2018年10
36、月13日,50,printf “The number is: %.2fn“ 100,printf “%-20s|%12.5f|n“ “Joy“ 10,printf “%-10d%010o%+10xn“ 20 20 20,printf “%6dt%6o“%6x“n“ 20 20 20,例:,数组变量,2018年10月13日,51,Bash 2.x 以上支持一维数组,下标从 0 开始。,variable=(item1 item2 item2 . ) variablen=value,declare -a variable variable=(item1 item2 item2 . ),使用 decl
37、are 声明或直接给变量名加下标来赋值。,数组变量举例,2018年10月13日,52,declare -a stu stu=(math1101 math1102 math1103) echo $stu0 # 列出stu的第一个元素 echo $stu* # 列出stu的所有元素 echo $#stu* # 给出数组stu中元素的个数,Shell内置命令declare,2018年10月13日,53,declare 举例,2018年10月13日,54,declare r myname=osmond unset myname declare myname=“Osmond Liang“,declare
38、,变量及相关命令小结1,2018年10月13日,55,echo $variable 或 echo $variable,unset variable,set,readonly variable,export variable=value export -n variable export -p,declare 选项 variable=value,变量及相关命令小结2,2018年10月13日,56,basename dirname,let 或 expr,$var:-word、$var:=word、$var:?word、$var:+word,$0、$1-$9、$n、$#、$*、$、$、$!、$?、$
39、-,eval newstr=$str2 、newstr=$!str2,hostname 、$(hostname) basename pwd 、 $(basename $(pwd),$expression 、$(expression),变量及相关命令小结3,2018年10月13日,57,输入,read var1 var2 .,read,read p “提示“,输出,printf “%-12.5f t %d n“ 123.45 8,format 以%开头,flag,field width,precision,格式符,-:左对齐 +:输出符号 0:空白处添0 空格:前面加一空格,字段宽度,小数点后输
40、出位数,c d e f g s o x,b n r t v ” %, REPLY, REPLY,输出参数用空格隔开,条件测试,2018年10月13日,58,条件测试简介,条件测试可以判断某个特定条件是否满足 测试之后通常会根据不同的测试值选择执行不同任务 条件测试的种类 命令成功或失败 表达式为真或假 条件测试的值 Bash中没有布尔类型变量 退出状态为 0 表示命令成功或表达式为真 非0 则表示命令失败或表达式为假 状态变量 $? 中保存了退出状态的值,2018年10月13日,59,条件测试举例(1),2018年10月13日,60,$ User=osmond $ grep $User /et
41、c/passwd $ echo $? $ grep $User /etc/passwd /dev/null & echo “$User is a user in /etc/passwd.” | echo “$User isnt a user in /etc/passwd.”,$ Host=centos1 $ ping c 1 $Host $ echo $? $ ping c 1 $Host /dev/null & echo “$Host is up.” | echo “$Host is down.”,条件测试语句,语句 格式1: test 格式2: 格式3: (bash 2.x 版本以上) 说
42、明 格式1 和 格式2 是等价的,格式3是扩展的 test 命令 在 中可以使用通配符进行模式匹配 &, |, 能够正常存在于 中,但不能在 中出现 和之后的字符必须为空格,和之前的字符必须为空格 要对整数进行关系运算也可以使用 () 进行测试,2018年10月13日,61,条件测试操作符,条件测试表达式中可用的操作符 文件测试操作符 字符串测试操作符 整数二元比较操作符 使用逻辑运算符,2018年10月13日,62,文件测试,2018年10月13日,63,测试:文件是否存在,文件属性,访问权限等。,更多文件测试符参见 test 的在线帮助,man test,使用lftp同步yum仓库,201
43、8年10月13日,64,#!/bin/bash # Script Name: sync_iredmail_yum_repo.sh DIST=5 ARCH=“i386“ EXCL_ARCH=“x86_64“ SRC=http:/iredmail.org/yum/rpms/$DIST/ DST=/var/ftp/yum/repos/iredmail/$DIST/$ARCH/ ! -e $DST & mkdir -p $DST excludes=“($EXCL_ARCH)|(repodata)“cd $DST lftp -e “set mirror:exclude-regex $excludes &
44、 mirror -delete -only-newer -verbose & exit“ $SRC createrepo . /dev/null,使用reposync同步仓库,2018年10月13日,65,#!/bin/bash # Script Name: sync_atomic-repo_with_reposync.sh ARCH=“i386“ url=“http:/ release=“atomic-release-1.0-13.el5.art.noarch.rpm“ rpm -U $url/$ARCH/RPMS/$release rpm -import /etc/pki/rpm-gpg/
45、RPM-GPG-KEY.art.txt,使用 reposync 同步仓库之前首先要配置仓库 安装 atomic-release 的RPM包 导入其RPM公钥 对 yum 和 reposync 使用不同的配置文件 yum 使用本地仓库配置以加快更新速度 /etc/yum.conf 和 /etc/yum.repos.d/atomic.repo reposync 使用运程仓库配置 /etc/reposync.conf 和 /etc/yum/repos.d/atomic.repo,使用reposync同步仓库续1,2018年10月13日,66, ! -f /etc/reposync.conf cat
46、/etc/yum.repos.d/atomic.repo atomic name = CentOS / Red Hat Enterprise Linux $releasever - baseurl = file:/var/ftp/yum/repos/atomic/centos/5/$ARCH/atomic enabled = 1 priority = 1 protect = 0 gpgkey = file:/etc/pki/rpm-gpg/RPM-GPG-KEY.art.txt gpgcheck = 1 _END_ ),配置对 yum 和 reposync 使用不同的配置文件 仅当 /etc
47、/reposync.conf 不存在时执行一次 (),使用reposync同步仓库续2,2018年10月13日,67,DST=“/var/ftp/yum/repos/atomic/centos/5/$ARCH“ ! -e $DST & mkdir -p $DST | cd $DST /usr/bin/yum clean all /usr/bin/reposync -arch=$ARCH -repoid=atomic -d -c /etc/reposync.conf cd atomic /usr/bin/createrepo . /usr/bin/yum clean all,若本地同步的目标目录不存在创建之,否则进入之 使用 reposync 同步仓库的RPM文件 -a, -arch: 指定架构 (i386、x86_64) -r, -repoid: 指定要同步的仓库名 -d, -delete: 删除本地存在而远程已经不存在的文件 -c, -config: 指定 reposync 使用的配置文件 使用 createrepo 创建仓库(repodata),