一、shell脚本简介。
1.1 什么是shell,
shell是一个命令解释器,他在操作系统的最外层,负责直接与用户对话,把用户的输入解释给操作系统,并处理各种各样的操作系统的输出结果,输出到屏幕返回给用户,这种对话方式可以是交互的方式(从键盘输入命令,可以立即得到shell的回应),或者非交互式(执行脚本程序的方式)shell是命令解释器,是使用者和内核操作系统沟通桥梁。
1.2 什么是shell脚本
当Linux命令或者语句不再命令行下执行(严格的说,命令行执行的语句也是shell脚本),而是通过一个程序文件执行时,该程序就称为shell脚本或shell程序,shell程序很类似DOS系统下的批处理程序(扩展名.bat),用户可以在shell脚本中敲入一系列的命令及命令语句组合,这些命令、变量和流程控制语句组合起来就形成了一个功能强大的shell脚本。
1.3 shell脚本在运维工作中的作用
shell脚本擅长处理纯文本的数据,而Linux系统中几乎所有的配置文件,日志文件,如(nfs rsync httpd nginx lvs等)、多数启动文件都是纯文本类型的文件,因此,学号shell脚本,可以利用他在运维发挥巨大的作用。
shell的调试:
1、学习好脚本开发规范
2、好的编码习惯,
在掌握脚本调试方法之前,我们应可能的先学些脚本开发规范,从而减少陷入脚本调试的问题,达到未雨绸缪的效果,也就是老师常说的平时多学习好的习惯,注意规范只读,也体现了我们未雨绸缪的思想,常见的错误有如下几个:
1、if 条件语句缺失 fi 结尾关键字。
2、双引号没有对应上
3、中括号中 [] 用 <> 来比较
shell脚本语法小结:
1、重视书写习惯,开发规范和开发制度,尽量减少脚本调试的难度和次数,提高效率
2、shell基本语法要熟悉,才能利用好脚本调试
3、写脚本思路要清晰,
思考开发的框架,尽量模块化开发,复杂的脚本分段实现,
函数1 函数2 函数3 main()函数 main $*
4、如果脚本不是你写的,或者是在windows下开发的脚本,你检查脚本明明没有问题,但就是执行出现错误,要想到执行dos2unix格式化一下。有一个好习惯是写脚本都执行dos2unix格式化一下。
5、使用echo调试,可以在变量操作前后echo开输出调试变量结果,echo 结果然后退出 exit1 这样来调试逻辑错误。
6、bash命令参数调试:
-n 不会执行该脚本,仅查询脚本语法是否有问题,并给出错误提示
-v 在执行脚本时,先将脚本的内容输出到屏幕上然后执行脚本,如果有错误,也会给出错误提示
-x 将执行的脚本内容及输出显示在屏幕上,这个是对调试很有用的参数。
7、shell调试技巧小结:要记得dos2unix来格式化脚本,直接执行脚本根据报错来调试,有时报错不准确, sh -x调试整个脚本,显示执行过程,比较困难,set -x set +x 调试部分脚本(要在脚本中设置),能缩小范围,echo 输出变量及相关内容,然后紧跟着exit退出,不执行后面程序的方法来跟踪脚本,对于逻辑错误比较好用,最关键的是要熟练语法,编码习惯,编程思想,将错误扼杀在萌芽志宏,降低调试负担,提高效率。
1.4 文件测试表达式
我们在编程时需要处理一个对象时,需要先进行判断,只有符合要求,采取操作处理,这样做的好处就是避免程序出错以及无谓的消耗系统资源,这个要测试的对象可以是文件,字符串,数字等。
在书写文件测试表达式时,常用如下:
常用文件测试操作符号 | 说明 |
-f 文件 英文file | 文件存在且为普通文件则真,即测试表达式成立 |
-d 文件 英文 directory | 文件存在且为目录文件则真,即测试表达式成立 |
-s 文件 英文size | 文件存在且文件大小不为0则真,即测试表达式成立 |
-e 文件 英文 exist | 文件存在则真,即测试表达式成立,只要有文件就行, |
-r 文件 英文read | 文件存在且可读则真,即测试表达式成立 |
-w 文件 英文write |
文件存在且可写则真,即测试表达式成立 |
-x 文件 英文 executable | 文件存在且可执行则真,即测试表达式成立 |
-L 文件 英文link | 文件存在且为链接文件则真,即测试表达式成立 |
f1 -nt f2 英文 newer than | 文件f1比f2新则真,即测试表达式成立,根据文件修改时间计算 |
f1 -ot f2 英文 older than | 文件f1比f2旧则真,即测试表达式成立,根据文件修改时间计算。 |
普通文件测试表达式举例:
[root@localhost pandj]# touch pandj //创建一个测试文件 [root@localhost pandj]# [ -f pandj ]&&echo 1||echo 0 //需要注意[]中前后都有空格,意思是 pandj这个文件存在就输出1 不存在就输出0 1 //返回1表示文件存在 [root@localhost pandj]# mkdir pandj.d //创建一个测试目录 [root@localhost pandj]# ll total 0 -rw-r--r--. 1 root root 0 Aug 8 10:28 pandj drwxr-xr-x. 2 root root 6 Aug 8 10:33 pandj.d [root@localhost pandj]# [ -d pandj.d ]&&echo 1 || echo 0 1 //测试文件属性 [root@localhost pandj]# ll pandj -rw-r--r--. 1 root root 0 Aug 8 10:28 pandj //查看文件具有可读可写的权限 [root@localhost pandj]# [ -r pandj ]&&echo 1||echo 0 1 [root@localhost pandj]# [ -w pandj ]&&echo 1||echo 0 1 [root@localhost pandj]# [ -x pandj ]&&echo 1||echo 0 0 [root@localhost pandj]# [root@localhost pandj]# file1=/etc/services //定义一个变量来举例、 [root@localhost pandj]# [ -f $file1 ]&&echo 1 || echo 0 //这里看的是file1这个变量对应的文件是否存在 1
1.5 字符串表达式
需要注意的是,字符串测试操作符号需要用""引起来,比较符号两端都有空格
常用字符串表达式操作符 | 说明 |
-z "字符串" | 若字符串长度为0 则为真 |
-n "字符串" | 若字符串长度不为0 则为真 |
"串1"="串2" | 若字符串1等于字符串2,为真,可使用==代替 = |
"串1"!="串2" | 若字符串1不等于字符串2,为真 |
字符串表达式举例:
[root@localhost pandj]# [ -n "pandj" ]&&echo 1 || echo 0 1 [root@localhost pandj]# [ -z "pandj" ]&&echo 1 || echo 0 0 [root@localhost pandj]# [ "pandj" = "pandj" ]&&echo 1||echo 0 1 [root@localhost pandj]# [ "pandj" != "pandj" ]&&echo 1||echo 0 0 [root@localhost pandj]# test=pandj [root@localhost pandj]# echo $test pandj [root@localhost pandj]# [ $test = "pandj" ]&&echo 1 ||echo 0 1
1.5 整数二元比较操作符
在书写测试表达式时,可以用下表中的整数二元比较操作符:
在[]以及test中使用的比较符 | 在(())和[[]]中使用的比较符 | 说明 |
-eq | == | 相当 |
-ne | != | 不相等 |
-gt | > | 大于 |
-ge | >= | 大于等于 |
-lt | < | 小于 |
-le | <= | 小于等于 |
整数二元比较符举例:
[root@localhost systemd]# [ 2 -eq 1 ]&&echo 1 ||echo 0 0 [root@localhost systemd]# [ 2 -gt 1 ]&&echo 1 ||echo 0 1 [root@localhost systemd]# [ 2 -lt 1 ]&&echo 1 ||echo 0 0 //提示: “=” “!=” 在[]中使用不需要转义,包含“>” “<”的符号在[]中使用需要转义,对于数字不转义的结果未必报错,但是结果可能不会对。推荐在使用过程中用 [] 及 -eq 这样的写法。 //整数比较不要加双引号
1.5 逻辑操作符
逻辑操作如下所示:
在[]和test中使用的逻辑操作符 | 在[[]]中使用的逻辑操作符, | 说明 |
-a | && | and 与 两端都为真,则真 |
-o | || | or 或两端有一个为真,则真 |
! | ! | not 非,相反则为真。 |
逻辑操作符举例:
[root@localhost systemd]# f1=/etc/rc.local;f2=/etc/services [root@localhost systemd]# [ -f "$f1" ]&&echo 1 1 [root@localhost systemd]# [ -f "$f2" ]&&echo 1 1 [root@localhost systemd]# [ -f "$f1" -a -f "$f2" ]&&echo 1||echo 0 1 [root@localhost systemd]# f2=/pandj/nofile [root@localhost systemd]# [ -f "$f1" -o -f "$f2" ]&&echo 1||echo 0 1 [root@localhost systemd]# [ -f "$f1" -a -f "$f2" ]&&echo 1||echo 0 0
逻辑操作符小结: [] 中就用 -a -o ! [[]] 中就用 && || ! test中的用法和[]相同,多个[]之间以及多个[[]]之间,或者任意混合中间逻辑操作符都是用 && ||
1.6 分支和循环结构 //重要
if语句是实际生产工作中最重要且最常用的语句,所以,if语句必须要掌握牢固
if 语句的语法如下:
1.6.1 if 语句的单分支结构:
if [ 条件 ] then 指令 fi //或者 if [ 条件 ];then //比较常用 指令 fi
1.6.2 if 语句的双分支结构
if [ 条件 ];then 指令1 else 指令2 fi
1.6.3 if 语句的多分支结构
if [ 条件 ];then 指令 elif [ 条件 ];then 指令 else 指令 fi
1.7 分支和循环结构
函数也就是具有和别名类似的功能:
简单的讲,函数的作用就是把程序里面多次调用的相同的代码部分定义成一份,然后为这这一份代码起个名字,其他所有的重复调用这部分代码就只要调用这个名字就可以了,当需要修改这部分重复代码时,只需要改变函数体内的一部分代码即可实现所有调用修改,
函数的优势:
1、把相同的程序段定义成函数,可以减少整个代码的代码量
2、增加程序的可读,易读性、以及可管理性。
3、可以实现程序功能模块化,不同的程序使用函数模块化
4、可以让程序代码结构更清晰
强调:对于shell来说,Linux系统的200个命令都可以说是shell的函数。
shell函数的语法如下:
简单语法格式:
函数名(){ 指令 return n //提示:shell中用exit来输出返回值,函数里用return来输出返回值。 }
规范语法格式:
function 函数名(){ 指令 return n //提示:shell中用exit来输出返回值,函数里用return来输出返回值。 }
shell函数的执行:
1、直接执行函数名即可(不带括号)
但是需要注意:
a、执行函数时,函数后的小括号不要带了,
b、函数定义及函数体必须在要执行的函数名前面定义,shell的执行是从上到下的按行执行的。
2、带参数的函数执行方法:
函数名 参数1 参数2
说明:
a、shell的位置参数($1 $2 $3 $# $* $? $@)都可以是函数的参数
b、此时父脚本的参数临时地被函数参数所掩盖或隐藏
c、$0 比较特殊,它仍然是父脚本的名称
d、当函数完成时,原来的命令行脚本的参数即恢复
e、在shell函数里面,return命令功能和shell里面的exit类似,作用是跳出函数。
f、在shell函数体里面使用exit会推出整个shell脚本,而不是退出shell函数
g、return 语句会返回一个退出值给调用函数的程序
h、函数的参数变量是在函数体里面定义,如果是普通变量一般会使用 local i 定义。
shell脚本中的自带参数:
$# 是传给脚本的参数个数
$0 是脚本本身的名字
$1是传递给该shell脚本的第一个参数
$2是传递给该shell脚本的第二个参数
$@ 是传给脚本的所有参数的列表
$0 是脚本本身的名字
$1是传递给该shell脚本的第一个参数
$2是传递给该shell脚本的第二个参数
$@ 是传给脚本的所有参数的列表
$* 是所有参数列表
1.7 分支和循环结构之 case 语句详解
case语句实际上就是规范的多分支If语句,
case "字符串变量"in 值1)指令1 ;; 值2)指令2 ;; *)指令3 esac
case小结:
1、case语句就相当于多分支if语句,case语句优势更规范,易读
2、case语句适合变量的值少,且为固定的数字或者字符串集合,(1、2、3)或者(start、stop、restart)
3、系统服务启动脚本传参的判断多用case语句,参考rpcbind/nfs/crond脚本
4、所有的case语句都可以用if实现,但是case更规范清晰一些
5、case 一般适合与服务的脚本启动。
6、case 的变量的值如果已知固定的 /start/stop/restart元素的时候比较适合一些,
语句小结:运维层面
1、case主要是写启动脚本,范围较窄
2、if 取值判断,比较, 应用更广。
1.7 分支和循环结构之 while语句详解
while循环在工作中使用的不多,一般是守护进程程序会用或者始终循环执行场景,其他循环计算,都会用for替换。
while条件语句格式如下:
while 条件 do 指令 done
while是当型循环,需要了解一下sleep,sleep 1 休息一秒, 如果长达一分钟,可以使用定时任务管理,
举例解释,没三秒输出当前系统的负载情况。
#!/bin/sh while true do uptime sleep 3 done ~
在脚本后面加 & 符的意思是后台执行脚本,在后台永久执行,我们称之为守护进程模式。
同时,在脚本后面加&符也是方式客户端执行脚本中断的方法,防止客户端执行脚本中断的方法一共有三种:
1、在脚本后面加 & 符,后台执行脚本
2、nohup /server/scripts/uptime.sh & nohup 用户退出系统之后继续工作。
3、screen 保持会话,有时间总结这个命令,
下面举个脚本后台执行的例子。
[root@localhost scripts]# sh while-1.sh & [1] 4776 [root@localhost scripts]# jobs //jobs可以显示后台程序 [1]+ Running sh while-1.sh & [root@localhost scripts]# ps -ef | grep while-1.sh root 4776 2317 0 11:17 pts/0 00:00:00 sh while-1.sh root 4815 2317 0 11:18 pts/0 00:00:00 grep --color=auto while-1.sh [root@localhost scripts]# kill 4776
命令补充:
bg 后台执行 fg 挂起执行 jobs 显示后台程序 kill killall pkill 杀掉进程 crontab 设置定时 ps 查看进程 pstree 显示进程状态树 nice 改变优先权, nohup 用户推出系统之后继续工作 pgrep 查找匹配条件的进程 strace 跟踪一个进程的系统调用情况 ltrace 跟踪进程调用库函数的情况 vmstat 报告虚拟内存他哦那估计信息。
while循环小结:
1、 while循环的特长是执行守护进程,以及我们希望循环不退出持续执行的场景,用于频率小于一分钟的循环处理,其他的while循环几乎都可以被我们即将要讲的for循环代替,
2、case语句可以if语句代替,一般在系统启动脚本传入少量固定规则字符串,用case语句,其他普通判断多用if
3、一句话,if for 语句最常用,其次是while(守护进程),case(服务启动脚本)
各个语句使用场景:
条件表达式,简单的判断,(文件是否存在,字符串是否为空)
if 取值判断,不同值数量较少的情况
for 正常的循环处理,最常用
while 守护进程,无限循环sleep
case服务启动脚本,菜单,
函数,逻辑清晰,减少重复语句。
1.7 分支和循环结构之 for 语句详解 (重要)
for循环语法结构:
for 变量名 in 变量列表 do 指令 done 提示。在此结构中in 变量取值列表可省略,省略时相当于 in $@ 使用for i 就相当于使用for i in $@
提示:
1、程序持续运行多用while,包括守护进程,还有配合read读入循环处理
2、有限次循环多用for,工作中使用for更多。
break continue exit return的区别。
break n n 表示跳出循环的层数,如果省略n表示跳出整个循环
continue n n表示退出到第n层继续循环,如果省略n表示跳过本次循环,忽略本次循环的剩余代码,进入循环的下一次循环
exit n 退出当前shell程序, n 为返回值,n也可以省略,再下一个shell中通过$?来接收这个n的值
retuen n 用于再函数里面,作为函数的返回值,用于判断函数执行是否正确。
1.8 shell数组,
平时的定义 a=1 b=2 c=3,变量如果变多了,再一个个定义很费劲,并且取变量也很费劲,
简单的说,数组就是各种数据类型的元素按一定顺序排序排列的集合,
数组就是把有限个元素变量或数据用一个名字命名,然后用编号区分他们的变量的集合,这个名字称为数组名,编号称为数组下标,组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量
由于有了数组,可以用相同名字引用一系列变量,并用数字(索引)来识别他们,在很多场合,使用数组可以缩短和简化程序开发,因为可以利用索引值设计一个循环,高效处理很多情况。
[root@localhost ~]# array=(1 2 3) //定义一个简单的数组 [root@localhost ~]# echo ${#array[*]} //取这个数组的值的个数。 3 [root@localhost ~]# echo ${array[0]} //取数组的第一个值,需要注意数组的下标从0开始。 1 [root@localhost ~]# echo ${array[*]} //查看数组中所有的值 1 2 3 [root@localhost file3]# array[3]=4 //给数组添加值 [root@localhost file3]# echo ${array[*]} 1 2 3 4 [root@localhost file3]# unset array[0] //删除数组中的值 [root@localhost file3]# echo ${array[*]} 2 3 4 [root@localhost scripts]# array1=($(ls)) //可以将一个命令的返回结果放进数组里面 [root@localhost scripts]# echo ${array1[*]} apple.sh case.sh color.sh file3 for1.sh for2.sh for3.sh fun01.8.9.sh httpd_check.8.8.sh lamp.8.8.sh lnmp.8.8.sh menu.sh nginx_admin.sh nginx_config.sh num100.sh num.8.8.sh url_check.8.9.sh while-1.sh youxiu.8.8.sh
shell数组相关的知识小结。
1、定义:
静态数据:array=(1 2 3)
动态数据:array=($(ls))
给数组赋值:array[3]=4
2、打印:
打印所有元素: ${array[@]} ${array[*]}
打印数组长度 ${#array[@]} ${#array[*]}
打印单个元素 ${array[i]} i是数组的下标。