我们在第一次作为运维人员去学习和写shell脚本的时候,我们经常会做什么,我们把我们在 Linux上面去执行的那些命令,我们一股脑的全部把它丢到shell脚本里面来。
举个例子就是说我们要做什么,我们先用 Linux 系统上面,先在 Linux 系统上面先演示,我们先到宿主机目录上面,我们有个test目录,我们先把它删掉,
我们再看是不是没有 test目录了?
假设我们登录到这个系统上面,我们需要做一系列的步骤,比如说我们第一件事情是什么叫mkdir test ,
我们先新建这样的一个目录,然后我们再进入到 test目录里面去对不对?
然后接着我们 ls 一下,这里什么都没有,
最后的话我们再 touch 一个比如说test点txt文件,我们新建的这样一个文件,这个时候我们来看里面有一个test点txt文件,然后我们输入一个什么hello word是吧?我们输入这样的内容把它导入到 text点txt文本里面,接着的话我们再 cat 一下 test点txt,
假设我们收工了,我们要退出了之后,这个目录还在,我们还要做一个什么事情,我们要把它 rm -rf test/ ,我们把它删掉。
假设我们要在每台节点上面都去这样子操作一下,七八个步骤,做起来还是至少要个九秒八秒五秒对吧?
假设我们现在就要写脚本,在脚本里面能够实现这种自动化操作,我们直接运行的脚本就把我们刚才的事情都做了,我们怎么写?
我们首先第一步就是#!/bin/bash
,默认写在里面,
第一步我们做什么?我们要先跳到宿主机目录上面来,对不对?
我们用 cd ~
,这个~
表示宿主机目录,如果是对于 root 用户的话,它的宿主目录是什么? 是/root
目录。
如果是其他普通用户,波浪线~
表示的是什么?表示的是/home/用户名
,
~
它其实是有两种表示含义,如果说你是root的话,波浪线~
表示的是/root
目录这个路径,
如果是其他普通用户的话,那么这个波浪线~
表示就是/home/用户名
。
我们先进到宿主目录之后,然后我们是不是mkdir test
创建了一个test目录是吧?
接着的话我们是不是要进入到 text目录里面去,cd test
接着我们做了一个什么事情,我们是不是创建了一个test点txt文件,touch test.txt
然后我们输出重定向,我们再往里面写了一行 hello,world,到text文本里面去,echo "htllo,world" >> test.txt
然后接着我们做了一个什么事情,cat test.txt
,
之后做什么,我们再跳出来,跳到上一级目录,cd ..
接着我们就 rm -rf test
,把整个目录移除/删掉,
这是我们刚刚做的动作,我们把所有的动作命令,我们所有执行的操作我们全部放到这来。
接着的话我们希望到另外一个节点,我们做同样的事情的时候,我们就只需要执行一下这个脚本,
我们看一下我们这个代码有没有传过来,代码已经全部传过来了,
接着的话我们来做一个什么事情,我们要执行一下,
chmod +x second.sh
权限也一样,然后./second.sh
执行脚本它输出hello,world, hello,world 是哪里输出来的?cat test.txt
就输出了,
但是整个过程我们其实没有感知到,我们看到这下面并没有 test的目录创建,
相当于我们执行这一项,可能不到一秒钟我们就执行了刚才这么多的步骤,
经常的我们到一个系统上面去部署一个服务的时候,步骤都是一样的,
比如我们先把原来的服务先停掉,然后把原来的整个目录备份,备份了之后,然后把我们的新包拷到里面去,拷到某个目录里面,然后解压,
然后执行里面的启动脚本,这样子就可以了。
我们这个步骤其实相当于就是把运维人员里面写的那些脚本一步一步写到这里面来,这就是一个命令堆积的过程。
但是这样的一个脚本会不会有问题,
举个例子,比如说我们这里是不是两次出现了text,
我们假设我们要换一个目录,我们要换一个text1,我们创建一个tes,1目录的话,我们这里是不是要改一下,这里要改一下,还有这里再改一下,我们要改三个地方,
我们如果有编程经验的人的话,我们就知道我们说把这种共同的东西,我们抽象出来,我们写一个变量,比如部署任务叫DEPLOY_DIR=test
,
我们就叫做test,假设我们这里创建的时候,这个时候我们创建就用变量名用$DEPLOY_DIR
去代替,使用变量的时候一定要在前面加上多乐符$
,
然后比如说我们 cd 也用变量名$DEPLOY_DIR
代替,然后最后的rm -rf
也把它用$DEPLOY_DIR
代替。
这样子的话,假设我要创建一个test2,我们只要改这里就可以了,这是第一点,我们把它这些东西变成了变量。
创建的文件,假设我们叫 USE_FILE=text.txt
,
我们把我们接下来会用到的创建的文件把它改一下,导入到$USE_FILE
里面来,
然后我cat也是这样 cat $USE_FILE
,
我们就是用变量实现了一个改造,优化了一下脚本,这样的话如果说脚本想要比如说我要创建换一个新的目录,或者说我导入到不同的文件的时候,
我们只需要改一处地方就可以了。这样的话整个脚本稍微会好一点,比如说免得你变量相同的值到处写,假设如果说是另外一个人维护的时候,我要改一下,可能他只改了一处地方,你还有其他几处地方没有改的话,脚本执行就会有问题,
我们脚本尽量的把一些经常用的地方我们要抽成变量,其他编程语言它都要做的一个事情,就是不要让一个特殊的值在整个代码的文件里面到处跑。
好,这样子的话,我们接下来这个代码还有没有需要改造的,当然肯定是有。
首先我们这样一点我们要注意一下,首先第一个是我们创建 test目录,你说有没有可能会失败?
假如失败了之后,假设我们说原来我们有 test目录对不对?我们有 text目录,我们这里创建失败了,你就要记住一点,这个脚本你即使命令执行失败了,它的脚本默认会继续执行下去了。假设这是别人的目录,我要创建一个目录的时候,我发现创建了别人的目录,别人的目录它存在导致我们命令失败了,但是我们这个命令并不会停下来,它会往下走走走到这里:
它会把别人的整个目录全部给删掉,这样子就会带来一个什么问题。我把别人的东西给删掉了,而且是rm -rf,这个命令也要在生产环境里面一定要注意,所谓网上调侃从删库跑路,就是说你用的这个rm -rf / ,/ 就是根目录。你把从根目录开始下的所有的文件,假设你有宿主权限的话,就一口气全部给它删干净了,好家伙,这台机器基本上就废掉了,所以这个时候的话不是你被公司开除,就是公司要把你给抓到什么监狱里面去,
所以说这个命令很危险,而且你这样子假设即使你这里用了 test是吧,删的是别人的目录,那也可能会造成一定很大的一个影响,对不对?假设我就说我把这里改成根目录或者改成 etc:
这些都是重要配置文件,你删了之后,你再重启一下机器的时候,你就发现它起不起来了,也就是说 mkdir创建失败,它其实会造成一个什么?就是说失败了的话,我希望ta能够不要执行下去。shell里面会有这样的一个操作,就是 set -e,set +e表示的是命令遇到错误会继续执行,遇到错误会继续执行,set -e表示的就是遇到错误停止运行。
我们可以试一下,
比如说我们删除这个目录,我们只是打印提示信息说一下,并不真正的做删除目录。
如果是第一次执行了之后,这个目录还是存在的。接着我们再次执行第二次的时候,我们注释一下,
好,我们来跑一下脚本,
这个时候它输出了 hello world,同时删除这个目录,但是这个目录是没有被删除的,我们先看一下:
有test目录对不对?它存在。
接着的话我们再次执行:
大家看到无法创建目录,文件已经存在,我输出了两个hello world,是因为原来已经追加的一个hello world在里面,然后又来一个hello world,但是我又执行了删除目录,也就是说我们没有默认的情况下,命令报错了,它会继续往下执行对不对?
假设我们把第3行注释取消,我们再看一下,
我们再次执行的时候,我们可以看到:
mkdir 这里命令报错了之后,后面的cat什么删除这些提示信息【删除test目录】都没有被执行了,也就是说我们可以通过这样的一个方式去控制命令,就是遇到错误之后要不要继续执行,我们可以简单的这样设置一下,这是shell里面的一个规范规则。
接着的话我们还可以做一个什么事情?
比如说我们的 test或者文件,我们都可以从外面的去传,
代码:
解读代码:
假设我们还可以做一个什么事情,我们的目录我们可以通过脚本自己传入,多了符号$
表示从脚本外面传的参数,
假设我们是 -eq 1
,
然后 我们用一下 if,后面我们会专门去介绍这个if语句的判断语句怎么用,就是说我们脚本如果是传入了一个,
然后我们先给一个默认值,【第5行】
如果说我们的参数传来一个值,就先把第一个参数当做是要创建的部署的目录,
假设我们传入了脚本,
假设第一个参数是xx,我们传了一个xxx的时候,它表示了一个位置参数,它就在多了一的位置上面,如果有的话,我们就把 xxx 这个值赋给这个变量$1
,【第9行】
假设我们这里还有一个yyy的话,这就是第二个参数。
如果说是它的参数的个数等于2的话,我们就会把第二个参数传给 $2
,即USER_FILE变量,【第14行】
我们用 echo 输出提示信息【第24行】
也就是说我们这里是加了两个if,可以通过外部的参数来控制这两个变量的值。
大概的逻辑就这些。