shell脚本的基本结构以及如何执行



Shell脚本通常都是以.sh 为后缀名的,这个并不是说不带.sh这个脚本就不能执行,只是大家的一个习惯而已所以,以后你发现了.sh为后缀的文件那么它一定会是一个shell脚本了。test.sh中第一行一定是 “#! /bin/bash” 它代表的意思是,该文件使用的是bash语法如果不设置该行,那么你的shell脚本就不能被执行。’#’表示注释,在前面讲过的后面跟一些该脚本的相关注释内容以及作者和创建日期或者版本等等当然这些注释并非必须的,如果你懒的很,可以省略掉,但是笔者不建议省略因为随着你工作时间的增加,你写的shell脚本也会越来越多,如果有一天你回头查看你写的某个脚本时,很有可能忘记该脚本是用来干什么的以及什么时候写的所以写上注释是有必要的另外系统管理员并非你一个,如果是其他管理员查看你的脚本,他看不懂岂不是很郁闷该脚本再往下面则为要运行的命令了



Shell脚本的执行很简单,直接”sh filename “ 即可,另外你还可以这样执行



默认我们用vim编辑的文档是不带有执行权限的,所以需要加一个执行权限,那样就可以直接使用’./filename’ 执行这个脚本了另外使用sh命令去执行一个shell脚本的时候是可以加-x选项来查看这个脚本执行过程的,这样有利于我们调试这个脚本哪里出了问题



shell脚本中用到了’date’这个命令,它的作用就是用来打印当前系统的时间其实在shell脚本中date使用率非常高有几个选项笔者常常在shell脚本中用到:



%Y表示年,%m表示月,%d表示日期,%H表示小时,%M表示分钟,%S表示秒



注意%y%Y的区别



-d 选项也是经常要用到的,它可以打印n天前或者n天后的日期,当然也可以打印n个月/年前或者后的日期

另外星期几也是常用的



shell脚本中的变量

shell脚本中使用变量显得我们的脚本更加专业更像是一门语言,开个玩笑,变量的作用当然不是为了专业如果你写了一个长达1000行的shell脚本,并且脚本中出现了某一个命令或者路径几百次突然你觉得路径不对想换一下,那岂不是要更改几百次?你固然可以使用批量替换的命令,但是也是很麻烦,并且脚本显得臃肿了很多变量的作用就是用来解决这个问题的



test2.sh中使用到了反引号,你是否还记得它的作用?’d’’d1’在脚本中作为变量出现,定义变量的格式为变量名=变量的值”。当在脚本中引用变量时需要加上’$’符号,这跟前面讲的在shell中自定义变量是一致的下面看看脚本执行结果吧



下面我们用shell计算两个数的和



数学计算要用’[ ]’括起来并且外头要带一个’$’。脚本结果为:



Shell脚本还可以和用户交互



这就用到了read命令了,它可以从标准输入获得变量的值,后跟变量名。”read x”表示x变量的值需要用户通过键盘输入得到脚本执行过程如下:



我们不妨加上-x选项再来看看这个执行过程:



test4.sh中还有更加简洁的方式


read -p
选项类似echo的作用执行如下:




你有没有用过这样的命令”/etc/init.d/iptables restart “ 前面的/etc/init.d/iptables 文件其实就是一个shell脚本,为什么后面可以跟一个”restart”? 这里就涉及到了shell脚本的预设变量实际上,shell脚本在执行的时候后边是可以跟变量的,而且还可以跟多个不妨笔者写一个脚本,你就会明白了


执行过程如下:



在脚本中,你会不会奇怪,哪里来的$1$2,这其实就是shell脚本的预设变量,其中$1的值就是在执行的时候输入的1,而$2的值就是执行的时候输入的$2,当然一个shell脚本的预设变量是没有限制的,这回你明白了吧另外还有一个$0,不过它代表的是脚本本身的名字不妨把脚本修改一下


执行结果想必你也猜到了吧



linux系统的任务计划

这部分内容太重要了,其实大部分系统管理工作都是通过定期自动执行某一个脚本来完成的,那么如何定期执行某一个脚本呢?这就要借助linuxcron功能了



关于cron任务计划功能的操作都是通过crontab这个命令来完成的其中常用的选项有:
-u :指定某个用户,不加-u选项则为当前用户;
-e :制定计划任务;
-l :列出计划任务;
-r :删除计划任务



