一. 关于shell
shell,英文是壳,外壳的意思,至于在计算机中,同样有这样的一层意思,也就是可以将shell看做是计算机系统封装的一层外壳,来供用户使用,因此,用户可以通过操纵shell也就是输入一系列命令来达到各种需要的目的,那么shell也可以被称为命令解释器。
在shell下,用户可以键入一行命令而shell解释一行来使其依次执行不同的任务,这种方式称为交互式,如果在执行命令比较少的情况下交互式还是方便的,而且还使这些命令透明,用户能够分辨出因果关系;
但是如果想要执行的命令是成批的,并不需要知道过程只是想要知道结果,或者重复度很高,那么一行一行的敲入shell一行一行的解释无疑是很费时费力的,因此就可以使用另外一种处理方式就是批处理,也就是可以编写一个shell脚本,shell脚本和编程语言有些相似,同样都有变量和流程控制语句,但shell脚本是解释行的,也就是shell会将脚本中的命令一行一行的拿出来,相当于用户一行一行地键入,但是和交互式相比较起来就省事多了。
二. shell执行原理
由于历史原因,shell的种类有很多,比如bash、sh、cah、ksh、tcsh等,可以在当前路径/etc/shells下面查看系统中所有已知(但不一定安装)的shell:
对于当前的一个shell比如bash来说,当使用交互式键入一条命令的时候,它并不会自身去亲自执行一条命令,因为有可能这条命令是一条恶意的命令会侵犯当前进程,所以它会创建出一个子进程,让子进程去执行这个shell命令,但是对于shell脚本来说,当创建好一个shell脚本运行起来的时候,相当于是一条命令,这时候bash会创建一个子进程来运行shell脚本,而这个子进程同样也是一个bash,因此,当其去执行shell脚本中一条条的命令的时候,也就是相当于再会去创建出一个子进程来执行shell脚本中的一条条命令,可画图如下:
因此,在执行shell脚本中的多个命令的时候,也就需要创建出多个进程来一一执行,尽管这样,但仍然比交互式的要简便的多;
三. shell脚本
- 运行方式
在shell脚本的编写中,‘#‘表示注释,相当于C语言中的‘//’,但是只有在第一行例外,在shell脚本中,第一行必须声明要使用的命令解释器,形式如下:
#!/bin/bash echo "this is a shell test"
‘#‘的使用,在第一行并不表示注释,它表示该脚本使用后面的解释器/bin/bash解释执行;而‘#!‘被称为shebang;
chmod u+x shell脚本
首先,因为shell脚本并不需要经过编译,但是对于刚创建出来的shell脚本文件,一开始时并没有执行权限的,因此,可以将shell所属者的权限加上可执行,然后运行shell脚本:
命令解释器 shell脚本
要运行shell脚本,除了在脚本的编写中需要制定使用的命令解释器外,在运行起来的时候,仍然可以指定需要哪一个命令解释器来进行解释执行:
除了指定/bin/bash之外,还可以指定在当前系统中已经安装好的任何一个命令解释器;当然,除去了‘/bin/‘只使用一个bash来进行解释仍然是可行的,理所应当,其他解释器sh等也是一样:
- shell变量
按照惯例,shell变量由全大写字母加下划线组成,有两种类型的shell变量:
环境变量:
在进程中提到,子进程会从父进程那里继承来环境变量,因此,对于执行shell脚本和脚本中的命令而创建出的子进程会继承父进程的shell环境变量;
用printenv命令可以显示当前shell进程的环境变量:
本地变量:
环境变量是所有进程都有的概念,而本地变量是shell特有的概念;本地变量只存在于当前shell进程,用set命令可以显示当前shell进程中定义的所有变量(包括环境变量和本地变量)和函数;
在shell中定义一个变量格式如下:
VALNAME=value
等号的两边是不能有空格的,因为如果用空格隔开,VALNAME就会被解释成一条命令,后面就会被解释为命令的参数;
一个变量被定以后仅存在于当前shell进程,而用export可以将其导出到环境变量中,而使用unset可以将删除:
和语言中定义变量不同的是,在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} //同样可以使用未定义的变量,只是内容是一个空字符串
运行结果:
上面程序示例中,取出一个变量的内容可以使用$VALNAME后面直接跟上变量名,也可以使用${VALNAME},但是加上花括号不容易引起歧义,比如:
//本意是想在VAL1的内容后面追加上abc echo $VAL1abc //将VAL1abc看做一个变量 echo ${VAL1}abc //取出VAL1的值后面再跟上abc
- 反引号 `` 和$()
大体上来说,反引号``和$()的相同之处是都可用于命令替换,就是将括起来的命令执行完毕后再交给相应的对象或者输出:
#!/bin/bash echo `pwd` echo $(pwd)
运行脚本:
这里要注意区分$()和$(()),前者的内容只能是命令,后者用于算术运算;
但是反引号和$()是有区别的,如下:
VAL=10 echo `echo \$VAL` echo $(echo \$VAL)
在C语言中‘\‘是转义字符,用于除去后面的字符的特殊意义转而取其字面意思,因此,上面的代码中期望输出的是“$VAL”,但是运行程序会发现:
反引号输出的是取出了变量的内容,并没有将后面的‘$‘转义成字面值,而$()中‘\‘将‘$‘转义成字面值不再取出变量的内容,输出了“$VAL”达到了预期值;
而如果将上面的两句代码中改为:
VAL=10 echo `echo \\$VAL` echo $(echo \$VAL)
运行脚本:
因此分析结果:
虽然同是命令替换,但是反引号中可以看出‘\\‘相当于一个转义字符‘\‘,而$()是正常一个转义字符‘\‘转义出后面的字符表示其字面意思;
- eval命令
eval命令用于将其后跟着的参数命令内容进行必要的替换,然后再执行命令,也就是说,eval会对命令行进行两次扫描,第一次将其重新替换,第二次才真正执行命令;
对于普通的简单的命令来说是看不到什么区别的:
#!/bin/bash echo "this is a shell test" echo "hello world" eval echo "hello world"
运行脚本:
但是对于如下程序:
#!/bin/bash echo "this is a shell test" VAL=100 STR="echo \$VAL" echo ${STR} eval echo ${STR}
运行程序:
直接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 $?
运行程序:
匹配条件成立退出码为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
运行程序:
这里需要强调的是,[ ]和[[ ]]在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任务:
一开始backups.txt的内容为空;每隔一分钟修改test.txt的内容,然后再打开backups.txt,使用
tail -f backups.txt命令观察如下:
这里为了观察方便将时间设定的比较短,实际使用中可以将时间设定为更加合适的备份时间,如此一来就可以自定义一个备份的机制,每次更改一个文件中的内容都会自动备份一份,这样将shell脚本添加进crond定时任务中,就更加提高了用户与shell进行命令与结果交互的效率。
《完》