Shell Script 控制脚本

进程信号

Linux利用信号与运行在系统中的进程进行通信。可以通过对脚本进行编程,使其在收到特定信号时执行某些命令,从而控制shell脚本的操作。

Linux常见信号
1 SIGHUP 挂起进程
2 SIGINT 终止进程
3 SIGQUIT 停止进程(会让程序继续保留在内存中,并能从上次停止的位置继续运行)
9 SIGKILL 无条件终止进程
15 SIGTERM 尽可能终止进程
17 SIGSTOP 无条件停止进程,但不是终止进程
18 SIGTSTP 停止或暂停进程,但不终止进程
19 SIGCONT 继续运行停止的进程

默认情况下,bash shell会忽略收到的任何SIGQUIT (3)和SIGTERM (5)信号。但是bash shell会处理收到的SIGHUP (1)和SIGINT (2)信号。如果bash shell收到了SIGHUP信号,比如当你要离开一个交互式shell,它就会退出。但在退出之前,它会将SIGHUP信号传给所有由该shell所启动的进程(包括正在运行的shell脚本)。通过SIGINT信号,可以中断shell。Linux内核会停止为shell分配CPU处理时间。这种情况发生时,shell会将SIGINT信号传给所有由它所启动的进程,以此告知出现的状况。

而shell脚本的默认行为是忽略这些信号。它们可能会不利于脚本的运行。要避免这种情况,你可以脚本中加入识别信号的代码,并执行命令来处理信号。

  • 生成信号

中断进程:Ctrl+C组合键会生成SIGINT信号,并将其发送给当前在shell中运行的所有进程。

暂停进程:Ctrl+Z组合键会生成一个SIGTSTP信号,停止shell中运行的任何进程

如果你的shell会话中有一个已停止的作业,在退出shell时,bash会提醒你,用ps命令来查看已停止的作业。在S列中(进程状态),ps命令将已停止作业的状态为显示为T。仍旧想退出shell,只要再输入一遍exit命令就行了。

$ sleep 100
^Z
[1]+ Stopped sleep 100
$
$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 501 2431 2430 0 80 0 - 27118 wait pts/0 00:00:00 bash
0 T 501 2456 2431 0 80 0 - 25227 signal pts/0 00:00:00 sleep
0 R 501 2458 2431 0 80 0 - 27034 - pts/0 00:00:00 ps
$

或者,既然你已经知道了已停止作业的PID,就可以用kill命令来发送一个SIGKILL信号来终止它。

$ kill -9 2456
$
[1]+ Killed sleep 100
$
  • 捕获信号

在信号出现时捕获它们并执行其他命令,trap命令允许你来指定shell脚本要监看并从shell中拦截的Linux信号

trap commands signals

在trap命令行上,你只要列出想要shell执行的命令,以及一组用空格分开的待捕获的信号。你可以用数值或Linux信号名来指定信号。每次使用Ctrl+C组合键,脚本都会执行trap命令中指定的echo语句,而不是处理该信号并允许shell停止该脚本

$ cat test1.sh
#!/bin/bash
trap "echo ' Sorry! I have trapped Ctrl-C'" SIGINT
count=1
while [ $count -le 10 ]
do
    echo "Loop #$count"
    sleep 1
    count=$[ $count + 1 ]
done
$ ./test1.sh
Loop #1
^C Sorry! I have trapped Ctrl-C
Loop #2
Loop #3
^C Sorry! I have trapped Ctrl-C
Loop #4
  • 捕获脚本退出

除了在shell脚本中捕获信号,你也可以在shell脚本退出时进行捕获,只要在trap命令后加上EXIT信号就行

$ cat test2.sh
#!/bin/bash
trap "echo Goodbye..." EXIT
count=1
while [ $count -le 5 ]
do
    echo "Loop #$count"
    sleep 1
    count=$[ $count + 1 ]
done
$ ./test2.sh
Loop #1
Loop #2
Loop #3
Loop #4
Loop #5
Goodbye...
$

如果提前退出脚本,同样能够捕获到EXIT

$ ./test2.sh
Loop #1
Loop #2
Loop #3
^CGoodbye...
$
  • 修改或移除捕获

要想在脚本中的不同位置进行不同的捕获处理,只需重新使用带有新选项的trap命令。

$ cat test3.sh
#!/bin/bash
trap "echo ' Sorry... Ctrl-C is trapped.'" SIGINT
[...]
trap "echo ' I modified the trap!'" SIGINT
[...]
$ ./test3.sh
Loop #1
Loop #2
Loop #3
^C Sorry... Ctrl-C is trapped.
Loop #4
Loop #5
Second Loop #1
Second Loop #2
^C I modified the trap!
Second Loop #3
Second Loop #4
Second Loop #5
$

也可以删除已设置好的捕获。只需要在trap命令与希望恢复默认行为的信号列表之间加上两个破折号就行了。