使用crontab -e 来制定计划任务,上面的例子表示在0526日(这天必须是周四)的1001分执行’ echo "ok" >/root/cron.log’这样的任务
Cron的格式是这样的,每一行代表一个任务计划,总共分成两部分,前面部分为时间,后面部分要执行的命令后面的命令不用多讲,至于前面的时间是有讲究的,这个时间共分为5段,用空格隔开(可以是多个空格),第一段表示分钟(0-59),第二段表示小时(0-23),第三段表示日(1-31),第四段表示月(1-12),第五段表示周(0-7,0或者7都可以表示为周日)。从左至右依次是:分,时,日,月,周(一定要牢记)!
crontab -e 实际上是打开了/var/spool/cron/username (如果是root则打开的是/var/spool/cron/root)这个文件使用的是vim编辑器,所以要保存的话则在命令模式下输入:wq即可但是,你千万不要直接去编辑那个文件,因为可能会出错,所以一定要使用crontab -e来编辑查看已经设定的任务计划使用crontab -l



删除计划任务要用crontab -r



下面笔者给你出一些练习题,帮助你熟悉这个cron的应用
1. 每天凌晨120分清除/var/log/slow.log这个文件;
2. 每周日3点执行’/bin/sh /usr/local/sbin/backup.sh’
3. 每月14410分执行’/bin/sh /usr/local/sbin/backup_month.sh’
4. 每隔8小时执行’ntpdate time.windows.com’
5. 每天的1点,12点,18点执行’/bin/sh /usr/local/sbin/test.sh’
6. 每天的9点到18点执行’/bin/sh /usr/local/sbin/test2.sh’
答案:
1. 20 1 * * * echo “”>/var/log/slow.log
2. 0 30 * * 0 /bin/sh /usr/local/sbin/backup.sh
3. 10 04 14 * * /bin/sh /usr/local/sbin/backup_month.sh
4. 0 */8 * * * ntpdate time.windows.com
5. 0 1,12,18 * * /bin/sh /usr/local/sbin/test.sh
6. 0 9-18 * * * /bin/sh /usr/local/sbin/test2.sh
Cron的这部分内容并不难,你只要会了这6道练习题,你就算掌握它了这里要简单说一下,每隔8小时,就是用全部小时(0-23)去除以8,你仔细想一下结果,其实算出来应该是0,8,16三个数当遇到多个数(分钟小时周)例如第5题,则需要用逗号隔开而时间段是可以用’-‘的方式表示的等设置好了所有的计划任务后需要查看一下crond服务是否启动,如果没有启动,需要启动它



如何启动稍后会做介绍除了用户自定义的计划任务外,其实系统本身也有计划任务的



系统会安装这个配置文件中的计划去执行内定的任务

ps 查看系统进程

作为系统管理员,一定要知道你所管理的系统都有那些进程在运行,在windows下只要打开任务管理器即可查看linux下呢?其实在上面介绍的top命令就可以,但是不够专业,当然还有专门显示系统进程的命令




对了,就是这个’ps aux’。笔者也经常看到有的人喜欢用’ps -elf’ 大同小异,显示的信息基本上是一样的。 ps命令还有更多的用法,笔者不再做介绍,因为你只要会用这个命令就足够了,请man一下下面介绍上图上出现的几个参数的意义
PID
:进程的id,这个id很有用,在linux中内核管理进程就得靠pid来识别和管理某一个程,比如我想终止某一个进程,则用 ‘kill 进程的pid’,有时并不能杀掉,则需要加一个-9选项了’kill -9 进程pid’
STAT :表示进程的状态,进程状态分为以下几种(不要求记住,但要了解
D  不能中断的进程(通常为IO
R  正在运行中的进程
S  已经中断的进程,通常情况下,系统中大部分进程都是这个状态
T  已经停止或者暂停的进程,如果我们正在运行一个命令,比如说sleep 10,如果我们按一下ctrl -z 让他暂停,那么我们用ps查看就会显示T这个状态
W 这个好像是说,从内核2.6xx 以后,表示为没有足够的内存页分配
X  已经死掉的进程(这个好像从来不会出现)
Z  僵尸进程,杀不掉,打不死的垃圾进程,占系统一小点资源,不过没有关系如果太多,就有问题了一般不会出现
<  高优先级进程
N  低优先级进程
L   在内存中被锁了内存分页
s   主进程
l   多线程进程
+  代表在前台运行的进程
这个ps命令是笔者在工作中用的非常多的命令之一,所以请记住它吧关于ps命令的使用,笔者经常会连同管道符一起使用,用来查看某个进程或者它的数量



上面的6不对,需要减掉1,因为使用grep命令时,grep命令本身也算作了一个

shell脚本中的逻辑判断

如果你学过C或者其他语言,相信你不会对if 陌生,在shell脚本中我们同样可以使用if逻辑判断shellif判断的基本语法为:
1)不带else
if 判断语句; then
command
fi



if1.sh中出现了 ((a<60))这样的形式,这是shell脚本中特有的格式,用一个小括号或者不用都会报错,请记住这个格式,即可执行结果为:



2)带有else
if 判断语句 ; then
command
else
command
fi



