shell--脚本控制

知识体系:
#回顾信号功能
#隐藏在背景中
#在没有控制台的情况下运行
#做得更好
#准确无误的运行
#从头开始
前面运行脚本的方式都是在命令行界面运行,实际上还有运行shell脚本的其他方式,以及中断脚本的运行进程,控制脚本的运行时间都可以实现。
1、处理信号
linux下有各种信号,如停止、启动、终止。通过信号控制shell脚本的运行只需要使得shell脚本接收来自linux体系特定信号时执行命令即可。
1.1、linux信号回顾
系统和应用程序可以生产30多个linux信号,如下罗列出常用的linux系统信号:
******************************************************
信号           值                描述
          SIGHUP           挂起进程              
          SIGINT           中断进程           
          SIGQUIT          停止进程            
          SIGKILL          无条件终止进程         
15           SIGTERM          如果可能的话终止进程         
17           SIGSTOP          无条件停止,但不终止进程  
18           SIGTSTP          停止或暂停进程,但不终止      
19           SIGCONT          重新启动停止的进程
*******************************************************
默认情况下,bash shell忽略接收到的任何SIGQUIT和SIGTERM信号,以防止交互的shell意外终止。但是bash shell接收任何SIGHUP和SIGINT信号。
1.2、生成信号
1》中断进程
使用ctrl+c组合键可以生产SIGINT信号,比如用sleep命令测试:
[root@wzp ~]# sleep 100
如果我不使用组合键,那么控制台就无法进行输入了,一直运行该sleep程序,所以通过这方法可以终止进程。
2》暂停进程
有些进程想暂停而不是终止它,可以使用ctrl+z组合键生产SIGTSTP信号
[root@wzp ~]# sleep 100

[1]+  Stopped                 sleep 100
看到没有,如果是暂停进程,会有log信息显示stopped的。
如上可以看到中括号里面有一个1数值,这个就是shell分配的作业编号,第一个启动的进程分配作业编号1,第二个启动的进程分配作业编号2,依此类推,如果shell会话中存在停止的作业,退出shell会发出警告的:
[root@wzp ~]# exit
exit
There are stopped jobs.
[root@wzp ~]# exit
退出时说存在着被暂停的作业,不过你再次输入exit可以终止了进程强行退出shell,或者说你可以通过Kill命令发出SIGKILL命令终止它:
[root@wzp ~]# sleep 100

[1]+  Stopped                 sleep 100
[root@wzp ~]# sleep 200

[2]+  Stopped                 sleep 200
[root@wzp ~]# ps au
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root      5007  0.0  0.0   4812   496 pts/0      16:19   0:00 sleep 100
root      5009  0.0  0.0   4812   496 pts/0      16:19   0:00 sleep 200
root      5025  0.0  0.1   5356   944 pts/0    R+   16:19   0:00 ps au
[root@wzp ~]# exit
exit
There are stopped jobs.
[root@wzp ~]# kill -9 5007
[root@wzp ~]# kill -9 5009
[1]-  已杀死               sleep 100
[2]+  已杀死               sleep 200
这样子通过kill进程号达到终止了进程。
1.3、捕获信号
trap命令可以指定通过shell脚本监控和拦截信号,使得信号不被shell处理,而在本地处理它,其格式为:
trap commands signals
下面看个例子,使用trap命令来忽略SIGINT和SIGTERM信号的简单示例:
[root@wzp ~]# chmod u+x 6.1test
[root@wzp ~]# cat 6.1test
#!/bin/bash
trap "echo you can not stop me!" SIGINT SIGTERM
echo "this is a test program"
count=1
while [ $count -le 10 ]
do
  echo "loop #$count"
  sleep 3
  count=$[ $count + 1 ]
done
echo "the program is over"
[root@wzp ~]# ./6.1test
this is a test program
loop #1
loop #2
loop #3
you can not stop me!
loop #4
loop #5
loop #6
you can not stop me!
loop #7
loop #8
you can not stop me!
loop #9
loop #10
the program is over
当如上程序每隔3秒显示一次信息的时候,我通过ctrl+c暂停程序的时候,该信号都被忽略了,并且在trap命令下回显echo you can not stop me!内容,并且程序一直执行完毕。由此可见trap命令的强大哈!
1.4、捕获脚本退出
除了上面在shell脚本中捕获信号之外,还可以在shell脚本退出那瞬间捕获,只需要在trap命令后添加EXIT信号,看下例子:
[root@wzp ~]# cat 6.1test
#!/bin/bash
trap "echo yeah, the program is over" EXIT
count=1
while [ $count -le 5 ]
do
  echo "loop #$count"
  sleep 3
  count=$[ $count + 1 ]