$ cat test3.sh
#!/bin/bash
trap "echo ' Sorry... Ctrl-C is trapped.'" SIGINT
[...]
trap -- SIGINT
echo "I just removed the trap"
[...]
$ ./test3b.sh
Loop #1
Loop #2
Loop #3
^C Sorry... Ctrl-C is trapped.
Loop #4
Loop #5
I just removed the trap
Second Loop #1
Second Loop #2
^C
$

以后台模式运行脚本

在后台模式中,进程运行时不会和终端会话上的STDIN、STDOUT以及STDERR关联。但仍然会使用终端显示器来显示STDOUT和STDERR消息。

  • 后台运行脚本

以后台模式运行shell脚本非常简单。只要在命令后加个&符就行了,方括号中的数字是shell分配给后台进程的作业号。下一个数是Linux系统分配给进程的进程ID(PID)

$ ./test4.sh &
[1] 3231
$

在非控制台下运行脚本

有时你会想在终端会话中启动shell脚本,然后让脚本一直以后台模式运行到结束,即使你退出了终端会话。nohup命令运行了另外一个命令来阻断所有发送给该进程的SIGHUP信号。这会在退出终端会话时阻止进程退出。

$ nohup ./test1.sh &
[1] 3856
$ nohup: ignoring input and appending output to 'nohup.out'
$

由于nohup命令会解除终端与进程的关联,进程也就不再同STDOUT和STDERR联系在一起。为了保存该命令产生的输出,nohup命令会自动将STDOUT和STDERR的消息重定向到一个名为nohup.out的文件中。

作业控制

启动、停止、终止以及恢复作业的这些功能统称为作业控制

  • 查看作业

jobs命令允许查看shell当前正在处理的作业,要想查看作业的PID,可以在jobs命令中加入-l选项

$ jobs -l
[1]+ 1897 Stopped ./test10.sh
[2]- 1917 Running ./test10.sh > test10.out &
$
jobs命令参数
-l

列出进程的PID以及作业号

-n 只列出上次shell发出的通知后改变了状态的作业
-p 只列出作业的PID
-r 只列出运行中作业
-s 只列出已停止作业

带加号的作业会被当做默认作业。当前的默认作业完成处理后,带减号的作业成为下一个默认作业。任何时候都只有一个带加号的作业和一个带减号的作业,

  • 重启停止的作业

要以后台模式重启一个作业,可用bg命令加上作业号。

$ ./test11.sh
^Z
[1]+ Stopped ./test11.sh
$
$ ./test12.sh
^Z
[2]+ Stopped ./test12.sh
$
$ bg 2
[2]+ ./test12.sh &
$
$ jobs
[1]+ Stopped ./test11.sh
[2]- Running ./test12.sh &
$

要以前台模式重启作业,可用带有作业号的fg命令。

$ fg 2
./test12.sh
This is the script's end...
$
  • 调整优先级

调度优先级是内核分配给进程的CPU时间(相对于其他进程)。在Linux系统中,由shell启动的所有进程的调度优先级默认都是相同的。

调度优先级是个整数值,从-20(最高优先级)到+19(最低优先级)。默认情况下,bash shell以优先级0来启动所有进程。

nice命令允许你设置命令启动时的调度优先级。要让命令以更低的优先级运行,只要用nice的-n命令行来指定新的优先级级别。必须将nice命令和要启动的命令放在同一行中。nice命令阻止普通系统用户来提高命令的优先级。

$ nice -n 10 ./test4.sh > test4.out &
[1] 4973
$
$ ps -p 4973 -o pid,ppid,ni,cmd
PID PPID NI CMD
4973 4721 10 /bin/bash ./test4.sh
$

nice命令的-n选项并不是必须的,只需要在破折号后面跟上优先级就行了。

$ nice -10 ./test4.sh > test4.out &
[1] 4993
$ ps -p 4993 -o pid,ppid,ni,cmd
PID PPID NI CMD
4993 4721 10 /bin/bash ./test4.sh
$

有时你想改变系统上已运行命令的优先级。这正是renice命令可以做到的。它允许你指定运行进程的PID来改变它的优先级。renice命令会自动更新当前运行进程的调度优先级。和nice命令一样,renice命令也有一些限制:

  • 只能对属于你的进程执行renice;
  • 只能通过renice降低进程的优先级;
  • root用户可以通过renice来任意调整进程的优先级。

如果想完全控制运行进程,必须以root账户身份登录或使用sudo命令。

$ ./test11.sh &
[1] 5055
$
$ ps -p 5055 -o pid,ppid,ni,cmd
PID PPID NI CMD
5055 4721 0 /bin/bash ./test11.sh
$
$ renice -n 10 -p 5055
5055: old priority 0, new priority 10
$
$ ps -p 5055 -o pid,ppid,ni,cmd
PID PPID NI CMD
5055 4721 10 /bin/bash ./test11.sh
$

定时运行作业