执行结果为:



3)带有elif
if 判断语句一 ; then
command
elif 判断语句二; then
command
else
command
fi



这里的 && 表示并且的意思,当然你也可以使用 || 表示或者,执行结果:



以上只是简单的介绍了if语句的结构在判断数值大小除了可以用”(( ))”的形式外,还可以使用”[ ]”。但是就不能使用>, < , = 这样的符号了,要使用 -lt (小于),-gt (大于),-le (小于等于),-ge (大于等于),-eq (等于),-ne (不等于)



再看看if中使用 && ||的情况



shell 脚本中if还经常判断关于档案属性,比如判断是普通文件还是目录,判断文件是否有读写执行权限等常用的也就几个选项:
-e :判断文件或目录是否存在
-d :判断是不是目录,并是否存在
-f :判断是否是普通文件,并存在
-r :判断文档是否有读权限
-w :判断是否有写权限
-x :判断是否可执行
使用if判断时,具体格式为: if [ -e filename ] ; then



shell 脚本中,除了用if来判断逻辑外,还有一种常用的方式,那就是case具体格式为:
case 变量 in
value1)
command
;;
value2)
command
;;
value3)
command
;;
*)
command
;;
esac
上面的结构中,不限制value的个数,*则代表除了上面的value外的其他值下面笔者写一个判断输入数值是奇数或者偶数的脚本



$a 的值或为1或为0,执行结果为:



也可以看一下执行过程:



case脚本常用于编写系统服务的启动脚本,例如/etc/init.d/iptables中就用到了,你不妨去查看一下

netstat 查看网络状况



netstat命令用来打印网络连接状况系统所开放端口路由表等信息笔者最常用的关于netstat的命令就是这个netstat -lnp
(打印当前系统启动哪些端口)以及netstat -an (打印网络连接状况)这两个命令非常有用,请一定要记住



如果你所管理的服务器是一台提供web服务(80端口)的服务器,那么你就可以使用netstat -an |grep 80开查看当前连接web服务的有哪些IP



linux的系统服务管理

如果你对windows非常熟悉的话,相信你肯定配置过开机启动的服务,有些服务我们日常用不到则要把它停掉,一来可以节省资源,二来可以减少安全隐患linux上同样也有相关的工具来管理系统的服务
1. ntsysv
用来配置哪些服务开启或者关闭,有点想图形界面,不过是使用键盘来控制的如果没有这个命令请使用 yum install -y ntsysv 安装它



敲完这个命令后则显示出如上图中的画面在屏幕的最上面有’Red Hat’等字样,这是在告诉我们这个工具是由Red Hat公司开发的按键盘的上下方向键可以调节红色光标,按空格可以选择开启或者不开启,如果前面的中括号内显示有’*’ 则表示开启否则不开启通过这个工具也可以看到目前系统中所有的服务建议除’crond, iptables, network, sshd, syslog, irqbalance, sendmail, microcode_ctl’ 外其他服务全部停掉选择好后,按’tab’键选择ok然后回车需要重启机器才能生效
2. chkconfig
Linux系统所有的预设服务可以查看/etc/init.d/目录得到



其实这就是系统所有的预设服务了为什么这样讲,因为系统预设服务都是可以通过这样的命令实现 ‘service 服务名 start|stop|restart’ ,这里的服务名就是/etc/init.d/目录下的这些文件了除了可以使用’service crond start ‘启动crond外,还可以使用/etc/init.d/crond start 来启动



如上图,这两个命令出来的结果是一样的



再看看这个chkconfig命令,它不仅可以列出来所有的服务,还可以详细到每个级别这里的级别(0,1,2,3,4,5,6)就是inittab里面介绍的那几个启动级别了



这样还可以查看某一个服务的启动情况



--level 指定级别,后面是服务名,然后是off或者on--level后还可以跟多个级别



另外还可以省略级别,默认是针对2,3,4,5级别操作



另外这个chkconfig 还有一个功能就是可以把某个服务加入到系统服务,即可以使用service 服务名 start 这样的形式,并且可以在chkconfig --list 中查找到当然也能删除掉



这个功能常用在把自定义的启动脚本加入到系统服务当中关于系统服务就讲这些内容,其实还有很多内容笔者没有介绍,道理很简单,一来讲多了你不能消化二来讲多了你也用不上

