Linux就该这么学第4章 Vim编辑器与Shell命令脚本简述 本章节首先教给读者们如何使用vim编辑器来编写、修改文档,然后通过逐个配置主机名称、系统网卡以及Yum仓库参数文件等实验来加深对vim编辑器中诸多命令、快捷键的执行方法与各个模式切换的方法。然后把第2章节的Linux命令、第3章节的命令语法与Shell脚本中的if条件测试语句、for条件测试循环语句、while条件循环语句以及case条件测试语句通过vim编辑器写到Shell脚本中结合到一起,实现最终能够自动化工作的软件脚本工具。最后为读者们演示了怎样通过at命令与crond计划任务服务来分别实现一次性与长期性的系统任务设置方法,让日常的工作更加高效自动化,一劳永逸。
本章目录结构 4.1.Vim文本编辑器 4.1.1.编写简单文档 4.1.2.配置主机名称 4.1.3.配置网卡信息 4.1.4.配置Yum仓库 4.2.编写Shell脚本 4.2.1.编写简单的脚本 4.2.2.接收用户的参数 4.2.3.判断用户的参数 4.3.流程控制语句 4.3.1.if条件测试语句 4.3.2.for条件循环语句 4.3.3.while条件循环语句 4.3.4.case条件测试语句 4.4.计划任务服务程序
4.1.Vim文本编辑器 每当我在讲课时碰到有需要让学生记住的知识点时,普遍都会为了能让同学们打起精神来突然提高嗓门,因此有句话同学们记得尤其深刻——“Linux系统中一切都是文件,而配置一个服务就是在修改其配置文件的参数”。而且在日常工作中也一定免不了遇到编写文档的事情吧,这些都是要通过文本编辑器来完成的。您手中在读的这本《Linux就该这么学》书籍创作目的是让读者真真切切掌握Linux系统的运维方法,而不单纯是针对某个系统而言的“会用”而已,所以咱们的课程中选择学习vim这个当前整个linux操作系统中都会默认安装好的一款超棒文本编辑器,学会后绝对会能让您“通吃”当前主流的系统。 Vim能够得到广大厂商与众多用户的认可,原因就是在Vim编辑器中内设有的三种模式——命令模式、末行模式和编辑模式,每种模式分别又支持多种不同的命令快捷键组合,大大的提高了工作效率,用习惯后会觉得非常的顺手。而要想在文本操作时更加高效率,就必须先搞清Vim编辑器的三种模式的操作不同与切换方法,如图4-1所示。
图4-1 Vim编辑器模式的切换方法 命令模式:控制光标移动,可对文本进行删除、复制、粘贴和查找等工作。 输入模式:正常的文本录入。 末行模式:保存、退出与设置编辑环境。 每次运行vim编辑器后都默认会是“命令模式”,需要先进入到“输入模式”后再进行编写文档的工作,而每次编辑完成需先返回到“命令模式”后再进入“末行模式”中执行对文本的保存或退出操作,并不能直接从“输入模式”切换到“末行模式”。vim编辑器内设支持的命令有成百上千种用法,为了能够帮助读者更快的掌握vim编辑器,刘遄老师分别总结了在命令模式和末行模式中最常用的一些快捷键命令,如下表所示: 命令-作用 dd-删除(剪切)光标所在整行。 5dd-删除(剪切)从光标处开始的5行。 yy-复制光标所在整行。 5yy-复制从光标处开始的5行。 n-显示搜索命令定位到的下一个字符串。 N-显示搜索命令定位到的上一个字符串。 u-撤销上一步的操作 p-将之前删除(dd)或复制(yy)过的数据粘贴到光标后。 ?字符串-在文本中从下至上搜索该字符串。 /字符串-在文本中从上至下搜索该字符串。 末行模式主要用于保存或退出文件,能够设置vim编辑器的整体使用环境,还可以让用户执行外部Linux命令或跳转到特定的行数,切换到末行模式的方式就是在命令模式中输入一个冒号就可以的,末行模式中可用的命令如下表: 命令-作用 :w-保存 :q-退出 :q!-强制退出(放弃对文本的修改内容) :wq!-强制保存退出 :set nu-显示行号 :set nonu-不显示行号 :命令-执行该命令 :整数-跳转到该行 :s/one/two-将当前光标所在行的第一个one替换成two :s/one/two/g-将当前光标所在行的所有one替换成two :%s/one/two/g- 将全文中的所有one替换成two
4.1.1.编写简单文档 读者们不要怕,您已经具备了在linux系统中编写文本的理论基础了,现在就让咱们一起动手编写个简单的文档吧,刘遄老师会尽力把所有操作步骤和按键过程都标注出来,如果快捷键命令忘记了就回到上面小节再看看吧。编写脚本的第1步就是给文件取个名字,如果文档的名称存在则打开它,如果不存在则是创建一个临时的输入文件,如图4-2所示。
图4-2 通过vim编辑器尝试编写一个文件 进入文件后默认看到的是vim编辑器的命令模式,这时只能够执行命令快捷键而不能随意输入文本内容,必须切换到输入模式才可以开始编写工作。有些读者应该能够猜出a、i、o三键的区别了吧,对,就是光标的位置不同!a键与i键分别是在光标右一位和光标当前位置切换到输入模式,而o键则是在光标的下面再创建一个空行,此时可敲击a键进入到编辑器的输入模式,如图4-3所示。
图4-3 切换至编辑器的输入模式 输入模式是可以随意输入文本内容的,更不会把您输入的文本内容当作命令而执行,如图4-4所示。
图4-4 编辑器中输入文本内容 如果想要保存并退出这个文本文件,必须先从输入模式返回到命令模式,然后切换至末行模式中才能完成保存退出操作,如图4-5与图4-6步骤所示。
图4-5 vim编辑器的命令模式
图4-6 vim编辑器的末行模式 当咱们在末行模式中键入:wq!时就意味着强制保存并退出文件,然后便可以用cat命令查看到保存文件后的信息了,如图4-7所示。
图4-7 查看文档的内容 感觉是不是很简单,咱们接下来继续编辑这个文件,因为要在原有文本的内容下面追加内容,所以在命令模式中敲击o键的话会更高效呢,操作如图4-8、图4-9与图4-10所示。
图4-8 再次通过vim编辑器尝试编写文档内容
图4-9 进入到vim编辑器的输入模式
图4-10 追加写入一行文本内容 因为此时已经对文本进行了修改,所以要想单纯的退出文件就不允许了,编辑器会提示您操作被拒绝,咱们只得强制退出才可以结束本次输入操作,如图4-11,4-12与4-13所示。
图4-11 尝试退出文本编辑器
图4-12 因文件已被修改而拒绝退出操作
图 4-13 强制退出文本编辑器 现在是不是感觉对vim编辑器有了一些实战经验呢,也不是想象中的那么难吧~现在咱们查看下文本的内容会发现果然后面一次输入的内容并没有被保存下来,如图4-14所示。
图 4-14 查看最终编写成的文本内容
4.1.2.配置主机名称 学习完了理论又自己动手编写了一个文本,心里是不是已经满满的的成就感了呢~接下来我会从浅入深为您安排三个小任务,为了真正能把vim编辑器掌握透彻,一定要逐个完成不许偷懒,如果操作不熟练忘记命令的话就回到上面再复习下吧。 为了便于在局域网中指定查找某个用户的电脑或区别主机的作用,除了要有IP地址外还要配置一个主机名,用户之间可以通过这个类似于域名的名称来便捷的相互访问。绝大部分的Linux系统主机名都是保存在/etc/hostname文件中的,咱们要想将其内容修改为"linuxprobe.com",思路大致如下: 第1步:使用vim编辑器修改"/etc/hostname"主机名称文件。 第2步:把原始主机名称删除后追加"linuxprobe.com"。 使用vim编辑器修改主机名称文件后记得要在末行模式执行:wq!后才是保存退出文档: 第3步:保存退出文档并用hostname命令检查是否修改成功。 [root@linuxprobe ~]# vim /etc/hostname linuxprobe.com 使用hostname命令查看当前的主机名称,但有时系统不能立即同步到主机名称已经发生改变,所以如果您确认修改完成却还显示原来的主机名称,可重启虚拟机后再查看下: [root@linuxprobe ~]# hostname linuxprobe.com
4.1.3.配置网卡信息 能够正确的配置网卡IP地址是保证两台服务器互相通信的前提,而Linux系统中的一切都是文件,配置网络的工作其实就是在编辑网卡配置文件,因此这个小任务不仅是帮助您熟练操作vim编辑器的过程,还是在为以后学习各种服务课程打下深深的基础。当您读完这本《Linux就该这么学》书籍后绝对会有一种特别棒的感觉,因为书籍前面基础部分非常扎实,而后面章节则在网卡IP地址和运行环境上几乎保持一致,让您能更加全心的投入到学习各类服务程序上,而不总用操心系统环境的问题。 如果您以前有一定的运维从业经验或者熟悉早期的Linux系统,在学习本书时会有一些不好接受的差异变化,在红帽RHEL6系统中网卡配置文件的前缀为"eth",第1块即为"eth0",第2块即为"eth1"并依此类推,但在红帽RHEL7系统中的网卡配置文件前缀则以"ifcfg-eno"开始的,好在除了文件名变化外也没有其他大的区别。例如现在要想配置一个名称为eno16777736的网卡设备开机自启动并且IP地址、子网、网关等信息由人工指定的话思路应该是: 第1步:首先要切换到"/etc/sysconfig/network-scripts"目录中(该目录存放着网卡的配置文件)。 第2步:使用vim编辑器修改网卡文件"ifcfg-eno16777736",逐项写入配置参数并保存退出,因每台电脑的硬件及架构情况都是不一样的,同学们的网卡默认名称请通过ifconfig命令自行确认。 设备类型:TYPE=Ethernet 地址分配模式:BOOTPROTO=static 网卡名称:NAME=eno16777736 是否启动:ONBOOT=yes IP地址:IPADDR=192.168.10.10 子网掩码:NETMASK=255.255.255.0 网关地址:GATEWAY=192.168.10.1 DNS地址:DNS1=192.168.10.1 第3步:重启网卡设备并测试网络是否联通。 进入到网卡配置文件所在的目录,然后编辑网卡配置文件填入下面的信息: [root@linuxprobe ~]# cd /etc/sysconfig/network-scripts/ [root@linuxprobe network-scripts]# vim ifcfg-eno16777736 TYPE=Ethernet BOOTPROTO=static NAME=eno16777736 ONBOOT=yes IPADDR=192.168.10.10 NETMASK=255.255.255.0 GATEWAY=192.168.10.1 DNS1=192.168.10.1 执行重启网卡设备的命令,正常情况不会有提示信息,然后通过ping命令测试网络能否联通。 [root@linuxprobe network-scripts]# systemctl restart network [root@linuxprobe network-scripts]# ping 192.168.10.10 PING 192.168.10.10 (192.168.10.10) 56(84) bytes of data. 64 bytes from 192.168.10.10: icmp_seq=1 ttl=64 time=0.081 ms 64 bytes from 192.168.10.10: icmp_seq=2 ttl=64 time=0.083 ms 64 bytes from 192.168.10.10: icmp_seq=3 ttl=64 time=0.059 ms 64 bytes from 192.168.10.10: icmp_seq=4 ttl=64 time=0.097 ms ^C --- 192.168.10.10 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 2999ms rtt min/avg/max/mdev = 0.059/0.080/0.097/0.013 ms
4.1.4.配置Yum仓库 在前面的章节中已经学过了Yum软件仓库的作用是为了进一步简化RPM管理软件难度以及自动分析所需软件包及其依赖关系的技术。您可以把Yum想象成是一个硕大的软件仓库,里面保存有几乎所有常用的工具,而只需要说出所需的软件包名称,系统就会自动的为您搞定一切。那么既然要使用Yum技术,就要先把软件仓库搭建起来,然后将其配置规则确定好才行,但是咱们在第6章才会学习到Linux的存储结构和设备挂载操作,所以接下来的任务您还是把重心放到vim编辑器的学习上吧,参数看不懂很正常,后面章节会单独再讲解,不要忘记现在的主要任务是学习vim编辑器哦,任务操作的大致流程顺序: 第1步:首先要进入到"/etc/yum.repos.d/"目录中(因为该目录存放着yum仓库的配置文件) 第2步:使用vim编辑器创建一个名为rhel7.repo的新配置文件(文件名称可随意,但后缀必须为repo),逐项写入下面加粗的配置参数并保存退出(不写中文注释)。 [rhel-media] yum仓库唯一标识符,避免与其他仓库冲突。 name=linuxprobe yum仓库的名称描述,易于识别仓库用处。。 baseurl=file:///media/cdrom 提供方式包括FTP(ftp://..)、HTTP(http://..)、本地(file:///..) enabled=1 设置此源是否可用,1为可用,0为禁用。 gpgcheck=1 设置此源是否校验文件,1为校验,0为不校验。 gpgkey=file:///media/cdrom/RPM-GPG-KEY-redhat-release 若为校验请指定公钥文件地址。 第3步:按配置参数的路径把光盘挂载,并把光盘挂载信息写入到/etc/fstab文件中。 第4步:使用"yum install httpd -y"命令检查Yum仓库是否已经可用。 进入到/etc/yum.repos.d目录中后创建Yum配置文件: [root@linuxprobe ~]# cd /etc/yum.repos.d/ [root@linuxprobe yum.repos.d]# vim rhel7.repo [rhel7] name=rhel7 baseurl=file:///media/cdrom enabled=1 gpgcheck=0 创建挂载点后进行挂载操作,并设置成开机自动挂载(第6章知识内容): [root@linuxprobe yum.repos.d]# mkdir -p /media/cdrom [root@linuxprobe yum.repos.d]# mount /dev/cdrom /media/cdrom mount: /dev/sr0 is write-protected, mounting read-only [root@linuxprobe yum.repos.d]# vim /etc/fstab /dev/cdrom /media/cdrom iso9660 defaults 0 0 尝试使用Yum软件仓库来安装Web服务,出现Complete(完成)则代表Yum仓库配置正确: [root@linuxprobe ~]# yum install httpd Loaded plugins: langpacks, product-id, subscription-manager ………………省略部分输出信息……………… Dependencies Resolved Package Arch Version Repository Size Installing: httpd x86_64 2.4.6-17.el7 rhel 1.2 M Installing for dependencies: apr x86_64 1.4.8-3.el7 rhel 103 k apr-util x86_64 1.5.2-6.el7 rhel 92 k httpd-tools x86_64 2.4.6-17.el7 rhel 77 k mailcap noarch 2.1.41-2.el7 rhel 31 k Transaction Summary Install 1 Package (+4 Dependent packages) Total download size: 1.5 M Installed size: 4.3 M Is this ok [y/d/N]: y Downloading packages: ………………省略部分输出信息……………… Complete!
4.2.编写Shell脚本 我经常把Shell终端解释器形容是人与计算机硬件的“翻译官”,它作为用户与Linux系统内部通讯的媒介,除了允许了各种变量与参数外还提供了诸如循环、分支等高级语言才有的控制结构特性,如何正确的使用这些功能,准确下达命令尤为重要。Shell脚本命令的工作方式有两种,首先是前面所接触的交互方式(Interactive),即当用户每输入一条命令就执行一次,而批处理(Batch)则是由用户事先编写好一个完整的Shell脚本,Shell会一次性执行脚本中诸多的命令。因此在Shell脚本中不仅需要用到很多前面学习过的Linux命令以及正则表达式、管道符、数据流重定向等语法规则,还需要把内部功能模块化后通过逻辑语句进行加工,最终才能成为日常所见的Shell脚本程序,可以通过SHELL变量来查看到当前系统已经默认使用bash解释器作为命令行终端了: [root@linuxprobe ~]# echo $SHELL /bin/bash
4.2.1.编写简单的脚本 估计读者看完上面对Shell脚本如此复杂的描述都有一种心累想放弃的感觉了吧,但这仅指的是一个高深Shell脚本的编写原则,其实使用vim编辑器把linux命令按照顺序依次写入文件就是编写完成一个最简单的脚本啦。例如想查看当前所在工作路径并列出当前目录下所有文件及属性信息,编写一个脚本来完成的话是这样: [root@linuxprobe ~]# vim example.sh #!/bin/bash #For Example BY linuxprobe.com pwd ls -al Shell脚本文件的名称是可以任意起,但为了避免其他同事误以为是普通文件,咱们应该遵守运维行业人员大众的规范把.sh后缀写上,这样让其他人一看就知道是个脚本文件,与人方便自己方便。在这个脚本中实际上出现了三种不同的元素,第一行脚本声明(#!)是用来告知系统用何种shell解释器来执行本脚本程序,第二行注释信息(#)是对程序功能和某些命令的介绍信息,使得自己或他人再次看到这个脚本内容时可以快速知道这些功能的作用或一些警告信息,第三、四行可执行语句也就是咱们平时执行的Linux命令啦~什么?同学们不相信这么简单就编写出来了一个脚本程序,那来执行看一看吧: [root@linuxprobe ~]# bash example.sh /root/Desktop total 8 drwxr-xr-x. 2 root root 23 Jul 23 17:31 . dr-xr-x---. 14 root root 4096 Jul 23 17:31 .. -rwxr--r--. 1 root root 55 Jul 23 17:31 example.sh 第二种运行脚本程序的方法是以输入完整路径的方式来执行,但默认会因为权限不足而提示报错信息,这种情况只需要为脚本文件增加执行权限即可(在第5章会详细讲到): [root@linuxprobe ~]# ./example.sh bash: ./Example.sh: Permission denied [root@linuxprobe ~]# chmod u+x example.sh [root@linuxprobe ~]# ./example.sh /root/Desktop total 8 drwxr-xr-x. 2 root root 23 Jul 23 17:31 . dr-xr-x---. 14 root root 4096 Jul 23 17:31 .. -rwxr--r--. 1 root root 55 Jul 23 17:31 example.sh
4.2.2.接收用户的参数 但是像上面这样的脚本程序在功能上真的太过于“死板”,为了能够让Shell脚本程序更好的满足用户对灵活完成工作的热切需要,必须要让脚本程序能够像咱们以前执行命令时那样来接收用户输入进来的参数。 其实Shell脚本早就考虑到了这些,已经在脚本中定义好了很多变量功能,例如$0对应当前Shell脚本程序的名称,$#对应总共有几个参数,$*对应所有位置的参数值,而$1,$2,$3……依次类推则分别对应着第N个位置的参数,如图4-15所示。
图4-15 Shell脚本程序中的参数位置变量 理论大致看过后就要来动手操作下啦,尝试来编写一个测试用的脚本程序,通过引用上面的变量参数来看下真实效果: [root@linuxprobe ~]# vim example.sh #!/bin/bash echo "当前脚本名称为$0" echo "总共有$#个参数,分别是$*。" echo "第1个参数为$1,第5个为$5。" [root@linuxprobe ~]# sh example.sh one two three four five six 当前脚本名称为example.sh 总共有6个参数,分别是one two three four five six。 第1个参数为one,第5个为five。
4.2.3.判断用户的参数 学习就像是在登台阶,在您学习完执行linux命令,掌握脚本语法变量和接收用户输入信息等方法后就要踏上新的高度——即能够进一步去处理接收到的用户的参数。因为有时咱们也需要像前面学习过的mkdir命令一样来判断用户输入的信息,从而判断用户指定的文件夹名称是否已经存在,已存在则提示报错,不存在则自动的创建。条件测试语法能够判断表达式是否成立,若条件成立则返回数字0,否则便返回其他随机数值。条件测试语法的执行格式如图4-16所示:
图4-16 条件测试语句的执行格式 条件判断语句按照测试对象可分为文件测试、逻辑测试、整数值比较与字符串比较,文件测试即用来按照指定条件来判断文件是否存在或权限是否满足,具体的参数包括有: 操作符 - 作用 -d - 测试是否为目录。 -e - 测试文件或目录是否存在。 -f - 判断是否为文件。 -r - 测试当前用户是否有权限读取。 -w - 测试当前用户是否有权限写入。 -x - 测试当前用户是否有权限执行。 好啦,那么先通过文件测试语句来判断/etc/fstab是否为一个目录文件,然后通过$?变量来显示上一条命令执行后的返回值,这样就可以通过返回的非零值判断目录是不存在的了(即文件测试语句判断结果不符合): [root@linuxprobe ~]# [ -d /etc/fstab ] [root@linuxprobe ~]# echo $? 1 再来用文件测试语句来判断下/etc/fstab是否为一般文件,这样看到返回值是0即代表这个一般文件是存在的: [root@linuxprobe ~]# [ -f /etc/fstab ] [root@linuxprobe ~]# echo $? 0 逻辑测试则是用于判断用户给出的条件是为真还是假,从而把条件测试语句与逻辑语句相搭配结合使用可以实现一个更高级的使用方法,例如在Shell终端中逻辑“与”符号是&&,它代表当前面的命令执行成功后才会执行后面的命令,因此可以用来判断/dev/cdrom设备是否存在,若存在时才输出Exist字样。 [root@linuxprobe ~]# [ -e /dev/cdrom ] && echo "Exist" Exist 除了“与”逻辑测试符号外还有“或”逻辑测试,在Linux系统中的逻辑“或”符号为“||”,它代表当前面的命令执行失败后才会执行后面的命令,因此可以结合系统环境变量USER来判断当前登录的用户是否为非超级管理员身份: [root@linuxprobe ~]# echo $USER root [root@linuxprobe ~]# [ $USER = root ] || echo "user" [root@linuxprobe ~]# su - linuxprobe [linuxprobe@linuxprobe ~]$ [ $USER = root ] || echo "user" user 除了基本的“与”、“或”逻辑符号外,还有逻辑“非”符号,在Linux系统中逻辑“非”的符号就是一个叹号,它代表把条件测试中的判断结果取相反值,也就是说原本测试的结果是正确,则变成错误,而错误的结果会变成正确,有一种负负为正的感觉。例如现在切换到一个普通用户的身份后再来判断当前用户是不是一个非超级管理员的用户,判断结果因为两次否定而变成正确,因此会正常的输出预设信息: [linuxprobe@linuxprobe ~]$ exit logout [root@linuxprobe root]# [ $USER != root ] || echo "administrator" administrator 写技术性教材一直有两种不同的方式,其一是让学员搞明白学懂技术,其二是让学员觉得自己搞明白学懂技术了,因此市面上很多书籍过于浅显使得让您读完后感觉进步速度特别快,这其实是作者有意为之,目的就是让您感觉自己完全掌握了其中奥妙,但步入运维工作后就会暴露出短板吃大亏,所以刘遄老师决定继续提高难度,给读者们增加一个综合运用的例子,也算作是对前面知识的一个小结,帮助大家能够在今后工作中更灵活的使用逻辑符号。 [root@linuxprobe ~]# [ $USER != root ] && echo "user" || echo "root" root 这个例子的执行顺序是先判断当前登录用户的环境变量名称是否不等于root,即判断他是否为普通用户,若条件成立则会根据逻辑“与”运算符输出user字样,而如果条件不满足则会通过逻辑“或”运算符输出root字样,即如果前面的&&不成立才会执行后面的||符号。 整数比较运算符是仅对数字的测试操作,不能把数字与字符串、文件等内容一起操作,而且不能想当然的使用日常生活中的等号、大于号、小于号等来做判断,因为等号与是赋值命令符冲突,大于号和小于号分别是和输出重定向命令符和输入重定向命令符冲突。虽然有时候碰巧也能执行成功,但是在后面脚本程序中普遍会产生错误,一定要使用规范的整数比较运算符来进行操作: 操作符 - 作用 -eq - 判断是否等于 -ne - 判断是否不等于 -gt - 判断是否大于 -lt - 判断是否小于 -le - 判断是否等于或小于 -ge - 判断是否大于或等于 咱们先小试牛刀的测试下10是否大于10以及10是否等于10,依次通过判断输出的返回值内容来进行判断: [root@linuxprobe ~]# [ 10 -gt 10 ] [root@linuxprobe ~]# echo $? 1 [root@linuxprobe ~]# [ 10 -eq 10 ] [root@linuxprobe ~]# echo $? 0 在前面第2章的第四小节中学习过一个叫做free的命令,它能够获取到当前系统正在使用及可用的内存量信息。接下来咱们先用free -m命令查看以兆为单位的内存使用量情况,然后通过grep Mem:命令对关键词匹配过滤出剩余内存量的行,再用awk '{print $4}'命令过滤只保留第三列,最后用FreeMem=`语句`的方式把语句内执行的结果赋值给变量,这个演示确实有些难度,但看懂后会觉得很有意思,写到笔记本上在运维工作时也会用得上。 [root@linuxprobe ~]# free -m total used free shared buffers cached Mem: 1826 1244 582 9 1 413 -/+ buffers/cache: 830 996 Swap: 2047 0 2047 [root@linuxprobe ~]# free -m | grep Mem: Mem: 1826 1244 582 9 [root@linuxprobe ~]# free -m | grep Mem: | awk '{print $4}' 582 [root@linuxprobe ~]# FreeMem=`free -m | grep Mem: | awk '{print $4}'` [root@linuxprobe ~]# echo $FreeMem 582 上面做的获取内存可用量的步骤有些难度“超纲”了,如果不能够马上理解也不用担心,接下来才是重点,需要通过整数运算符来判断内存可用量的值是否小于1024,若小于则会提示内存不足的字样: [root@linuxprobe ~]# [ $FreeMem -lt 1024 ] && echo "Insufficient Memory" Insufficient Memory 字符串比较是判断测试字符串是否为空值,或两个字符串是否相同的操作,常常用来判断某个变量是否未被定义(即内容为空值),理解起来也比较简单,常见的操作运算符如下: 操作符 - 作用 = - 比较字符串内容是否相同。 != - 比较字符串内容是否不同。 -z - 判断字符串内容是否为空。 咱们可以通过判断String变量是否为空值,进而判断是否未被定义: [root@linuxprobe ~]# [ -z $String ] [root@linuxprobe ~]# echo $? 0 最后再尝试把逻辑运算符引入来试试,当判断用于保存当前语系的环境变量值LANG不是为英语(en.US)则会满足逻辑条件并输出非英语的字样: [root@linuxprobe ~]# echo $LANG en_US.UTF-8 [root@linuxprobe ~]# [ $LANG != "en.US" ] && echo "Not en.US" Not en.US
4.3.流程控制语句 虽然此时已经能够通过使用第2章的Linux命令,第3章的管道符、重定向及前面小节的条件测试语句写出最基本的Shell脚本,但坦白来讲这种脚本暂时并不能适用于日常生产环境的工作,首先是它不能根据实际工作内容来调整具体的执行命令内容,也不能根据某些条件来实现自动循环执行,例如需要批量的创建一千个用户,首先您要能判断这些用户是否已经存在了,若不存在然后再通过循环语句让脚本自动化的依次创建他们。接下来咱们通过if、for、while、case四种条件、循环语句来学习相对更高难度级别的Shell脚本,另外为了保证下面实验内容实用与好玩,我会尽可能多的为您讲解各种不同功能的Shell脚本,而绝不是在某个脚本内容上单纯的修修补补加一些代码,这样虽然课是讲明白了,但是思路放不开,工作时候要吃亏的。
4.3.1.if条件测试语句 if条件语句可以让脚本根据实际情况的不同而自动切换命令执行方案,从技术角度上来说分为单分支结构、双分支结构、多分支结构,复杂度随着灵活度一起逐级上升。 单分支的if条件语句结构,这种结构仅用if、then、fi关键词组成,只在条件成立后才执行预设命令,相当于口语的“如果……那么……”,属于最简单的一种条件判断结构,操作语法如图4-17所示:
图4-17 单分支的if条件语句 使用单分支的if条件语句来判断某个目录是否存在,若已经存在就结束条件判断和整个Shell脚本,而如果不存在则去创建这个目录: [root@linuxprobe ~]# vim mkcdrom.sh #!/bin/bash DIR="/media/cdrom" if [ ! -e $DIR ] then mkdir -p $DIR fi 因为第5章才会去学习用户身份与权限,此时继续直接用“bash 脚本名称”的方式来执行脚本即可,顺利执行脚本文件后正常情况下是没有任何输出信息的,咱们可以使用ls命令验证下这个目录是否已经成功创建: [root@linuxprobe ~]# bash mkcdrom.sh [root@linuxprobe ~]# ls -d /media/cdrom /media/cdrom 双分支的if条件语句结构,这种结构仅用if、then、else、fi关键词组成,进行两次条件判断匹配,两次判断中任何一项匹配成功后都会执行预设命令,相当于口语的“如果……那么……或者……那么……”,也是属于很简单的一种条件判断结构,操作语法如图4-18所示:
图4-18 双分支的if条件语句 使用双分支的if条件语句来验证某个主机是否在线,然后根据判断执行返回值结果分别给予对方主机是在线还是不在线的提示信息。脚本中我主要是使用ping命令来测试与对方主机的网络联通性,而linux系统中的ping命令不像windows系统一样仅会尝试四次就结束,因此为了避免用户等待时间过长,而通过-c参数来规定尝试的次数,-i参数定义每个数据包的发送间隔时间以及-W参数定义最长的等待超时时间。 [root@linuxprobe ~]# vim chkhost.sh #!/bin/bash ping -c 3 -i 0.2 -W 3 $1 &> /dev/null if [ $? -eq 0 ] then echo "Host $1 is On-line." else echo "Host $1 is Off-line." fi 咱们在4.2.3小节中用过$?变量,作用是显示上一次命令的执行返回结果,若上一条语句是顺利执行成功的则会返回数字0,而若上一条语句执行是失败的则返回一个非零的数字(随系统版本差异可能会是1或者2都有可能),因此可以通过用数字条件测试的方法判断$?变量是否等于零来获知上一条语句的最终判断情况,192.168.10.10是服务器本机地址,验证下脚本的效果吧: [root@linuxprobe ~]# bash chkhost.sh 192.168.10.10 Host 192.168.10.10 is On-line. [root@linuxprobe ~]# bash chkhost.sh 192.168.10.20 Host 192.168.10.20 is Off-line. 多分支的if条件语句结构,这种结构需要使用if、then、else、elif、fi关键词组成,进行多次条件判断匹配,多次判断中任何一项匹配成功后都会执行预设命令,相当于口语的“如果……那么……如果……那么……N次等等”,这是一种工作中最常使用的条件判断结构,虽然相对复杂但更加灵活,操作语法如图4-19所示:
图 4-19 多分支的if条件语句 使用多分支的if条件语句来判断用户输入的分数在那个成绩区间内,然后输出如优秀、合格、不合格等提示信息。read是用来读取用户输入信息的命令,它能够把接收到的用户输入信息赋值给后面的指定变量,而-p参数则是给予了用户一定的提示信息。下面实例中判断用户输入的分数是否同时具备大于等于85分且小于等于100分,这样的话才输出Excellent字样,若上一条件没有匹配成功则继续判断用户输入分数是否大于等于70分且小于等于84分,这样的话输出Pass字样,如果两次都落空没有匹配成功,则最终输出Fail字样: [root@linuxprobe ~]# vim chkscore.sh #!/bin/bash read -p "Enter your score(0-100):" GRADE if [ $GRADE -ge 85 ] && [ $GRADE -le 100 ] ; then echo "$GRADE is Excellent" elif [ $GRADE -ge 70 ] && [ $GRADE -le 84 ] ; then echo "$GRADE is Pass" else echo "$GRADE is Fail" fi [root@linuxprobe ~]# bash chkscore.sh Enter your score(0-100):88 88 is Excellent [root@linuxprobe ~]# bash chkscore.sh Enter your score(0-100):80 80 is Pass 如果用户输入的分数并没有满足第一项匹配条件,则会自动进行下面的匹配流程: [root@linuxprobe ~]# bash chkscore.sh Enter your score(0-100):30 30 is Fail [root@linuxprobe ~]# bash chkscore.sh Enter your score(0-100):200 200 is Fail 您会不会很奇怪,为什么我输入200分的超高成绩却依然提示了Fail字样?其实原因是很简单明显的,咱们的条件判断语句两条都没有匹配成功,因此自动执行了最终的兜底策略。也就是说这个脚本其实还不是很完美,您来动手把所有大于100分或小于0分的用户输入都设置提示下Error字样吧,加油! 4.3.2 for条件循环语句 for循环语句可以让脚本一次性读取多个信息值,然后逐一对信息值进行循环操作处理,因此当您要处理的数据是有目标和范围时简直再适合不过了~for循环语句处理流程如图4-20所示。例如使用for循环语句来从列表文件中读取多个用户名,然后逐一创建用户帐号并为其设置密码。
图4-20 for循环语句处理流程图 首先创建用户名称的列表文件,把每个用户名称单独占一行,当然具体的用户名称和个数都是可以由同学们来决定的: [root@linuxprobe ~]# vim users.txt andy barry carl duke eric george Shell脚本中使用read命令来读取用户输入的密码值后赋值给PASSWD变量,并通过-p参数来显示一段给用户的提示内容,告诉用户正在输入的内容即将作为帐号密码。当下面的脚本执行后会自动的用users.txt列表文件中获取到所有的用户名称值,然后逐一使用id 用户名的方式查看用户的信息,并使用$?变量判断这条命令是否执行成功,也就是判断该用户是否已经存在。而/dev/null是被称作Linux的黑洞的文件,把输出信息重定向到这个文件后等同于删除数据(没有回收功能的垃圾箱),让用户的屏幕窗口保持简洁。 [root@linuxprobe ~]# vim Example.sh #!/bin/bash read -p "Enter The Users Password : " PASSWD for UNAME in `cat users.txt` do id $UNAME &> /dev/null if [ $? -eq 0 ] then echo "Already exists" else useradd $UNAME &> /dev/null echo "$PASSWD" | passwd --stdin $UNAME &> /dev/null if [ $? -eq 0 ] then echo "$UNAME , Create success" else echo "$UNAME , Create failure" fi fi done 执行批量创建用户的Shell脚本程序,在输入为帐户设定的密码口令后将由脚本全自动的检查并创建这些帐号,因为已经把多余的信息通过输出重定向转移到了黑洞文件中,因此屏幕窗口除了用户创建成功的提示后不会有其他的内容。/etc/passwd是用来保存Linux系统中用户帐号信息的文件,因此如果不放心的话可以手动的再看下这个文件中有无这些新用户的信息,同时这也又回归到了前面3章中重复提到了一个概念——Linux系统中的一切都是文件。 [root@linuxprobe ~]# bash Example.sh Enter The Users Password : linuxprobe andy , Create success barry , Create success carl , Create success duke , Create success eric , Create success george , Create success [root@linuxprobe ~]# tail -6 /etc/passwd andy:x:1001:1001::/home/andy:/bin/bash barry:x:1002:1002::/home/barry:/bin/bash carl:x:1003:1003::/home/carl:/bin/bash duke:x:1004:1004::/home/duke:/bin/bash eric:x:1005:1005::/home/eric:/bin/bash george:x:1006:1006::/home/george:/bin/bash 您还记得刚刚在双分支if条件语句学习中那个用于测试对方主机是否在线的脚本吗?既然现在已经掌握了for循环语句,不妨做些更酷的事情,比如来试试让脚本自动的从文本中读取主机列表,然后进行自动逐个测试对方是否在线吧~首先创建出一个主机的列表文件: [root@linuxprobe ~]# vim ipadds.txt 192.168.10.10 192.168.10.11 192.168.10.12 然后通过把前面双分支if条件语句与for循环语句做结合,让脚本自动的从上面主机列表文件中读取IP地址并赋值给HLIST变量,从而通过判断ping命令执行后的返回值来进行逐个的测试对方主机是否在线。其中出现的$(命令)是一种完全类似于第3章转义符中反引号`命令`的Shell操作符,效果同样是执行括号或双引号内的命令,这样平时同学们写脚本的时候会多学习几种类似的新方法,便于在工作中大显身手: [root@linuxprobe ~]# vim CheckHosts.sh #!/bin/bash HLIST=$(cat ~/ipadds.txt) for IP in $HLIST do ping -c 3 -i 0.2 -W 3 $IP &> /dev/null if [ $? -eq 0 ] ; then echo "Host $IP is On-line." else echo "Host $IP is Off-line." fi done [root@linuxprobe ~]# ./CheckHosts.sh Host 192.168.10.10 is On-line. Host 192.168.10.11 is Off-line. Host 192.168.10.12 is Off-line.
4.3.3.while条件循环语句 这是一种让脚本根据某些条件来重复执行命令的条件循环语句,而这种循环结构往往在执行前并不确定最终执行的次数,完全不同于for循环语句中有目的、有范围的使用场景。而while循环语句判断是否继续执行命令的依据一般是检查若条件为真就继续执行,而条件为假就结束循环,循环结构如图4-21所示:
图4-21 while条件循环语句的结构 接下来就来利用多重分支的if条件测试语句与while条件循环语句来结合写一个用来判断数值的脚本吧,脚本中会使用$RANDOM变量来调取出一个随机的数值(范围:0--32767),然后通过expr命令计算取整出1000以内的一个随机数值,用这个数值来跟用户通过read命令输入的数值做比较判断。判断语句结构分为三项,分别是判断是否相等、是否大于随机值以及是否小于随机值,但这不是重点~关键是在于while条件循环语句的判断值为true,因此会无限的运行下去,直到猜中后运行exit 0命令才终止脚本。 [root@linuxprobe ~]# vim Guess.sh #!/bin/bash PRICE=$(expr $RANDOM % 1000) TIMES=0 echo "商品实际价格为0-999之间,猜猜看是多少?" while true do read -p "请输入您猜测的价格数目:" INT let TIMES++ if [ $INT -eq $PRICE ] ; then echo "恭喜您答对了,实际价格是 $PRICE" echo "您总共猜测了 $TIMES 次" exit 0 elif [ $INT -gt $PRICE ] ; then echo "太高了!" else echo "太低了!" fi done 通过给脚本加上解释说明后整个内容开始变得丰满起来,互动感也变得很强,每当循环到let TIMES++这个命令时都会让TIMES变量内数值加上1,这样用来统计总共循环次数的功能更是画龙点睛,让操作者可以知道猜对价格最终使用了几次机会。 [root@linuxprobe ~]# bash Guess.sh 商品实际价格为0-999之间,猜猜看是多少? 请输入您猜测的价格数目:500 太低了! 请输入您猜测的价格数目:800 太高了! 请输入您猜测的价格数目:650 太低了! 请输入您猜测的价格数目:720 太高了! 请输入您猜测的价格数目:690 太低了! 请输入您猜测的价格数目:700 太高了! 请输入您猜测的价格数目:695 太高了! 请输入您猜测的价格数目:692 太高了! 请输入您猜测的价格数目:691 恭喜您答对了,实际价格是 691 您总共猜测了 9 次
4.3.4.case条件测试语句 如果您学习过C语言,此刻一定是会心一笑,这不就是switch语句吗?是的,功能非常相似!case条件测试语句是在多个范围内匹配数据,若匹配到则执行相关命令并结束整个条件测试,而如果数据不在所列出的范围内,则会去执行*)中所规定的默认命令,测试结构如图4-22所示:
图4-22 case条件语句的测试结构 刚刚学习的脚本普遍有一个致命的弱点,不信您就输入一个字母或乱码试一试~脚本立即就崩溃了。这是由于字母是不能跟数字做大小比较的,例如a是否大于等于3,这样的命题完全错误,变量操作会直接导致系统崩溃。咱们必须马上想出一个办法来判断用户的输入内容,一旦碰到字母或乱码也能予以提示,不至于因错误输入而崩溃,因此这样的需求用case条件测试语句和第3章节中学习的通配符来一起组合写一个脚本简直再适合不过了~提示用户输入一个字符并将其赋值给变量KEY,判断变量KEY为何种字符后分别输出是字母、数字还是其他字符: [root@linuxprobe ~]# vim Checkkeys.sh #!/bin/bash read -p "请输入一个字符,并按Enter键确认:" KEY case "$KEY" in [a-z]|[A-Z]) echo "您输入的是 字母。" ;; [0-9]) echo "您输入的是 数字。" ;; *) echo "您输入的是 空格、功能键或其他控制字符。" esac [root@linuxprobe ~]# bash Checkkeys.sh 请输入一个字符,并按Enter键确认:6 您输入的是 数字。 [root@linuxprobe ~]# bash Checkkeys.sh 请输入一个字符,并按Enter键确认:p 您输入的是 字母。 [root@linuxprobe ~]# bash Checkkeys.sh 请输入一个字符,并按Enter键确认:^[[15~ 您输入的是 空格、功能键或其他控制字符。
4.4.计划任务服务程序 厉害的系统运维工程师能够让Linux系统实现自动化工作,无需人工的干预就可以让各个服务、命令在指定的时间段执行、服务或停止。更何况咱们已经有了如此彪悍的脚本程序,如果只是需要每天凌晨两点敲一下回车去执行这个Shell脚本程序,这样的工作简直就是痛苦死了(或者训练您家的小猫半夜按下回车键)。刘遄老师接下来教给同学们如何来设置服务器的计划任务服务,把有周期规律性的工作交给系统去自动完成。计划任务有“一次性”与“长期性”的区分,可以理解为: 一次性计划任务:今晚11点30分开启网站服务(例如新网站的公测) 长期性计划任务:每周一的凌晨3点25分把/home/wwwroot目录打包备份为backup.tar.gz 一次性计划任务顾名思义就只是一次性有效,一般用于临时的工作需要,咱们可以用at命令实现这种功能,只需要写成"at 时间"的形式就可以,如果想要查看已设置好但还未执行的计划任务可以用"at -l"命令,而删除的话可以用"atrm 任务序号"即可,默认用at命令来设置计划任务的话是通过交互式的方法,例如设置系统在今晚23:30分自动重启网站服务吧: [root@linuxprobe ~]# at 23:30 at > systemctl restart httpd at > 此处请同时按下Ctrl+d来结束编写计划任务 job 3 at Mon Apr 27 23:30:00 2015 [root@linuxprobe ~]# at -l 3 Mon Apr 27 23:30:00 2016 a root 当然如果您想挑战一下更加高难度但又简便的方式,可以把前面章节中学习的管道符(任意门)放到两条命令之间,让at命令接收前面echo命令的输出信息,以达到通过非交互式的方式创建计划任务,这样的话在Shell脚本中都可以做引用了。 [root@linuxprobe ~]# echo "systemctl restart httpd" | at 23:30 job 4 at Mon Apr 27 23:30:00 2015 [root@linuxprobe ~]# at -l 3 Mon Apr 27 23:30:00 2016 a root 4 Mon Apr 27 23:30:00 2016 a root 如果一不不小心设置了两条,想要删除其中一条的话也是很简单的: [root@linuxprobe ~]# atrm 3 [root@linuxprobe ~]# at -l 4 Mon Apr 27 23:30:00 2016 a root 而如果您的工作需要是有周期规律性的,那Linux系统中默认启用的crond服务简直再适合不过了,创建、编辑计划任务的命令为"crontab -e",查看当前计划任务的命令为"crontab -l",删除某条计划任务的命令为"crontab -r",另外如果您登录的是超级用户的话,还可以通过加上-u参数来编辑其他人的计划任务。不过在正式的部署计划任务前,请先跟刘遄老师念一下口诀“分、时、日、月、星期 命令”,这是使用crond服务设置任务的参数格式,没有设置的位置也要用*号占位,如图4-23所示:
图4-23 Crond计划任务配置文件的参数格式 字段 - 说明 分钟 - 取值为从0到59之间的整数 小时 - 取值为从0到23之间的任意整数 日期 - 取值为1到31之间的任意整数 月份 - 取值为1到12之间的任意整数 星期 - 取值为0到7之间的任意整数,其中0与7均为星期日 命令 - 要执行的命令或程序脚本 参考上面crond计划任务实现的参数格式,假设目前每周一、三、五的凌晨3点25分都需要把某个网站数据目录通过tar命令打包成一个压缩包来当做备份文件,那么除了用减号(-)来表示一段连续的时间周期,例如12-15就可以代表每月的12-15日,除号(/)代表任务的间隔时间,例如/2就是每隔2分钟执行一次的意思,还可以用逗号(,)来分别表示多个时间段,如8,9,12来代表八月、九月和12月执行任务,那么来尝试做一下吧: [root@linuxprobe ~]# crontab -e no crontab for root - using an empty one crontab: installing new crontab [root@linuxprobe ~]# crontab -l 25 3 * * 1,3,5 /usr/bin/tar -czvf backup.tar.gz /home/wwwroot 而如果计算机服务器中需要有多条计划任务,并且这次的工作是每周一至五的每天的凌晨1点钟清空/tmp临时目录中的所有文件,就可以把相应的参数依次写到下面。而在crond服务的计划任务参数中所有的命令请一定要用绝对路径的方式来写,如果不知道的话用whereis命令来查下吧: [root@linuxprobe ~]# whereis rm rm: /usr/bin/rm /usr/share/man/man1/rm.1.gz /usr/share/man/man1p/rm.1p.gz [root@linuxprobe ~]# crontab -e crontab: installing new crontab [root@linuxprobe ~]# crontab -l 25 3 * * 1,3,5 /usr/bin/tar -czvf backup.tar.gz /home/wwwroot 0 1 * * 1-5 /usr/bin/rm -rf /tmp/* 在本章节最后,刘遄老师再对同学们今后工作中使用计划服务来嘱咐几句建议,首先是crond服务的配置参数中可以像Shell脚本一样以#号开头来写上注释信息,这样方便以后再看到这段命令代码时快速回忆起功能、需求以及当时编写人等等重要信息,其次是计划任务中的分钟项目必须有数值,绝对不能为空或是*号,而日和周不能同时使用,否则就会发生冲突,这些细节我想您一定要记下,以免工作和考试时出现。最后再啰嗦一下,我想您也发现了,例如crond、git等很多的服务默认都是调用的vim编辑器,因此我相信您也能够看出linux系统中掌握vim文本编辑器的大大好处吧,所以一定要仔细的复习好再进行学习下一章,不能有一点偷懒哦。
本章节的复习作业 1:Vim编辑器的三种模式分别叫做什么名字? 答案:命令模式、末行模式与输入模式(也叫编辑模式或插入模式)。 2:从输入模式切换到末行模式的操作是? 答案:需要先敲击[ESC]健退回到命令模式后敲击冒号":"后进入末行模式。 3:一个完整的Shell脚本应该包括什么? 答案:一个完整的Shell脚本应该包括“脚本声明”、“注释信息”和“可执行语句”。 4:分别解释Shell脚本中$0与$3变量的作用。 答案:Shell脚本中的$0代表该脚本文件的名称而$3则代表该程序接收的第三个参数。 5:回忆下if条件测试语句中分别有几种结构,最为灵活且复杂的是那种结构? 答案:包括有单分支、双分支与多分支,其中多分支是最为灵活且复杂的结构,结构为if……then……elif……then……else……fi。 6:for条件循环语句的循环结构是什么样子的? 答案:for条件循环语句的结构为“for 变量 in 取值列表 do …… done”。 7:若在while条件循环语句中使用true作为循环条件,那么会发生什么事情? 答案:因条件测试值永久为true则会导致脚本中循环部分无限的重复执行下去,直到碰到exit退出命令才会结束。 8:如果需要依据用户的输入参数执行不同的操作,最方便的条件测试语句是? 答案:case条件语句最方便。 9:Linux系统的长期计划任务所使用的服务是什么?参数格式有什么规律? 答案:长期计划任务需要使用crond服务程序,参数格式是“分、时、日、月、星期 命令”。 |