Linux系统提供了多个在预选时间运行脚本的方法:at命令和cron表。每个方法都使用不同的技术来安排脚本的运行时间和频率

  • 用at 命令来计划执行作业

at命令允许指定Linux系统何时运行脚本。at命令会将作业提交到队列中,指定shell何时运行该作业。at的守护进程atd会以后台模式运行,检查作业队列来运行作业

at [-f filename] time

at命令会将STDIN的输入放到队列中。你可以用-f参数来指定用于读取命令(脚本文件)的文件名。time参数指定了Linux系统何时运行该作业。如果你指定的时间已经错过,at命令会在第二天的那个时间运行指定的作业。

  • 标准的小时和分钟格式,比如10:15。
  • AM/PM指示符,比如10:15 PM。
  • 特定可命名时间,比如now、noon、midnight或者teatime(4 PM)。
  • 标准日期格式,比如MMDDYY、MM/DD/YY或DD.MM.YY。
  • 文本日期,比如Jul 4或Dec 25,加不加年份均可。
  • 你也可以指定时间增量:当前时间+25 min; 明天10:15 PM;10:15+7天

在你使用at命令时,该作业会被提交到作业队列(job queue)。作业队列会保存通过at命令提交的待处理的作业。针对不同优先级,存在26种不同的作业队列。作业队列通常用小写字母a~z和大写字母A~Z来指代。如果想以更高优先级运行作业,可以用-q参数指定不同的队列字母

at命令利用sendmail应用程序来发送邮件。如果你的系统中没有安装sendmail,那就无法获得任何输出!因此在使用at命令时,最好在脚本中对STDOUT和STDERR进行重定向。如果不想在at命令中使用邮件或重定向,最好加上-M选项来屏蔽作业产生的输出信息

$ cat test13b.sh
#!/bin/bash
echo "This script ran at $(date +%B%d,%T)" > test13b.out
echo >> test13b.out
sleep 5
echo "This is the script's end..." >> test13b.out
$ at -M -f test13b.sh now
job 8 at 2015-07-14 12:48
$ cat test13b.out
This script ran at July14,12:48:18
This is the script's end...
$

atq命令可以查看系统中有哪些作业在等待 

$ at -M -f test13b.sh teatime
job 17 at 2015-07-14 16:00
$
$ at -M -f test13b.sh tomorrow
job 18 at 2015-07-15 13:03
$
$ at -M -f test13b.sh 13:30
job 19 at 2015-07-14 13:30
$
$ at -M -f test13b.sh now
job 20 at 2015-07-14 13:03
$
$ atq
20 2015-07-14 13:03 = Christine
18 2015-07-15 13:03 a Christine
17 2015-07-14 16:00 a Christine
19 2015-07-14 13:30 a Christine
$

atrm命令来删除等待中的作业

$ atq
18 2015-07-15 13:03 a Christine
17 2015-07-14 16:00 a Christine
19 2015-07-14 13:30 a Christine
$
$ atrm 18
$
$ atq
17 2015-07-14 16:00 a Christine
19 2015-07-14 13:30 a Christine
$
  • 安排需要定期执行的脚本

Linux系统使用cron程序来安排要定期执行的作业。cron程序会在后台运行并检查一个特殊的表(被称作cron时间表),以获知已安排执行的作业。cron时间表采用一种特别的格式来指定作业何时运行。

min hour dayofmonth month dayofweek command

cron时间表允许你用特定值、取值范围(比如1~5)或者是通配符(星号)来指定条目。例如,如果想在每天的10:15运行一个命令,可以用cron时间表条目:

15 10 * * * command

要指定在每周一4:15 PM运行的命令,可以用下面的条目:可以用三字符的文本值(mon、tue、wed、thu、fri、sat、sun)或数值(0为周日,6为周六)来指定dayofweek表项

15 16 * * 1 command

在每个月的第一天中午12点执行命令。可以用下面的格式:dayofmonth表项指定月份中的日期值(1~31)。

00 12 1 * * command

命令列表必须指定要运行的命令或脚本的全路径名。你可以像在普通的命令行中那样,添加任何想要的命令行参数和重定向符号。cron程序会用提交作业的用户账户运行该脚本。因此,你必须有访问该命令和命令中指定的输出文件的权限

15 10 * * * /home/rich/test4.sh > test4out
  • 构建cron时间表

每个系统用户(包括root用户)都可以用自己的cron时间表来运行安排好的任务。Linux提供了crontab命令来处理cron时间表。要列出已有的cron时间表,可以用-l选项。要为cron时间表添加条目,可以用-e选项

$ crontab -l
no crontab for rich
$

如果你创建的脚本对精确的执行时间要求不高,用预配置的cron脚本目录会更方便。有4个基本目录:hourlydailymonthlyweekly。如果脚本需要每天运行一次,只要将脚本复制到daily目录,cron就会每天执行它。

猜你喜欢

转载自blog.csdn.net/linshuo1994/article/details/84196049