《Linux命令行与shell脚本编程大全》 第十五章 学习笔记

第十五章:控制脚本

处理信号

重温Linux信号

信号 名称 描述
1 HUP 挂起
2 INT 中断
3 QUIT 结束运行
9 KILL 无条件终止
11 SEGV 段错误
15 TERM 尽可能终止
17 STOP 无条件停止运行,但不终止
18 TSTP 停止或暂停,但继续在后台运行
19 CONT 在STOP或TSTP之后恢复执行

默认情况下,bash shell会忽略收到的任何SIGQUIT(3)和SIGTERM(5)信号。

如果bash shell收到了SIGHUP信号,它会退出。但是在退出之前,它会将SIGHUP信号传给shell启动的所有进程(比如shell脚本)。通过SIGINT可以中断shell。Linux内核会停止将CPU的处理时间分配给shell。此时,shell会将SIGINT信号传给shell启动的所有进程。

注意:shell会将这些信号传给shell脚本程序来处理,而shell脚本的默认行为是忽略这些信号。

产生信号

终止进程

CTRL+C会生成SIGINT信号

暂停进程

CTRL+Z会生成SIGSTP信号

$ gedit a
^Z
[1]+ Stopped gedit a

这里看到有一个作业产生,状态为stopped

关于作业讲解可参看(#1)

可以用ps查看已停止的作业,然后用kill将其终止。

捕捉信号

trap commands signal

指定shell脚本观察的信号并拦截

#!/bin/bash
trap "echo you can\'t stop me!" SIGINT count=1
while [ $count -le 10 ]
do
echo "Loop #$count"
count=$[ $count + 1]
sleep 5
done

执行脚本时候按下CTRL+C,那么此时脚本不会终止,而是打出log

$ signal_test
Loop #1
^Cyou can't stop me!
Loop #2
^Cyou can't stop me!
……

虽然不能终止它,但是发现,本来应该sleep 5秒的,CTRL+C之后,这次sleep就马上结束了

捕捉脚本的退出

同上面一样,只需把trap捕捉的信号改为EXIT即可

trap "echo bye!" EXIT

当脚本退出的时候则会输出:

$ signal_test
Loop #1
^Cbye!

移出捕捉

trap - signals

在连词线后面加信号即可。

以后台模式运行脚本

后台运行脚本

只需在命令后加&即可

此时会输出作业ID和进程ID

$ gedit signal_test &
[1] 9651

运行多个后台作业

$ gedit redirect_test &
[2] 9689
[1] Done gedit signal_test
$ signal_test &
[3] 9699

通过ps au可以看到程序执行情况

1000      9689  2.5  0.4 395676 29944 pts/0    Sl   20:12   0:00 gedit redirect_test

1000      9699  0.0  0.0  13624  1468 pts/0    S    20:12   0:00 /bin/bash ./signal_test

退出终端

通过ps的输出,可以看到每个后台进程都绑定到了该终端会话的终端上了(pts/0).如果进程会话退出了,后台进程也会退出。

退出终端时,如果有关联到终端的还在运行的后台进程,有的终端模拟器会提醒你,但不是全部。

在非控制台下运行脚本

nohup命令可以帮助你使得脚本一直在后台运行,直到其完成,即使退出了终端会话。

nohup command args...

$ nohup gedit signal_test &
[1] 9878
$ nohup: ignoring input and appending output to `nohup.out'

如果关闭该会话,脚本会忽略任何终端发过来的SIGHUP信号

由于nohup命令会从终端解除进程的关联,进程会丢掉到STDOUT和STDERR的链接。

为了保存输出,nohup会自动将STDOUT和STDERR重定向到名为nohup.out的文件中

注意:如果使用nohup执行了多个命令,那么这些输出都会重定向到nohup.out中!

作业控制

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

查看作业

jobs可以列出分配给shell的作业

-l:列出进程的PID以及作业号

-n:只列出上次shell发出的通知后改变了状态的作业

-p:只列出作业的PID

-r:只列出运行中的作业

-s:只列出已停止的作业

$ gedit test.xml &
[1] 5463
$ jobs
[1]+ Running gedit test.xml &

带加号的作业会被当成默认的作业。使用作业命令时,如果未指定作业号,那么该作业会被当做操作对象。

带减号的作业会在当前默认作业处理完毕的时候成为下一个默认作业。

任何时候都不会有超过1个的带加号的作业和带减号的作业

$ signal_test
Loop #1
^Z
[1]+ Stopped signal_test
$ signal_test
Loop #1
^Z
[2]+ Stopped signal_test
$ signal_test
Loop #1
^Z
[3]+ Stopped signal_test
$ jobs -l
[1] 5596 Stopped signal_test
[2]- 5598 Stopped signal_test
[3]+ 5602 Stopped signal_test
$ kill -9 5602
$ jobs -l
[1]- 5596 Stopped signal_test
[2]+ 5598 Stopped signal_test

kill了3号任务之后,之前带减号的2号任务就变成了当前的任务

重启停止的作业

bg jobid:在后台重启任务

fg jobid:在前台重启任务

(fg、bg更多讲解可参看(#1))

调整谦让度

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

nice命令

nice -n level command

普通用户无法设置更高优先级,会得到如下错误

$ nice -n -1 gedit
nice: cannot set niceness: Permission denied

renice命令

renice level -p pid

使用renice时需要注意:

1.只能对属于你的进程执行

2.只能降低优先级

3.root用户可以随意调整任何进程任何优先级

定时运行作业

atd守护进程会检查系统上的一个特殊目录(通常为/var/spool/at。我机器上面没有这个目录,在我机器上应该是/var/spool/cron/atjobs)

默认情况下,atd守护进程会每60s检查一下这个目录。有作业时,atd守护进程会检查作业设置运行的时间。

at命令的格式

at [-f filetime

默认情况下,at会将STDIN的输入放到队列中。-f可以指定执行的脚本文件。

下面是at可以识别的时间格式:

标准小时和分钟,比如10:11

~AM/~PM指示符,比如10:32~PM

特定可命名的时间:比如now、noon、midnight、teatime(4~PM)

如果指定的时间已经过去了,那么at会在第二天这个时候执行

标准日期格式:比如MMDDYY、MM/DD/YY、DD.MM.YY

文本日期,比如Jul 4,有无年份均可

也可指定时间增量

当前时间+25min

明天11:32~PM

11:50+4天

使用at命令,作业会被提交到作业队列(job queue)中。有26种不同优先级的队列,用小写a-z表示。

作业队列字母排序越高,优先级就越低。可以使用-q执行优先级。

$ at -f for_test noon
warning: commands will be executed using /bin/sh
job 8 at Thu Aug 29 12:00:00 2013
$ sudo ls /var/spool/cron/atjobs/
a00008015e6130
$ at -q c -f for_test noon
warning: commands will be executed using /bin/sh
job 9 at Thu Aug 29 12:00:00 2013
$ sudo ls /var/spool/cron/atjobs/
a00008015e6130 c00009015e6130

当任务执行完毕的时候,/var/spool/cron/atjobs/下的文件会被删除

注意:

使用at的时候,我们会发现有这样一行log输出

warning: commands will be executed using /bin/sh

也就是说,系统会用/bin/sh来执行脚本,而不是使用bash!

这样就会遇到一些问题,足以让人崩溃。

大多数:inux发行版中,赋给/bin/sh的默认shell是bash shell,但是Ubuntu将dash shell作为其默认shell。

为了使用bash执行脚本,我们需要做个wrapper

bash /home/su1216/android/source/linux_learned/for_test

保存为文件,我这里命名它为bash_wrapper

然后用at执行此脚本即可

$ at -f bash_wrapper now
warning: commands will be executed using /bin/sh
job 27 at Thu Aug 29 16:08:00 2013

这个时候,世界将恢复正常。

获取作业的输出

作业运行时,屏幕不会有输出。系统会将提交作业的用户Email作为STDOUT和STDERR。

在机器上要安装mailutils

之后执行mail,可能会出现下面的问题:

$ mail
Cannot open mailbox /var/mail/su1216: Permission denied
No mail for su1216

可以按着下列步骤解决:

sudo touch /var/mail/$USER
sudo chown $USER:mail /var/mail/$USER
sudo chmod o-r /var/mail/$USER
sudo chmod g+rw /var/mail/$USER

上面的命令之前都已经讲过,意义分别为

创建/var/mail/$USER文件

改变属主

去除其他用户的读权限

给属组增加读写权限

列出等待的作业

atq可以列出等待的作业

$ atq
30 Fri Aug 30 16:28:00 2013 a su1216

删除作业

atrm job

注意:只能删除自己提交的作业

计划定期执行脚本

Linux系统使用cron来定期执行作业。cron会在后台运行并检查称作cron时间表(cron table)来获得计划执行的作业。

cron时间表

cron时间表格式如下:

min hour dayofmonth month dayofweek command

cron时间表允许使用特定的值、范围、通配符指定时间。比如:

15 10 * * * command

在dayofmonth、month和dayofweek字段中使用通配符,表示字段值的可以取到的全集。上面表示在每天的10:15都要执行command

可以使用三字符文本值(mon、tue……)或者数值(0=周日,6=周六)指定dayofweek

注意:如何指定每月的最后一天

可以使用date来查看明天的日期是不是01

00 12 *  * * if [ `date +%d -d tomorrow` = 01 ] ; then ; command

在每天上午12点的时候检查明天是不是01

cron时间表必须指定命令的全路径。可以添加任何参数和重定向符号

cron程序是假定Linux是7*24小时运行的!

如果脚本还没有执行,系统就关闭了,过了执行时间再开启机器,那么cron是不会执行过期的脚本的。

构建cron时间表

每个用户都有自己的cron时间表

crontab -l:列出当前用户的cron时间表,默认cron时间表是不存在的。可以使用-e参数来添加条目。

cron目录

当不要求有精确的时间执行脚本时,用预配置的cron脚本目录会更方便。

有4个基本目录

$ ls /etc/cron.*ly
/etc/cron.daily:
0anacron apport apt bsdmainutils dpkg exim4-base logrotate man-db mlocate popularity-contest quota samba standard /etc/cron.hourly: /etc/cron.monthly:
0anacron /etc/cron.weekly:
0anacron apt-xapian-index cvs man-db

每天执行一次,则只需把脚本拷贝到 /etc/cron.daily下面即可。

anacron程序

anacron和cron不同,他会处理因为关机而过期的任务。

anacron只会处理cron目录的程序。它用时间戳来决定作业是否在适当的计划间隔内运行了。每个cron目录都有时间戳文件,位于/var/spool/anacron

anacron程序有自己的用来检查作业目录的表,通常位于/etc/anacrontab

anacron时间表于cron时间表格式不同,具体如下:

period delay identifier command

period单位为天,delay系统启动多少分钟后,anacron开始执行错过的脚本。command包括run-parts程序和一个cron脚本目录。run-parts程序负责运行目录中传给它的任何脚本。

注意:anacron不会运行位于/etc/cron.hourly的脚本

identifier是一个特殊的非空白字符串,它用于唯一识别日志消息和错误Email中的作业。

下面是我机器上面的anacrontab

$ cat /etc/anacrontab
# /etc/anacrontab: configuration file for anacron # See anacron(8) and anacrontab(5) for details. SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin # These replace cron's entries
1 5 cron.daily nice run-parts --report /etc/cron.daily
7 10 cron.weekly nice run-parts --report /etc/cron.weekly
@monthly 15 cron.monthly nice run-parts --report /etc/cron.monthly

启动时运行

开机时运行脚本

开机过程

开始运行Linux系统时,Linux内核会加载到内存中并运行。它做的第一件事是开始UNIX System V过程或Upstart init过程(具体取决于发行版和版本)。然后这个过程将负责启动Linux系统上所有其他进程。

System V init过程

System V init过程会读取/etc/inittab文件。inittab文件会列出系统的运行级(run level)

基于Red Hat发行版的Linux运行级别

运行级别 描述
0 关机
1 单用户模式
2 多用户模式,通常不支持网络
3 全功能多用户模式,支持网络
4 可定义用户
5 多用户模式,支持网络和图形化X Window会话
6 重启

基于Debian发行版的Linux运行级别(包括Ubuntu、Linux Mint等)

运行级别 描述
0 关机
1 单用户模式
2-5 多用户模式,支持网络和图形化X Window会话
6 重启

Ubuntu没有/etc/inittab文件,默认情况下会以运行级别2启动系统,想要修改,需要自行创建/etc/inittab文件

有些Linux发行版将开机脚本放在/etc/rc#.d,其中#代表运行级别。有些放在/etc/init.d,有些放在/etc/init.d/rc.d下面

Upstart init过程

Upstart不关注系统运行级别,而关注时间。

在Upstart中,系统开机称为开机事件(startup event)。

Upstart使用位于/etc/event.d或/etc/init目录下的文件来启动进程,具体取决于发行版和版本。

为了向后兼容,许多Upstart实现仍会调用较早的位于/etc/init.d以及/etc/rc#.d目录中的System V init脚本

定义自己的开机脚本

Linux本地开机文件位置

发行版 文件位置
debian /etc/init.d/rc.local
Fedora /etc/rc.d/rc.local
Mandriva /etc/rc.local
OpenSure /etc/init.d/boot.local
Ubuntu /etc/rc.local

可以修改本地开机文件,使用脚本时,必须指定脚本的全路径。

警告:不同Linux发行版在开机过程的不同事件执行该本地开机脚本。有时该脚本会在网络支持等启动前运行。

在新shell中启动

bash shell会用主目录下的两个文件.bash_profile和.bashrc来自动启动脚本并设置环境变量

当新shell是新的登录生成的话,bash shell会运行.bash_profile文件。可以把任何登录时要运行的脚本放到该文件中。

当新shell启动时,包括有新的登录的情况,bash shell 会运行.bashrc文件。

如果想为系统中所有用户运行一个脚本。大多数Linux发行版提供了/etc/bashrc文件

更多登录shell和非登录shell内容参见(#2

1.《Unix & Linux 大学教程》 - 第二十六章 进程和作业控制

2.《Unix & Linux 大学教程》 - 第十四章:使用shell:初始化文件

转贴请保留以下链接

本人blog地址

http://su1216.iteye.com/

http://blog.csdn.net/su1216/

上一篇:JavaScript高级程序设计(第三版)学习笔记20、21、23章


下一篇:升级到EntityFramework 6的注意事项