Linux 利用信号与系统中的进程进行通信。
重温 Linux 信号
Linux 系统和应用程序可以产生超过 30 个信号。在 shell 脚本编程时会遇到的最常见的 Linux 系统信号如下:
| 信号 | 值 | 描述 |
|---|---|---|
| 1 | SIGHUP | 挂起(hang up )进程 |
| 2 | SIGINT | 中断(interrupt)进程 |
| 3 | SIGQUIT | 停止(stop)进程 |
| 9 | SIGKILL | 无条件终止(terminate)进程 |
| 15 | SIGTERM | 尽可能终止进程 |
| 18 | SIGCONT | 继续运行停止的进程 |
| 19 | SIGSTOP | 无条件停止,但不终止进程 |
| 20 | SIGTSTP | 停止或暂停( pause ),但不终止进程 |
在默认情况下, bash shell 会忽略收到的任何 SIGQUIT(3)信号和 SIGTERM(15)信号(因此交互式 shell 才不会被意外终止)。但是,bash shell 会处理收到的所有 SIGHUP(1)信号和 SIGINT(2)信号。
如果收到了 SIGHUP 信号(比如在离开交互式 shell 时), bash shell 就会退出。但在退出之前,它会将 SIGHUP 信号传给所有由该 shell 启动的进程, 包括正在运行的shell 脚本。
随着收到 SIGINT 信号, shell 会被中断。 Linux 内核将不再为 shell 分配 CPU 处理时间。当出现这种情况时, shell 会将 SIGINT 信号传给由其启动的所有进程,以此告知出现的状况。
shell 会将这些信号传给 shell 脚本来处理。而 shell 脚本的默认行为是忽略这些信号,因为可能不利于脚本运行。
$ sleep 60
^Z
[1]+ Stopped sleep 60
$
$ sleep 70
^Z
[2]+ Stopped sleep 70
$
$ exit
logout
There are stopped jobs.
$
$ ps -l
F S UID PID PPID [...] TTY TIME CMD
0 S 1001 1509 1508 [...] pts/0 00:00:00 bash
0 T 1001 1532 1509 [...] pts/0 00:00:00 sleep
0 T 1001 1533 1509 [...] pts/0 00:00:00 sleep
0 R 1001 1534 1509 [...] pts/0 00:00:00 ps
$
trap commands signals
$ cat trapsignal.sh
#!/bin/bash
#Testing signal trapping
#
trap "echo ' Sorry! I have trapped Ctrl-C'" SIGINT
#
echo This is a test script.
#
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
echo "This is the end of test script."
exit
$
trap "" SIGINT
$ cat trapexit.sh
#!/bin/bash
#Testing exit trapping
#
trap "echo Goodbye..." EXIT
#
count=1
while [ $count -le 5 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
exit
$
$ cat trapmod.sh
#!/bin/bash
#Modifying a set trap
#
trap "echo ' Sorry...Ctrl-C is trapped.'" SIGINT
#
count=1
while [ $count -le 3 ]
do
echo "Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
trap "echo ' I have modified the trap!'" SIGINT
#
count=1
while [ $count -le 3 ]
do
echo "Second Loop #$count"
sleep 1
count=$[ $count + 1 ]
done
#
exit
$
trap "echo ' Sorry...Ctrl-C is trapped.'" SIGINT
trap -- SIGINT
$ ps -e
PID TTY TIME CMD
1 ? 00:00:02 systemd
2 ? 00:00:00 kthreadd
3 ? 00:00:00 rcu_gp
4 ? 00:00:00 rcu_par_gp
[...]
2585 pts/0 00:00:00 ps
$
$ ./backgroundscript.sh &
[1] 2595
$
[1]+ Done ./backgroundscript.sh
nohup command
$ nohup ./testAscript.sh &
[1] 1828
$ nohup: ignoring input and appending output to 'nohup.out'
jobs 是作业控制中的关键命令,该命令允许用户查看 shell 当前正在处理的作业。
通过 jobs 命令可以查看分配给 shell 的作业,例如:
$ jobs
[1]+ Stopped ./jobcontrol.sh
[2]- Running ./jobcontrol.sh > jobcontrol.out &
可以使用 jobs 命令的-l 选项(小写字母 l)查看作业的 PID。
$ jobs -l
[1]+ 1580 Stopped ./jobcontrol.sh
[2]- 1603 Running ./jobcontrol.sh > jobcontrol.out &
jobs 命令提供了一些命令行选项:
| 选项 | 描述 |
|---|---|
| -l | 列出进程的 PID 以及作业号 |
| -n | 只列出上次 shell 发出通知后状态发生改变的作业 |
| -p | 只列出作业的 PID |
| -r | 只列出运行中的作业 |
| -s | 只列出已停止的作业 |
如果需要删除已停止的作业, 那么使用 kill 命令向其 PID 发送 SIGKILL(9)信号即可。
$ jobs -l
[1]+ 1580 Stopped ./jobcontrol.sh
$
$ kill -9 1580
[1]+ Killed ./jobcontrol.sh
$
$ ./restartjob.sh
^Z
[1]+ Stopped ./restartjob.sh
$
$ bg
[1]+ ./restartjob.sh &
$
$ jobs
[1]+ Running ./restartjob.sh &
$
$ jobs
$
$ ./restartjob.sh
^Z
[1]+ Stopped ./restartjob.sh
$
$ ./newrestartjob.sh
^Z
[2]+ Stopped ./newrestartjob.sh
$
$ bg 2
[2]+ ./newrestartjob.sh &
$
$ jobs
[1]+ Stopped ./restartjob.sh
[2]- Running ./newrestartjob.sh &
$
$ jobs
[1]+ Stopped ./restartjob.sh
[2]- Running ./newrestartjob.sh &
$
$ fg 2
./newrestartjob.sh
This is the script's end.
$
$ nice -n 10 ./jobcontrol.sh > jobcontrol.out &
[2] 16462
$
$ ps -p 16462 -o pid,ppid,ni,cmd
PID PPID NI CMD
16462 1630 10 /bin/bash ./jobcontrol.sh
$
$ ./jobcontrol.sh > jobcontrol.out &
[2] 16642
$
$ ps -p 16642 -o pid,ppid,ni,cmd
PID PPID NI CMD
16642 1630 0 /bin/bash ./jobcontrol.sh
$
$ renice -n 10 -p 16642
16642 (process ID) old priority 0, new priority 10
$
$ ps -p 16642 -o pid,ppid,ni,cmd
PID PPID NI CMD
16642 1630 10 /bin/bash ./jobcontrol.sh
$
at [-f filename] time
$ cat tryat.sh
#!/bin/bash
# Trying out the at command
#
echo "This script ran at $(date +%B%d,%T)"
echo
echo "This script is using the $SHELL shell."
echo
sleep 5
echo "This is the script's end."
#
exit
$
$ at -f tryat.sh now
warning: commands will be executed using /bin/sh
job 3 at Thu Jun 18 16:23:00 2020
$
$ cat tryatout.sh
#!/bin/bash
# Trying out the at command redirecting output
#
outfile=$HOME/scripts/tryat.out
#
echo "This script ran at $(date +%B%d,%T)" > $outfile
echo >> $outfile
echo "This script is using the $SHELL shell." >> $outfile
echo >> $outfile
sleep 5
echo "This is the script's end." >> $outfile
#
exit
$
$ at -M -f tryatout.sh now
warning: commands will be executed using /bin/sh
job 4 at Thu Jun 18 16:48:00 2020
$
$ cat $HOME/scripts/tryat.out
This script ran at June18,16:48:21
This script is using the /bin/bash shell.
This is the script's end.
$
$ at -M -f tryatout.sh teatime
warning: commands will be executed using /bin/sh
job 5 at Fri Jun 19 16:00:00 2020
$
$ at -M -f tryatout.sh tomorrow
warning: commands will be executed using /bin/sh
job 6 at Fri Jun 19 16:53:00 2020
$ atq
1 Thu Jun 18 16:11:00 2020 a christine
5 Fri Jun 19 16:00:00 2020 a christine
6 Fri Jun 19 16:53:00 2020 a christine
$ atq
1 Thu Jun 18 16:11:00 2020 a christine
5 Fri Jun 19 16:00:00 2020 a christine
6 Fri Jun 19 16:53:00 2020 a christine
$ atrm 5
$ atq
1 Thu Jun 18 16:11:00 2020 a christine
6 Fri Jun 19 16:53:00 2020 a christine
minutepasthour hourofday dayofmonth month dayofweek command
15 10 * * * command
15 16 * * 1 command
00 12 1 * * command
00 12 28-31 * * if [ "$(date +%d -d tomorrow)" = 01 ] ; then command ; fi
这行脚本会在每天中午 12 点检查当天是不是当月的最后一天(28~31),如果是,就由 cron 执行 command。另一种方法是将 command 替换成一个控制脚本(controlling script),在可能是每月最后一 天的时候运行。控制脚本包含 if-then 语句,用于检查第二天是否为某个月的第一天。如果是,则由控制脚本发出命令,执行必须在当月最后一天执行的内容。
15 10 * * * /home/christine/backup.sh > backup.out
$ crontab -l
no crontab for christine
$
$ ls /etc/cron.*ly
/etc/cron.daily:
0anacron apt-compat cracklib-runtime logrotate [...]
apport bsdmainutils dpkg man-db [...]
/etc/cron.hourly:
/etc/cron.monthly:
0anacron
/etc/cron.weekly:
0anacron man-db update-notifier-common
$
$ ls /var/spool/anacron
cron.daily cron.monthly cron.weekly
$
$ sudo cat /var/spool/anacron/cron.daily
[sudo] password for christine:
20200619
$
period delay identifier command
source $scriptToRun > $scriptOutput & #Run script in background