shell脚本编程
本章内容
编程基础 脚本基本格式 变量 运算 条件测试 流程控制 函数 数组 高级字符串操作 高级变量 配置用户环境
编程基础
程序:指令+数据 编程程序风格: 过程式:以指令为中心,数据服务于指令 对象式:以数据为中心,指令服务于数据 shell程序:提供了编程能力,解释执行
程序的执行方式
计算机:运行二进制指令; 编程语言: 低级:汇编 高级: 编译:高级语言-->编译器-->目标代码 java,C# 解释:高级语言-->解释器-->机器代码 shell, perl, python
编程基本概念
编程逻辑处理方式: 顺序执行 循环执行 选择执行 shell编程:过程式、解释执行 编程语言的基本结构: 数据存储:变量、数组 表达式: a + b 语句:if
shell脚本基础
shell脚本是包含一些命令或声明,并符合一定格式的文 本文件 格式要求:首行shebang机制 #!/bin/bash #!/usr/bin/python #!/usr/bin/perl shell脚本的用途有: 自动化常用命令 执行系统管理和故障排除 创建简单的应用程序 处理文本或文件
创建shell脚本
第一步:使用文本编辑器来创建文本文件 第一行必须包括shell声明序列: #! #!/bin/bash 添加注释 注释以#开头 第二步:运行脚本 给予执行权限,在命令行上指定脚本的绝对或相对路径 直接运行解释器,将脚本作为解释器程序的参数运行
脚本调试
bash -n /path/to/some_script 检测脚本中的语法错误 bash -x /path/to/some_script 调试执行
变量
变量:命名的内存空间 数据存储方式: 字符: 数值:整型,浮点型 变量:变量类型 作用: 1、数据存储格式 2、参与的运算 3、表示的数据范围 类型: 字符 数值:整型、浮点型
编程程序语言分类
强类型:定义变量时必须指定类型、参与运算必须符合类型 要求;调用未声明变量会产生错误 如 java,python 弱类型:无须指定类型,默认均为字符型;参与运算会自动 进行隐式类型转换;变量无须事先定义可直接调用 如: bash 不支持浮点数 变量命名法则: 1、不能使程序中的保留字:例如if, for; 2、只能使用数字、字母及下划线,且不能以数字开头 3、见名知义 4、统一命名规则:驼峰命名法
bash中变量的种类
根据变量的生效范围等标准: 本地变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子进程均无效 环境变量:生效范围为当前shell进程及其子进程 局部变量:生效范围为当前shell进程中某代码片段(通常指函数) 位置变量:$1,$2..来表示,用于让脚本在脚本代码中调试通过命令行传递给它的参数 特殊变量:$?,$0,$*,$@,$#
本地变量
变量赋值: name=‘value’, 可以使用引用value: (1) 可以是直接字串; name=“root" (2) 变量引用: name="$USER" (3) 命令引用: name=`COMMAND`, name=$(COMMAND) 变量引用: ${name}, $name "":弱引用,其中的变量引用会被替换为变量值 '':强引用,其中的变量引用不会被替换为变量值,而保 持原字符串 显示已定义的所有变量: set 删除变量: unset name
环境变量
变量声明、赋值: export name=VALUE declare -x name=VALUE 变量引用: $name, ${name} 显示所有环境变量: export env printenv 删除: unset name bash有许多内建的环境变量: PATH, SHELL, USRE,UID, HISTSIZE, HOME, PWD, OLDPWD, HISTFILE, PS1
只读和位置变量
只读变量:只能声时,但不能修改和删除 readonly name declare -r name 位置变量:在脚本代码中调用通过命令行传递给脚本的参数 $1,$2..:对应第一、第二等参数,shift[n]换位置 $0:命令本身 $*:传递给脚本的所有参数,全部参数合为一个字符串 $@:传递给脚本的所有参数,每个参数为独立字符串 $#:传递给脚本的参数的个数 $@,$*只在被双引号包起来的时候才会有差异 示例:判断给出的文件的行数 linecount="$(wc -l $1| cut -d' ' -f1)" ho "$1 has $linecount lines."
算术运算
bash中的算术运算:help let +,-,*,/,%(取模),**(乘方) 实现算术运算: (1)let var=算术表达式 (2)var=$[算术表达式] (3)var=$((算术表达式)) (4)var=$(expr arg1 arg2 arg3..) (5)declare -i var =数值 (6)echo '算术表达式'|bc 乘法符号有些场景中需要转义,如* bash有内建的随机数生成器: $RANDOM( 1-32767) echo $[$RANDOM%50] : 0-49之间随机数
赋值
增强型赋值: +=,-=,*=,/=,%= let varOPERvalue 例如:let count+=3 自加3后自赋值 自增,自减: let var+=1 let var++ let var-=1 let var--
逻辑运算
true,false 1 0 与: 1 与 1 = 1 1 与 0 = 0 0 与 1 = 0 0 与 0 = 0 或: 1 或 1 = 1 1 或 0 = 1 0 或 1 = 1 0 或 0 = 0 非:! ! 1 = 0 ! 0 = 1 短路运算: 短路与: 第一个为0,结果必定为0; 第一个为1,第二个必须要参与运算; 短路或: 第一个为1,结果必定为1; 第一个为0,第二个必须要参与运算; 异或: ^ 异或的两个值,相同为假,不同为真
聚集命令
有两种聚集命令的方法: 复合式:date;who |wc -l 命令会一个接一个地运行 子shell:(date;who |wc -l) >> /tmp/trace 所有的输出都被发送给单个STDOUT和STDERR
退出状态
进程使用退出状态来报告成功或失败 0 代表成功,1-255代表失败 $?变量保存最近的命令退出状态 例如: $ ping -c1 -W1 hostname &> /dev/null $ echo $?
退出状态码
bash自定义退出状态码 exit [n]:自定义退出状态码; 注意:脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字 注意:如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
条件测试
判断某需求是否满足,需要由测试机制来实现; 专用的测试表达式需要由测试命令辅助完成测试过程; 评估布尔声明,以便用在条件性执行中 • 若真,则返回0 • 若假,则返回1 测试命令: • test EXPRESSION • [ EXPRESSION ] • [[ EXPRESSION ]] 注意: EXPRESSION前后必须有空白字符
条件性的执行操作符
根据退出状态而定,命令可以有条件地运行 • && 代表条件性的AND THEN • || 代表条件性的OR ELSE 例如: $ grep -q no_such_user /etc/passwd \ || echo 'No such user' No such user $ ping -c1 -W2 station1 &> /dev/null \ > && echo "station1 is up" \ > || (echo 'station1 is unreachable'; exit 1) station1 is up
test命令
长格式的例子: $ test "$A" == "$B" && echo "Strings are equal" $ test “$A” -eq “$B” \ && echo "Integers are equal" 简写格式的例子: $ [ "$A" == "$B" ] && echo "Strings are equal" $ [ "$A" -eq "$B" ] && echo "Integers are equal"
bash的测试类型
数值测试: -eq:是否等于 -ne:是不等于否 -gt:是否大于 -ge:是否大于等于 -lt:是否小于 -le:是否小于等于
bash的测试类型
字符串测试: ==:是否等于; >: ascii码是否大于ascii码 <: 是否小于 !=: 是否不等于 =~:左侧字符串是否能被右侧的PATHERN所匹配 注意:此表达式一般用于[[ ]]中; -z "STRING":字符串是否为空,空为真,不空为假 -n "STRING":字符串是否不空,不空为真,空为假 注意:用于字符串比较时用到的操作数都应该使用引号
文件测试
存在性测试 -a FILE:同-e -e FILE: 文件存在性测试,存在为真,否则为假; 存在性及类别测试 -b FILE:是否存在且为块设备文件; -c FILE:是否存在且为字符设备文件; -d FILE:是否存在且为目录文件; -f FILE:是否存在且为普通文件; -h FILE 或 -L FILE:存在且为符号链接文件; -p FILE:是否存在且为命名管道文件; -S FILE:是否存在且为套接字文件; 文件权限测试: -r FILE:是否存在且可读 -w FILE: 是否存在且可写 -x FILE: 是否存在且可执行 文件特殊权限测试: -g FILE:是否存在且拥有sgid权限; -u FILE:是否存在且拥有suid权限; -k FILE:是否存在且拥有sticky权限; 文件大小测试: -s FILE: 是否存在且非空; 文件是否打开: -t fd: fd表示文件描述符是否已经打开且与某终端相关 -N FILE:文件自动上一次被读取之后是否被修改过 -O FILE:当前有效用户是否为文件属主 -G FILE:当前有效用户是否为文件属组 双目测试: FILE1 -ef FILE2: FILE1与FILE2是否指向同一个设 备上的相同inode FILE1 -nt FILE2: FILE1是否新于FILE2; FILE1 -ot FILE2: FILE1是否旧于FILE2;
组合测试条件
第一种方式: COMMAND1 && COMMAND2 并且 COMMAND1 || COMMAND2 或者 ! COMMAND 非 如: [ -e FILE ] && [ -r FILE ] 第二种方式: EXPRESSION1 -a EXPRESSION2 并且 EXPRESSION1 -o EXPRESSION2 或者 ! EXPRESSION
使用read命令来接收输入
使用read来把输入值分配给一个或多个shell变量: -p 指定要显示的提示 -t TIMEOUT 多久以后没输入命令自动退出 read 从标准输入中读取值,给每个单词分配一个变量,所有剩余的单词都被分配给最后一个变量 read -p “Enter a filename: “ FILE
流程控制
过程式编程语言: 顺序执行 选择执行 循环执行 ###条件选择if语句 选择执行: 注意: if语句可嵌套 单分支 if 判断条件: then 条件为真的分支代码 fi 双分支 if 判断条件; then 条件为真的分支代码 else 条件为假的分支代码 fi 多分支 if CONDITION1; then if-true elif CONDITION2; then if-ture elif CONDITION3; then if-ture ... else all-false fi 逐条件进行判断,第一次遇为“真”条件时,执行其分支, 而后结束整个if语句 条件判断:case语句 case 变量引用 in PAT1) 分支1 ;; PAT2) 分支2 ;; ... *) 默认分支 ;; esac case支持glob风格的通配符: *: 任意长度任意字符 ?: 任意单个字符 []:指定范围内的任意单个字符 a|b: a或b
循环
循环执行 将某代码段重复运行多次 重复运行多少次: 循环次数事先已知 循环次数事先未知 有进入条件和退出条件 for, while, until
for循环
for 变量名 in 列表;do 循环体 done 执行机制: 依次将列表中的元素赋值给“变量名” ; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束 列表生成方式: (1) 直接给出列表 (2) 整数列表: (a) {start..end} (b) $(seq [start [step]] end) (3) 返回列表的命令 $(COMMAND) (4) 使用glob, 如: *.sh (5) 变量引用; $@, $*
while循环
while CONDITION; do 循环体 done CONDITION:循环控制条件;进入循环之前,先做一次判 断;每一次循环之后会再次做判断;条件为“ true”,则执行 一次循环;直到条件测试状态为“ false”终止循环 因此: CONDTION一般应该有循环控制变量;而此变量的值 会在循环体不断地被修正 进入条件: CONDITION为true; 退出条件: CONDITION为false
until循环
until CONDITION; do 循环体 done 进入条件: CONDITION 为false 退出条件: CONDITION 为true
循环控制语句continue
用于循环体中 continue [N]:提前结束第N层的本轮循环,而直接进入下一 轮判断;最内层为第1层 while CONDTIITON1; do CMD1 ... if CONDITION2; then continue fi CMDn ... done
循环控制语句break
用于循环体中 break [N]:提前结束第N层循环, 最内层为第1层 while CONDTIITON1; do CMD1 ... if CONDITION2; then break fi CMDn ... done
创建无限循环
while true; do 循环体 done until false; do 循环体 Done
特殊用法
while循环的特殊用法(遍历文件的每一行): while read line; do 循环体 done < /PATH/FROM/SOMEFILE 依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将 行赋值给变量line 双小括号方法,即((…))格式,也可以用于算术运算 双小括号方法也可以使bash Shell实现C语言风格的变量操作 #I=10 #((I++)) for循环的特殊格式: for ((控制变量初始化;条件判断表达式;控制变量的修正表达式)) do 循环体 done 控制变量初始化:仅在运行到循环代码段时执行一次 控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断
select循环与菜单
select variable in list do 循环体命令 done select 循环主要用于创建菜单,按数字顺序排列的 菜单项将显示在标准错误上,并显示 PS3 提示符,等待用户输入 用户输入菜单列表中的某个数字,执行相应的命令 用户输入被保存在内置变量 REPLY 中。
select与case
select 是个无限循环,因此要记住用 break 命令退 出循环,或用 exit 命令终止脚本。也可以按 ctrl+c退出循环。 select 经常和 case 联合使用 与 for 循环类似,可以省略 in list , 此时使用位置 参量
函数介绍
1、函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程 2、它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell序的一部分。 3、函数与shell程序比较相似,区别在于: Shell程序在子Shell中运行 而Shell函数在当前Shell中运行。因此在当前Shell中,函数可以对shell中变量进行修改
定义函数
函数由两部分组成:函数名和函数体。 语法一: function f_name { ...函数体... }
函数使用
函数的定义和使用: 可在交互式环境下定义函数 可将函数放在脚本文件中作为它的一部分 可放在只包含函数的单独文件中 调用:函数只有被调用才会执行; 调用:给定函数名 函数名出现的地方,会被自动替换为函数代码 函数的生命周期:被调用时创建,返回时终止
函数返回值
函数有两种返回值; 函数的执行结果返回值: (1)使用echo或printf命令进行输出 (2)函数体中调用命令的输出结果 函数的退出状态码: (1)默认取决于函数中执行的最后一条命令的退出状态码 (2)自定义退出状态码,其格式为: return 从函数中返回,用最后状态命令决定返回值 return 0 无错误返回 return 1-255 有错误返回
交互式环境下定义和使用函数
示例: $dir() { > ls -l > } 定义该函数后,若在$后面键入dir,其显示结果同ls -l的 作用相同。 $dir 该dir函数将一直保留到用户从系统退出,或执行了如下 所示的unset命令: $ unset dir
在脚本中定义及使用函数
函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用 调用函数仅使用其函数名即可。 示例: $cat func1 #!/bin/bash # func1 hello() { echo "Hello there today's date is `date +%F`" } echo "now going to the function hello" hello echo "back from the function"
使用函数文件
可以经常使用的函数存入函数文件,然后将函数文件载入shell。 文件名可任意选取,但最好与相关任务有某种联系。例如:function.main 一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数。 若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入次文件。
创建函数文件
$cat func1 #!/bin/bash # func1 hello() { echo "Hello there today's date is `date +%F`" } echo "now going to the function hello" hello echo "back from the function"
载入函数
函数文件已创建好后,要将它载入shell 定位函数文件并载入shell的格式: . filename 或 source filename 注意:此即<点> <空格> <文件名> 这里的文件名要带正确路径 示例:上例中的函数,可使用如下命令: $ . functions.main
检测载入函数
使用set命令检查函数是否已载入。 set命令将在shell中显示 所有的载入函数。 示例: $set findit=( ) { if [ $# -lt 1 ]; then echo "usage :findit file"; return 1 fi find / -name $1 -print }
执行shell函数
要执行函数,简单地键入函数名即可: 示例: $findit groups /usr/bin/groups /usr/local/backups/groups.bak
删除shell函数
现在对函数做一些改动。首先删除函数,使其对shell不可用 。使用unset命令完成此功能. 命令格式为: unset function_name 实例: $unset findit 再键入set命令,函数将不再显示
函数参数
函数可以接受参数: 传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可;例如“ testfunc arg1 arg2 ...” 在函数体中当中,可使用$1, $2, ...调用这些参数;还可以使用$@, $*, $#等特殊变量
函数变量
变量作用域: 环境变量:当前shell和子shell有效 本地变量:只在当前shell进程有效,为执行脚本会启动 专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数。 局部变量:函数的生命周期;函数结束时变量被自动销毁 注意:如果函数中有局部变量,如果其名称同本地变量, 使用局部变量。 在函数中定义局部变量的方法 local NAME=VALUE
函数递归示例
函数递归: 函数直接或间接调用自身 注意递归层数 递归实例: 阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语 一个正整数的阶乘( factorial)是所有小于及等于该数的正整 数的积,并且有0的阶乘为1。自然数n的阶乘写作n!。 n!=1×2×3×...×n。 阶乘亦可以递归方式定义: 0!=1, n!=(n-1)!×n。 n!=n(n-1)(n-2)...1 n(n-1)! = n(n-1)(n-2)! 示例: fact.sh #!/bin/bash # fact() { if [ $1 -eq 0 -o $1 -eq 1 ]; then echo 1 else echo $[$1*$(fact $[$1-1])] fi } fact 5
作业
1、打印九九乘法表
2、利用变量RANDOM生成10个随机数字,输出这个10数字,并显示其中的最大者和最小者
3、打印国际象棋棋盘
4、写个脚本:
*
5、扫描/etc/passwd文件每一行,如发现GECOS字段为空,则填充用户名和单位电话为62985600,并提示该用户的GECOS信息修改成功
6函数
1、写一个服务脚本/root/bin/testsrv.sh,完成如下要求
(1) 脚本可接受参数: start, stop, restart, status
(2) 如果参数非此四者之一,提示使用格式后报错退出
(3) 如是start:则创建/var/lock/subsys/SCRIPT_NAME, 并显示“启动成功”考虑:如果事先已经启动过一次,该如何处理?
(4) 如是stop:则删除/var/lock/subsys/SCRIPT_NAME, 并显示“停止完成”考虑:如果事先已然停止过了,该如何处理?
(5) 如是restart,则先stop, 再start考虑:如果本来没有start,如何处理?
(6) 如是status, 则如果/var/lock/subsys/SCRIPTNAME文件存在,则显示“ SCRIPTNAME is running…”如果/var/lock/subsys/SCRIPT_NAME文件不存在,则显示“ SCRIPTNAMEis stopped…”其中: SCRIPTNAME为当前脚本名
(7)在所有模式下禁止启动该服务,可用chkconfig 和 service命令管理
1 #!/bin/bash 2 # 3 # 4 cat << EOF 5 start 6 stop 7 restart 8 status 9 ================== 10 EOF 11 12 read -p "please input options:" option 13 14 while [ "$option" != "start" -a "$option" != "stop" -a "$option" != "restart" -a "$option" != "status" ];do 15 read -p "please input options again:" option 16 done 17 18 file=/var/lock/subsys/SCRIPT_NAME 19 20 start(){ 21 if [[ -f $file ]];then 22 echo "this file has been exist" 23 else 24 touch $file 25 echo "start success" 26 fi 27 } 28 29 stop(){ 30 if [[ -f $file ]];then 31 rm -f $file 32 echo "stop success" 33 else 34 echo "this file has been disappion" 35 fi 36 } 37 38 status(){ 39 if [ -z $file ];then 40 echo "SCRIPT_NAME is running" 41 else 42 echo "SCRIPT_NAME is stopping" 43 fi 44 } 45 46 case $option in 47 start) 48 start 49 ;; 50 stop) 51 stop 52 ;; 53 restart) 54 start 55 stop 56 ;; 57 *) 58 status 59 ;; 60 esac 61 62
7、写一个函数实现两个数字做为参数,返回最大值
8、斐波那契数列又称黄金分割数列,因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列: 0、 1、 1、 2、 3、 5、 8、 13、 21、 34、 ……,斐波纳契数列以如下被以递归的方法定义: F( 0) =0, F( 1) =1, F( n) =F(n-1)+F(n-2)( n≥2)写一个函数,求n阶斐波那契数列
原创文章,作者:15152188070,如若转载,请注明出处:http://www.178linux.com/38938