抓包工具tcpdump

有时候,也许你会有这样的需求,想看一下某个网卡上都有哪些数据包,尤其是当你初步判定你的服务器上有流量攻击这时,使用抓包工具来抓一下数据包,就可以知道有哪些IP在攻击你了



如果你没有tcpdump 这个命令,需要用’yum install -y tcpdump ’命令去安装一下上图中第三列和第四列显示的信息为哪一个IP+port在连接哪一个IP+port,后面的信息是该数据包的相关信息,如果不懂也没有关系,毕竟你不是专门搞网络的,而这里需要你关注的只是第三列以及第四列。-i 选项后面跟设备名称,如果你想抓eth1网卡的包,后面则要跟eth1.至于-nn选项的作用是让第三列和第四列显示成IP+端口号的形式,如果不加-nn则显示的是主机名+服务名称

shell脚本中的循环

Shell脚本中也算是一门简易的编程语言了,当然循环是不能缺少的常用到的循环有for循环和while循环下面就分别介绍一下两种循环的结构



脚本中的seq 1 5 表示从15的一个序列你可以直接运行这个命令试下脚本执行结果为:



通过这个脚本就可以看到for循环的基本结构

for 变量名 in 循环的条件; do
command
done



循环的条件那一部分也可以写成这样的形式,中间用空格隔开即可你也可以试试,for i in `ls`; do echo $i; done for i in `cat test.txt` do echo $i; done



再来看看这个while循环,基本格式为:
while 条件; do
command
done
脚本的执行结果为:



另外你可以把循环条件忽略掉,笔者常常这样写监控脚本
while :; do
command
done


linux中的数据备份

数据备份,不用说太多吧,毫无疑问很重要笔者就曾经有过一次非常痛苦的经历,备份策略没有做好,结果磁盘坏掉数据丢失,简直是撕心裂肺的痛呀还好数据重要性不是特别高,即使是不高也是丢失了数据,这是作为系统管理员最不应该出现的事故所以,在你以后的系统维护工作中,一定要把数据备份当回事,认真对待linux上作为数据备份的工具很多,但笔者就只用一种那就是rsync 从字面上的意思你可以理解为remote sync (远程同步)这样可以让你理解的更深刻一些。Rsync不仅可以远程同步数据(类似于scp),当然还可以本地同步数据(类似于cp),但不同于cpscp的一点是,rsync不像cp/scp一样会覆盖以前的数据(如果数据已经存在),它会先判断已经存在的数据和新数据有什么不同,只有不同时才会把不同的部分覆盖掉如果你的linux上下面看例子吧(如果没有rsync命令请使用yum install -y rsync安装)