done
[root@wzp ~]# ./6.1test
loop #1
loop #2
loop #3
loop #4
loop #5
yeah, the program is over
当脚本准备退出之际,就会触发trap,并且捕获了EXIT信号显示echo内容
当然,如果你ctrl+c终止进程也是会捕获EXIT信号的,暂停进程则不会。
1.5、移除捕获
我们可以使用破折号来移除捕获,使得shell捕获信号的功能失效,例子:
[root@wzp ~]# cat 6.1test
#!/bin/bash
trap "echo yeah, the program is over" EXIT
count=1
while [ $count -le 5 ]
do
  echo "loop #$count"
  sleep 3
  count=$[ $count + 1 ]
done
trap - EXIT
[root@wzp ~]# ./6.1test
loop #1
loop #2
loop #3
loop #4
loop #5
我只是在上面的例子中最后添加了移除捕获的一行,结果脚本退出的捕获被移除了,就没有了捕获信息显示了。

2、以后台模式运行脚本
有些shell脚本在执行中需要等待漫长时间,而且终端回话无法执行其他操作,这个时候就可以使得shell放置到后台运行。
2.1、以后台模式运行
这个很简单就得以实现,只要在运行脚本时在命令后面附带一个&符号即可。
[root@wzp ~]# cat 6.2test
#!/bin/bash
count=1
while [ $count -le 5 ]
do
  echo "loop #$count"
  sleep 3
  count=$[ $count + 1 ]
done
[root@wzp ~]# ./6.2test &
[5] 7348
[root@wzp ~]# loop #1
loop #2
loop #3
loop #4
loop #5

[5]   Done                    ./6.2test
通过这方法就可以到程序放到后台运行,控制台便可以进行其他操作。
所以可以借用这种方法运行多个程序。

3、在不使用控制台的情况下运行脚本
一个程序运行过程默认情况下会随着中断回话的退出而中断。这个时候就可以借用nohup命令来使得阻塞任何发送到进程的SIGHUP信号。
[root@wzp ~]# cat 6.2test
#!/bin/bash
count=1
while [ $count -le 5 ]
do
  echo "loop #$count"
  sleep 3
  count=$[ $count + 1 ]
done
[root@wzp ~]# nohup ./6.2test &
[2] 7877
[root@wzp ~]# nohup: appending output to “nohup.out”

[root@wzp ~]#
[2]-  Done                    nohup ./6.2test
因为nohup将进程和终端断开,所以进程没有STDOUT和STDERR输出链接。nohup命令将自动把这两类消息重定向到nohup.out这个自动新创建的文件中去:
[root@wzp ~]# cat nohup.out
loop #1
loop #2
loop #3
loop #4
loop #5
这个文件的内容跟命令行运行进程输出是完全一样的!

4、作业控制
shell运行的进程可以通过ctrl+z中断,利用kill终止进程,可以使用发送SIGCONT信号重启停止的进程。对于重启、停止。终止、恢复作业的操作就叫做作业控制。
4.1、查看作业
通过jobs命令可以直接查看shell处理的当前作业,看例子:
[root@wzp ~]# nohup ./6.2test &
[1] 8477
[root@wzp ~]# nohup: appending output to “nohup.out”

[root@wzp ~]# nohup ./6.2test &
[2] 8479
[root@wzp ~]# nohup: appending output to “nohup.out”

[root@wzp ~]# jobs
[1]-  Running                 nohup ./6.2test &
[2]+  Running                 nohup ./6.2test &
[root@wzp ~]# jobs
[1]-  Done                    nohup ./6.2test
[2]+  Done                    nohup ./6.2test
我运行两次同样的脚步程序到后台,然后通过jobs查看,显示running状态,等到脚步运行结束再次jobs查看即可显示已完成状态。
[root@wzp ~]# nohup ./6.2test &
[2] 8594
[root@wzp ~]# nohup: appending output to “nohup.out”

[root@wzp ~]# jobs
[2]+  Running                 nohup ./6.2test &
[root@wzp ~]# kill 8594
[root@wzp ~]# jobs
[2]+  已终止               nohup ./6.2test
当脚本一开始运行到后台会显示了PID号8594,当脚本还没运行结束我kill掉进程,也可以通过jobs查看出进程已经被kill了。
4.2、重新启动停止的作业
我们可以通过ctrl+z暂停进程,当要重新启动停止的作业时可以通过使用带有作业编号的bg命令,看例子:
[root@wzp ~]# nohup ./6.2test
nohup: appending output to “nohup.out”

[1]+  Stopped                 nohup ./6.2test
[root@wzp ~]# jobs
[1]+  Stopped                 nohup ./6.2test
[root@wzp ~]# bg 1
[1]+ nohup ./6.2test &
[root@wzp ~]# jobs
[1]+  Running                 nohup ./6.2test &
[root@wzp ~]# jobs
[1]+  Done                    nohup ./6.2test
先是把程序放到后台运行,然后马上暂停了进程运行,通过jobs即可查看。然后通过bg命令附带作业编号1重启进程。注意:作业编号在我一开始暂停进程的时候就显示了[1]。
上面的bg命令重启的进程后是放到进程去运行的。如果你想把它放到显示屏运行,即可以使用带编号的fg命令,看例子:
[root@wzp ~]# ./6.2test
loop #1
loop #2

