信号和 trap
如果亲自运行清单 7 的 getclock.sh 脚本,并在运行脚本时关闭时钟窗口,脚本会继续运行但是会在每次尝试获取时钟窗口快照时打印错误信息。类似地,如果运行清单 4 中的 runclock3.sh 脚本,并在运行脚本的终端窗口中按下 Ctrl-c,脚本会立即终止,但是没有关闭时钟。要解决这类问题,脚本需要能够捕获或 trap 终止子进程 一节讨论的信号。
如果在后台中执行 runclock3.sh 并同时运行 ps -f 命令,将看到类似清单 8 中的输出。
清单 8. runclock3.sh 的进程信息
[ian@attic4 ~]$ ./runclock3.sh 20s&
[1] 10101
[ian@attic4 ~]$ My PID=10101. Clock’s PID=10102
UID PID PPID C STIME TTY STAT TIME CMD
ian 10102 10101 0 06:37 pts/1 S 0:00 xclock
ps -f
UID PID PPID C STIME TTY TIME CMD
ian 4598 12455 0 Jul29 pts/1 00:00:00 bash
ian 10101 4598 0 06:37 pts/1 00:00:00 /bin/bash ./runclock3.sh 20s
ian 10102 10101 0 06:37 pts/1 00:00:00 xclock
ian 10104 10101 0 06:37 pts/1 00:00:00 sleep 20s
ian 10105 4598 0 06:37 pts/1 00:00:00 ps -f
[ian@attic4 ~]$ All done
[1]+ Done ./runclock3.sh 20s
注意:ps -f 输出具有三个和 runclock3.sh 进程(PID 10101)相关的条目。特别是,sleep 命令是作为独立进程运行的。处理 xclock 进程的过早终止或使用 Ctrl-c 终止运行脚本的方法是捕获这些信号并使用 kill 命令终止 sleep 命令。
有多种方法可以确定使用 sleep 命令的进程。清单 9 展示了最新的脚本,runclock4.sh。注意以下几点:
sleep 命令在后台显式运行。
wait 命令用于等待 sleep 命令的终止。
第一个 trap 命令将使 stopsleep 函数在接受到 SIGCHLD、SIGINT 或 SIGTERM 信号时运行。sleeper 进程的 PID 将作为参数传递。
stopsleep 函数将作为信号结果运行。它将打印状态信息并将 sleep 命令发送给 SIGINT 信号。
当 sleep 命令终止后,将完成 wait 命令。然后清除 trap,终止 xclock 命令。
清单 9. 使用 runclock4.sh 以 trap 信号
[ian@attic4 ~]$ cat runclock4.sh
#!/bin/bash
stopsleep() {
sleeppid=$1
echo "$(date +’%T’) Awaken $sleeppid!"
kill -s SIGINT $sleeppid >/dev/null 2>&1
}
runtime=${1:-10m}
mypid=$$
# Enable immediate notification of SIGCHLD
set -bm
# Run xclock in background
xclock&
clockpid=$!
#Sleep for the specified time.
sleep $runtime&
sleeppid=$!
echo "$(date +’%T’) My PID=$mypid. Clock’s PID=$clockpid sleep PID=$sleeppid"
# Set a trap
trap ’stopsleep $sleeppid’ CHLD INT TERM
# Wait for sleeper to awaken
wait $sleeppid
# Disable traps
trap SIGCHLD
trap SIGINT
trap SIGTERM
# Clean up child (if still running)
echo "$(date +’%T’) terminating"
kill -s SIGTERM $clockpid >/dev/null 2>&1 && echo "$(date +’%T’) Stopping $clockpid"
echo "$(date +’%T’) All done"
清单 10 展示了三次运行 runclock4.sh 的输出。第一次,一切正常完成。第二次,xclock 提前终止。第三次,使用 Ctrl-c 终止 shell 脚本。
清单 10. 使用不同方法停止 runclock4.sh
[ian@attic4 ~]$ ./runclock4.sh 20s
09:09:39 My PID=11637. Clock’s PID=11638 sleep PID=11639
09:09:59 Awaken 11639!
09:09:59 terminating
09:09:59 Stopping 11638
09:09:59 All done
[ian@attic4 ~]$ ./runclock4.sh 20s
09:10:08 My PID=11648. Clock’s PID=11649 sleep PID=11650
09:10:12 Awaken 11650!
09:10:12 Awaken 11650!
[2]+ Interrupt sleep $runtime
09:10:12 terminating
09:10:12 All done
[ian@attic4 ~]$ ./runclock4.sh 20s
09:10:19 My PID=11659. Clock’s PID=11660 sleep PID=11661
09:10:22 Awaken 11661!
09:10:22 Awaken 11661!
09:10:22 Awaken 11661!
[2]+ Interrupt sleep $runtime
09:10:22 terminating
09:10:22 Stopping 11660
./runclock4.sh: line 31: 11660 Terminated xclock
09:10:22 All done
注意,“Awaken” 消息显示了 stopsleep 函数的调用次数。如果想细究其原因,可以尝试针对每种终止类型获得该函数的单独副本,研究产生额外调用的原因。
您还将注意到一些业务控制信息将通知 xclock 命令和 sleep 命令的终止。当使用默认的 bash 终端设置在后台运行作业时,bash 通常会捕获 SIGCHLD 信号并在打印下一个终端输出行之后 打印消息。脚本中的 set -bm 命令将通知 bash 立即报告 SIGCHLD 信号并启用业务控制监视。下一小节的闹钟示例将展示如何禁用这些消息。
闹钟
我们最后一个应用将返回到本文最初的问题上:如何录制广播节目。我们将实际构建一个闹钟。如果当地法律允许录制这类内容,您可以构建一个录制程序而不需添加 vsound 这样的程序。
对于这个示例,我们将使用 GNOME rhythmbox 应用程序进行演示。即使您使用的是其他媒体播放器,仍然可从该示例获益。
闹钟应该能够发出您希望的任何声音,包括播放自己的 CD、MP3 文件。在北卡罗莱纳州中部,我们具有一个 广播电台 WCPE,该电台全天播放古典音乐。除了广播外,WCPE 还以不同的格式连入互联网,包括 Ogg Vorbis。您可以选择自己喜欢的流媒体源。
要从 X Windows 终端会话中启动 rhythmbox,播放 WCPE Ogg Vorbis 流,需要使用清单 11 所示的命令。
清单 11. 使用 WCPE Ogg Vorbis 流启动 rhythmbox
rhythmbox --play http://audio-ogg.ibiblio.org:8000/wcpe.ogg
rhythmbox 的第一个有趣特性是运行中的程序可以对命令作出响应,包括表示终止的命令。因此,这里不需要使用 kill 命令终止应用程序,但是您可以根据需要使用该命令。
第二个有趣特性是,和我们在前面示例中使用的时钟一样,大多数媒体播放器都需要使用图形化表示。通常,当您不在计算机附近时,一般使用 cron 和 at 实用程序运行命令,这些程序通常会认为预定的作业不能 访问播放器的图形化表示。rhythmbox 命令允许您指定要使用的图形化表示。您很可能需要进行登录,即使屏幕被锁定,但是您可以亲自研究这些不同变化。清单 12 展示了 alarmclock.sh 脚本,可用作闹钟工作的基础。其中包含指定运行时间的单个参数,默认值为一小时。
清单 12. 闹钟 - alarmclock.sh
[ian@attic4 ~]$ cat alarmclock.sh
#!/bin/bash
cleanup () {
mypid=$1
echo "$(date +’%T’) Finding child pids"
ps -eo ppid=,pid=,cmd= --no-heading | grep "^ *$mypid"
ps $playerpid >/dev/null 2>&1 && {
echo "$(date +’%T’) Killing rhythmbox";
rhythmbox --display :0.0 -quit;
echo "$(date +’%T’) Killing rhythmbox done";
}
}
stopsleep() {
sleeppid=$1
echo "$(date +’%T’) stopping $sleeppid"
set +bm
kill $sleeppid >/dev/null 2>&1
}
runtime=${1:-1h}
mypid=$$
set -bm
rhythmbox --display :0.0 --play http://audio-ogg.ibiblio.org:8000/wcpe.ogg&
playerpid=$!
sleep $runtime& >/dev/null 2>&1
sleeppid=$!
echo "$(date +’%T’) mypid=$mypid player pid=$playerpid sleeppid=$sleeppid"
trap ’stopsleep $sleeppid’ CHLD INT TERM
wait $sleeppid
echo "$(date +’%T’) terminating"
trap SIGCHLD
trap SIGINT
trap SIGTERM
cleanup $mypid final
wait
注意 stopsleep 函数中 set +bm 的用法,它将重置作业控制设置并禁止显示 runclock4.sh 中出现的消息。
清单 13 展示了一个示例 crontab,它将在每星期周一到周五的上午六点到七点、每个周六上午七点到九点、以及每个周日上午八点半到十点之间运行闹钟程序。
清单 13. 运行闹钟程序的示例 crontab
0 6 * * 1-6 /home/ian/alarmclock.sh 1h
0 7 * * 7 /home/ian/alarmclock.sh 2h
30 8 * * 0 /home/ian/alarmclock.sh 90m
请参考前一篇技巧文章 Job scheduling with cron and at,学习如何针对新的闹钟程序设置 crontab。
在更复杂任务中,可能具有多个子线程。cleanup 例程将展示如何使用 ps 命令查找脚本进程的子进程。您可以在此应用之上进行扩展,循环遍历任意的子进程集合并一一终止它们。
.
分页: [
1] [
2]
TAG:
Linux作业