一. 关于shell

    shell,英文是壳,外壳的意思,至于在计算机中,同样有这样的一层意思,也就是可以将shell看做是计算机系统封装的一层外壳,来供用户使用,因此,用户可以通过操纵shell也就是输入一系列命令来达到各种需要的目的,那么shell也可以被称为命令解释器

    在shell下,用户可以键入一行命令而shell解释一行来使其依次执行不同的任务,这种方式称为交互式,如果在执行命令比较少的情况下交互式还是方便的,而且还使这些命令透明,用户能够分辨出因果关系;

    但是如果想要执行的命令是成批的,并不需要知道过程只是想要知道结果,或者重复度很高,那么一行一行的敲入shell一行一行的解释无疑是很费时费力的,因此就可以使用另外一种处理方式就是批处理,也就是可以编写一个shell脚本,shell脚本和编程语言有些相似,同样都有变量和流程控制语句,但shell脚本是解释行的,也就是shell会将脚本中的命令一行一行的拿出来,相当于用户一行一行地键入,但是和交互式相比较起来就省事多了。



二. shell执行原理

    由于历史原因,shell的种类有很多,比如bash、sh、cah、ksh、tcsh等,可以在当前路径/etc/shells下面查看系统中所有已知(但不一定安装)的shell:

wKiom1ddTTWwFbHJAAAF5t7vcyI835.png


   对于当前的一个shell比如bash来说,当使用交互式键入一条命令的时候,它并不会自身去亲自执行一条命令,因为有可能这条命令是一条恶意的命令会侵犯当前进程,所以它会创建出一个子进程,让子进程去执行这个shell命令,但是对于shell脚本来说,当创建好一个shell脚本运行起来的时候,相当于是一条命令,这时候bash会创建一个子进程来运行shell脚本,而这个子进程同样也是一个bash,因此,当其去执行shell脚本中一条条的命令的时候,也就是相当于再会去创建出一个子进程来执行shell脚本中的一条条命令,可画图如下:


wKiom1ddUdfg1rTZAAALXsJk0iI664.png因此,在执行shell脚本中的多个命令的时候,也就需要创建出多个进程来一一执行,尽管这样,但仍然比交互式的要简便的多;



三. shell脚本

  • 运行方式

    在shell脚本的编写中,'#'表示注释,相当于C语言中的‘//’,但是只有在第一行例外,在shell脚本中,第一行必须声明要使用的命令解释器,形式如下:

#!/bin/bash
echo "this is a shell test"

'#'的使用,在第一行并不表示注释,它表示该脚本使用后面的解释器/bin/bash解释执行;而'#!'被称为shebang;


chmod u+x shell脚本

首先,因为shell脚本并不需要经过编译,但是对于刚创建出来的shell脚本文件,一开始时并没有执行权限的,因此,可以将shell所属者的权限加上可执行,然后运行shell脚本:

wKioL1ddXHPD_g7EAAAOl4Di72I118.png


命令解释器 shell脚本

要运行shell脚本,除了在脚本的编写中需要制定使用的命令解释器外,在运行起来的时候,仍然可以指定需要哪一个命令解释器来进行解释执行:

wKioL1ddXCGDTq34AAAFl65HWos727.png


除了指定/bin/bash之外,还可以指定在当前系统中已经安装好的任何一个命令解释器;当然,除去了'/bin/'只使用一个bash来进行解释仍然是可行的,理所应当,其他解释器sh等也是一样:

wKioL1ddXcDDr5sOAAAIqaHtg0c521.png


  • shell变量

    按照惯例,shell变量由全大写字母加下划线组成,有两种类型的shell变量:

环境变量:

    在进程中提到,子进程会从父进程那里继承来环境变量,因此,对于执行shell脚本和脚本中的命令而创建出的子进程会继承父进程的shell环境变量;

用printenv命令可以显示当前shell进程的环境变量:

wKioL1ddadTz-q9hAAB1hMstdWY448.png


本地变量:

    环境变量是所有进程都有的概念,而本地变量是shell特有的概念;本地变量只存在于当前shell进程,用set命令可以显示当前shell进程中定义的所有变量(包括环境变量和本地变量)和函数;

在shell中定义一个变量格式如下:

VALNAME=value