[1]+  Stopped                 ./6.2test
[root@wzp ~]# fg 1
./6.2test
loop #3
loop #4
loop #5
运行一半的程序被暂停后,通过fg 1又可以调到命令行运行下去了。

5、使脚本更好的运行
linux是一个多任务操作系统,内核负责为系统中运行的每个进程分配CPU时间。而CUP中一次就能运行一个进程,因此内核轮流向每个进程分配CPU时间。从shell启动的所有进程在linux系统上的调度优先级都是相同的。调度优先级是内核相对其他进程分配某一个进程的CPU时间量。
调度优先级是一个整数值,从-20(最高优先级)到+20(最低优先级),默认情况下,bash shell启动所有优先级为0的进程
5.1 nice命令
nice命令可以在启动命令时设置它的调度优先级,要让命令在更低的优先级下运行,可以使用-n选项。看个例子:
[root@wzp ~]# nice -n 10 ./6.2test &
[1] 9888
这样子,该程序就运行在10优先级下了。还有一点注意的是我是通过root超级用户设定脚本运行的优先级别,如果设置在更低的优先级,对于普通用户即可操作,并且一般是属于该脚本程序的用户所有者。但是普通用户无法设置脚本再更高的优先级别下运行。看例子:
[testuser@wzp ~]$ ll
总计 8
-rwxr--r-- 1 testuser root 107 02-15 20:00 6.2test
[testuser@wzp ~]$ nice -n 10 ./6.2test &
[1] 10164
[testuser@wzp ~]$
loop #1
loop #2
loop #3
运行指定在低优先级是没问题的!
[testuser@wzp ~]$ nice -n -10 ./6.2test &
[1] 10186
[testuser@wzp ~]$ nice: cannot set niceness: 权限不够

[1]+  Exit 1                  nice -n -10 ./6.2test
设定在高优先级下运行报权限不够的错误信息!
5.2、renice命令
有使用需要更改系统中已运行的优先级,就可以使用renice命令,例子:
[root@wzp ~]# nohup ./6.2test &
[1] 10369
[root@wzp ~]# nohup: appending output to “nohup.out”

[root@wzp ~]# renice -10 -p 10369
10369: old priority 0, new priority -10
原先我设定脚本在后台运行,并且默认是0的优先级别。通过renice命令指定-10更高的优先级别,并且通过-p加PID号设定,从log信息我们知道旧的调度优先级被修改了。

6、准确无误的运行
shell脚本有时候需要设定在给定的时间运行,特别是半夜,等业务量没有那么忙的时候往往是SA反而忙碌的时候,我们很容易可以想到通过计划任务是实现这一需求,这里头主要有三种方式:
*at 命令
*batch 命令
*cron 表格
6.1、使用at命令调度作业
对于at命令的具体使用方面这里就不罗嗦了,可以看下网上的资料或者linux的复习进阶一讲述。其命令格式、日期格式都是需要注意的。
这里讲述下通过运行atd和sendmail应用程序来获取作业输出:
由于linux使用提交作业的用户的电子邮箱地址作为STDOUT和STDERR,所以任何以STDOUT和STDERR为目的地的输出都通过电子邮件发送给用户。
当需要制定运行某个文件的at计划,可以附带-f选项,如:
at -f testfile xx:xx
当制定好at命令后可以通过atq命令查看计划列表;
如果要删除已经制定好的计划列表可以通过atrm命令,附带计划number。
6.2、使用batch命令
batch命令跟at命令不太相同,我们知道at命令是在一个指定的时间运行计划好的命令,而batch则是安排脚本在系统使用率低的时候运行。如果linux处于高负荷下运行,那么batch命令就会延迟提交作业的运行。跟at命令类型的地方是命令的格式,可以通过-f参数从文件中读取,默认从STDIN读取。
6.3、调度定期脚本
如果需要脚本在每天、每周、每月的一个特定时间执行脚本,就可以使用cron程序调度需要运行的作业。cron程序在后台运行,它从特殊表格中查找需要调度运行的作业。
对于cron表格的一些命令格式、时间格式,构建cron表格等内容就不说了,这个直接去找网上资料或者看linux复习进结一的计划任务即可。
不过有点挺搞的提及下:系统管理员想在每个月的最后一天执行脚本,那么cron表格应该怎么写么?我们知道每个月的最后一天有可能是28、29、30、31,那么应该怎么判断这一天呢?答案:
这里可以借助添加if-then语句,用date命令检查明天是否1号即可解决问题,具体的cron表格内容如下:
* * * * * if [ `date +%d -d tomorrow` = 01 ]; then ; command
通过这种方法就可以判定每个月最后一天啦~\(≧▽≦)/~


posted @ 2012-06-21 14:15  刘竹青  阅读(156)  评论(0编辑  收藏  举报