上面例子表示把当前目录下的123同步到/tmp/目录下,并且同样也命名为123。如果是远程拷贝的话就是这样的形式了 IP:path (如:10.0.2.34:/root/



当建立连接后,是需要输入密码的如果手动去执行这些操作还好,但是如果是写在脚本中怎么办?这就涉及到添加信任关系了,该部分内容稍后会详细介绍


ifconfig 查看网卡IP

ifconfig类似与windowsipconfig,不加任何选项和参数只打印当前网卡的IP相关信息(子网掩码网关等)



当然ifconfig后面可以跟设备名,只打印指定设备的IP信息



windows下设置IP非常简单,然而在命令窗口下如何设置?这就需要去修改配置文件/etc/sysconfig/network-scripts/ifcfg-eth0了,如果是eth1那么配置文件是/etc/sysconfig/network-scripts/ifcfg-eth1.



如果想修改IP的话,则只需要修改IPADDR , NETMASK以及GATEWAY即可如果你的linux是通过dhcp服务器自动获得的IP,那么配置文件肯定和上图中的不一样,BOOTPROTO那里会是’dhcp’,如果你要配置成静态IP的话,这里就需要写成’none’。关于如何设置IP以及子网掩码的这些知识属于网络相关的基础知识了,如果你对这方面比较陌生的话,建议你去看看网络相关的资料当修改完IP后需要重启网络服务新IP才能生效,重启命令为’ service network restart’



另外如果你有多个网卡的情况时,只想重启某一个网卡的话,还可以使用这个命令



ifdown 即停掉网卡,ifup即启动网卡有一点要提醒你的是,如果你远程登录你的服务器,当你使用ifdown eth0这个命令的时候,很有可能后面的命令ifup eth0不会被运行,这样导致你断网而无法连接服务器,所以请尽量使用service network restart 这个命令来重启网卡

shell脚本中的函数

如果你学过开发,肯定知道函数的作用如果你是刚刚接触到这个概念的话,也没有关系,其实很好理解的函数就是把一段代码整理到了一个小单元中,并给这个小单元起一个名字,当用到这段代码时直接调用这个小单元的名字即可有时候脚本中的某段代总是重复使用,如果写成函数,每次用到时直接用函数名代替即可,这样就节省了时间还节省了空间



fun.sh 中的sum() 为自定义的函数,在shell脚本中要用

function 函数名() {
command
}
这样的格式去定义函数
上个脚本执行过程如下:



有一点笔者要提醒你一下,在shell脚本中,函数一定要写在最前面,不能出现在中间或者最后,因为函数是要被调用的,如果还没有出现就被调用,肯定是会出错的

rsync的命令格式

rsync [OPTION]... SRC DEST
rsync [OPTION]... SRC [USER@]HOST:DEST
rsync [OPTION]... [USER@]HOST:SRC DEST
rsync [OPTION]... [USER@]HOST::SRC DEST
rsync [OPTION]... SRC [USER@]HOST::DEST
笔者在一开始举的两个例子,第一个例子即为第一种格式,第二个例子即为第二种格式,但不同的是,笔者并没有加user@host 如果不加默认指的是root 。第三种格式是从远程目录同步数据到本地第四种以及第五种格式使用了两个冒号,这种方式和前面的方式的不同在于验证方式不同,稍后详细介绍

给一个网卡设定多个IP

linux系统中,网卡是可以设定多重IP的,笔者曾经管理的一台服务器的eth1就设定了5IP,实在是够变态的



ifcfg-eth0复制成ifcfg-eth0:1 然后编辑ifcfg-eth0:1修改DEVICE以及IPADDR保存后重启网卡



再次查看eth0上就有两个IP这里你要注意一下,文件名(ifcft-eth0:1)写成什么都无所谓,但是文件内的DEVICE=eth0:1一定要按照这样的格式写,否则你启动不起来网卡

shell脚本练习题

1. 编写shell脚本,计算1-100的和;

2. 编写shell脚本,要求输入一个数字,然后计算出从1到输入数字的和,要求,如果输入的数字小于1,则重新输入,直到输入正确的数字为止;

3. 编写shell脚本,把/root/目录下的所有目录(只需要一级)拷贝到/tmp/目录下;

4. 编写shell脚本,批量建立用户user_00, user_01, … ,user_100并且所有用户同属于users组;

5. 编写shell脚本,截取文件test.log中包含关键词’abc’的行中的第一列(假设分隔符为”:”),然后把截取的数字排序(假设第一列为数字),然后打印出重复次数超过10次的列;

6. 编写shell脚本,判断输入的IP是否正确(IP的规则是,n1.n2.n3.n4,其中1<n1<255, 0<n2<255, 0<n3<255, 0<n4<255)。

以下为练习题答案:

1.#! /bin/bash

sum=0

for i in `seq 1 100`; do

sum=$[$i+$sum]

done

echo $sum
2.#! /bin/bash

n=0

while [ $n -lt "1" ]; do

read -p "lease input a number, it must greater than "1":" n

done



sum=0

for i in `seq 1 $n`; do

sum=$[$i+$sum]

done

echo $sum
3.#! /bin/bash

for f in `ls /root/`; do

if [ -d $f ] ; then

cp -r $f /tmp/

fi

done
4.#! /bin/bash

groupadd users

for i in `seq 0 9`; do

useradd -g users user_0$i

done



for j in `seq 10 100`; do

useradd -g users user_$j

done
5.#! /bin/bash

awk -F':' '$0~/abc/ {print $1}' test.log >/tmp/n.txt

sort -n n.txt |uniq -c |sort -n >/tmp/n2.txt

awk '$1>10 {print $2}' /tmp/n2.txt
6.#! /bin/bash

checkip() {

if echo $1 |egrep -q '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' ; then

a=`echo $1 | awk -F. '{print $1}'`

b=`echo $1 | awk -F. '{print $2}'`

c=`echo $1 | awk -F. '{print $3}'`

d=`echo $1 | awk -F. '{print $4}'`



for n in $a $b $c $d; do

if [ $n -ge 255 ] || [ $n -le 0 ]; then

echo "the number of the IP should less than 255 and greate than 0"

return 2

fi

done

else

echo "The IP you input is something wrong, the format is like 192.168.100.1"

return 1

fi

}



rs=1

while [ $rs -gt 0 ]; do

read -p "lease input the ip:" ip

checkip $ip

rs=`echo $?`

done

echo "The IP is right!"