等号的两边是不能有空格的,因为如果用空格隔开,VALNAME就会被解释成一条命令,后面就会被解释为命令的参数;

一个变量被定以后仅存在于当前shell进程,而用export可以将其导出到环境变量中,而使用unset可以将删除:

wKioL1deWLmhEM8fAAB3ZshJrj0917.jpg


和语言中定义变量不同的是,在shell中定义变量可以直接赋值定义而不需要指明变量的类型,比如无论是字符串、整型还是浮点型,都一律可用 VALNAME=value 来进行定义;事实上,在shell中,所有变量都是字符串,比如定义 VAL=15,VAL实际是一个字符串“15”而不是一个整数;在shell中如果对一个没有定义的变量取值,则为空字符串:

#!/bin/bash
echo "this is a shell test"

VAL1=22       //定义变量VAL1值为22
VAL2=11       //定义变量VAL2值为11
RES=0

//$(())表示将变量的值取出变成整数,只支持+-*/和()运算符,且只支持整数运算
let RES=$((VAL1+VAL2))

echo $RES     //符号 $ 表示取出变量的内容
echo ${VAL3}  //同样可以使用未定义的变量,只是内容是一个空字符串

运行结果:

wKioL1demDzQqaZgAAAGnXRoMps008.png


上面程序示例中,取出一个变量的内容可以使用$VALNAME后面直接跟上变量名,也可以使用${VALNAME},但是加上花括号不容易引起歧义,比如:

//本意是想在VAL1的内容后面追加上abc
echo $VAL1abc   //将VAL1abc看做一个变量
echo ${VAL1}abc  //取出VAL1的值后面再跟上abc


  • 反引号 `` 和$()

大体上来说,反引号``和$()的相同之处是都可用于命令替换,就是将括起来的命令执行完毕后再交给相应的对象或者输出:

#!/bin/bash

echo `pwd`
echo $(pwd)

运行脚本:

wKiom1denn7R9MM5AAAHqZJ4Vik783.png

这里要注意区分$()和$(()),前者的内容只能是命令,后者用于算术运算;


但是反引号和$()是有区别的,如下:

 VAL=10 
 echo `echo \$VAL`
 echo $(echo \$VAL)

在C语言中'\'是转义字符,用于除去后面的字符的特殊意义转而取其字面意思,因此,上面的代码中期望输出的是“$VAL”,但是运行程序会发现:

wKiom1deqEXS1wzaAAAHRkEYa1Y963.png

反引号输出的是取出了变量的内容,并没有将后面的'$'转义成字面值,而$()中'\'将'$'转义成字面值不再取出变量的内容,输出了“$VAL”达到了预期值;


而如果将上面的两句代码中改为:

VAL=10 
echo `echo \\$VAL`
echo $(echo \$VAL)

运行脚本:

wKioL1deqfzj7BJlAAAHSOQe3Sw793.png

因此分析结果:

虽然同是命令替换,但是反引号中可以看出'\\'相当于一个转义字符'\',而$()是正常一个转义字符'\'转义出后面的字符表示其字面意思;


  • eval命令

eval命令用于将其后跟着的参数命令内容进行必要的替换,然后再执行命令,也就是说,eval会对命令行进行两次扫描,第一次将其重新替换,第二次才真正执行命令;

对于普通的简单的命令来说是看不到什么区别的:

 #!/bin/bash
 echo "this is a shell test"
 echo "hello world"
 eval echo "hello world"

运行脚本:

wKioL1der53zCUUFAAAHuZ1-RWA301.png


但是对于如下程序:

#!/bin/bash
echo "this is a shell test"

VAL=100
STR="echo \$VAL"
echo ${STR}
eval echo ${STR}

运行程序:

wKiom1detPDz5DC6AAAH-Fru14s480.png

直接echo,会将变量的内容直接输出,其中STR字符串的内容中将'$'符号进行了转义,表示直接echo并不会取出VAL的值;

但是使用eval命令,首先会将STR的内容进行转义,之后再运行eval后面的命令行,这时候就会将VAL的值按照‘$’符号给提取出来输出,因此,eval命令的作用就可以看做是执行那些一次性并不能完成的,而是需要进行进一步的替换转置之后才能得出正确结论的命令;


  • 单中括号[ ]和双中括号` `

单中括号[]:

1. [ 用于条件测试,它并不是一个符号而是一个命令,用于判断后面条件的真假,并设置相应的退出码;和在C语言中的判断成立条件不同的是,使用[进行条件判断,如果为真则退出码为0,如果为假则退出码为1

#!/bin/bash
echo "this is a shell test"
read str 
//比较字符串时,强烈建议在左右两边都加上相同的字符,以确保当输入为空的时候仍能够进行比较
[ "X$str" == "Xred" ]
echo $?
[ "X$str" == "Xyellow" ]
echo $?
[ "X$str" == "Xblue" ]
echo $?
[ "X$str" == "Xpink" ]
echo $?


运行程序:

wKioL1dewYrx9-0OAAAHpW8R0XM291.png

匹配条件成立退出码为0,不匹配则为1;

而对于整数的比较,需要使用-gt(大于)、-lt(小于)、-eq(等于)、-ge(大于等于)、-le(小于等于)来进行比较;

2. [ ]还有另外的一个用途就是作为正则表达式的一部分,描述一个字符的匹配范围;


双中括号` `:

` `同样可用于条件判断,但可以说是[ ]的加强版,因为在其内部的条件判断语句支持‘&&’、‘||’、‘>’和‘<’等C语言符号,而如果在[ ]的内部想要进行与、或的判断,就需要用到-a(与)、-o(或)来进行连接;

如下栗子:

#!/bin/bash
echo "this is a shell test"

read score//输入分数
if [ $score -ge 90 -a $score -le 100 ];then  //成绩在90到100之间为优秀
    echo "You are excellent"
elif [ $score -lt 90 ] && [ $score -ge 80 ];then //成绩在80到89之间为良好 
    echo "You are great"
elif [[ $score < 80 && $score > 59 ]];then   //成绩在60到79之间为及格
    echo "You are good"
else                                         //低于60分不及格     
    echo "Sorry,you failed"
fi

运行程序:

wKiom1dfWLbR-RFsAAAZxy1Pbfc719.png

这里需要强调的是,[ ]和` `在shell中都是命令,因此应该将其后面跟着的参数之间加上空格分开,不然就有可能会报错,同时,在` `中仅支持‘>’和‘<’,并不支持‘>=’和‘<=’;


  • crond定时执行shell脚本

crond是Linux系统中的一个守护进程,设定启动之后可以用来定时执行指定的任务或等待处理某些任务,因此,当编辑好了一个shell脚本而想让这个shell脚本定期地去运行起来就可以将其添加用户的crontab文件中并将其启动,这样的话crond就会依照设定好的时间来运行脚本完成预定的任务;


栗子时间:

首先,在shell脚本中编写如下命令:

#!/bin/bash

DATETIME=$(date)   //提取出当前系统时间

//将特定目录下的test.txt文件拷贝到特定目录下的一个backups.txt中
$(cp /home/lounuo/test_6_12/test.txt /home/lounuo/test_6_12/backups.txt)
//将当前系统时间追加到backups.txt文件中
echo ${DATETIME} >> /home/lounuo/test_6_12/backups.txt


在当前用户下用crontab -e命令编写自身的一个crontab,路径为/var/spool/cron,文件名就为用户名:

*/1 * * * * /home/lounuo/test_6_12/test.sh

表示每隔一分钟执行一次命令也就是test.sh脚本;也就是说每隔一分钟将test.text文件的内容备份到backups.txt文件中

这里需要强调的是,在shell中仍然需要指明路径,不然crond在执行的时候就会默认在用户目录下创建出一个backups.txt文件,至于要拷贝的test.txt文件就当然找不到了,因此只会看到一个时间戳


使用不管crond是什么状态,service crond restart重启crond任务:

wKioL1dfc1qR85qYAAAGnGHtT2o112.png 


一开始backups.txt的内容为空;每隔一分钟修改test.txt的内容,然后再打开backups.txt,使用

tail -f backups.txt命令观察如下:

wKiom1dfdIeQDNuUAABvFEPiweY627.png

这里为了观察方便将时间设定的比较短,实际使用中可以将时间设定为更加合适的备份时间,如此一来就可以自定义一个备份的机制,每次更改一个文件中的内容都会自动备份一份,这样将shell脚本添加进crond定时任务中,就更加提高了用户与shell进行命令与结果交互的效率。



《完》