2005年05月04日

研究过Linux内核的人都知道,Linux内核开发一般是分两个支流(branch)的,一个是第二个数字为偶数的版本号如2.4.x,是稳定性比较好的版本;另一个是第二个数字为奇数的版本如2.5.x,里面有很多新功能,但是不稳定。

   

但是在去年的kernel summit
会中,大家决定了不需要为2.6内核引入一个2.7的支流。因为Linus Torvalds现在和Andrew
Morton现在合作的也很愉快,他们可以先把新的东西放到Andrew的-mm内核中,然后进行测试,没有问题的话再并入2.6内核中。对于2.6内
核,有人总结说:



"Andrew’s vision, as expressed at the summit, is that the mainline
kernel will be the fastest and most feature-rich kernel around, but
not, necessarily, the most stable. Final stabilization is to be done by
distributors (as happens now, really), but the distributors are
expected to merge their patches quickly."



也就是Linux内核的稳定性交给了RedHat之类的开发商。对于开发商,这自然是好事,他们买的本来就是服务,现在就有更多的理由让你掏钱买它的发行版了。对于我们这种个人用户,应该也不算坏事,至少我们总是会有fedora、gentoo之类的Linux可用,虽然性能、稳定性等发面肯定比RHEL之类的要差一些;对于喜欢研究内核的,自然是好事了,因为总是可以最快的得到最新的功能特性。

之所以在十个月之后再提到这件事,主要是因为最近LKML的一些列邮件基本上给新的开发模式定下了结论:更好的设计,更多的测试,更快速的更新,以及不会引入一些在将奇数版本的功能合并入偶数版本时常出现的莫名其妙的错误(因为合并的内容很多,往往这类错误很难找到错误源)。总之:

"Things get merged one change at a time, and stabilised one change at a
time. This is a big change from the even/odd numbered kernel series,
where sometimes a bug crops up without anybody knowing exactly what
change introduced it. The current development model seems to go much
smoother than anything I’ve seen before."

2005年04月30日

   
你是不是常常想,要是可以把自己blog上的内容备份下来就好了?至少我是的,虽然我的blog只是自娱自乐的玩票性质,不过毕竟全是自己的咚咚,备份一
下比较放心嘛:)
不过donews好像不具备备份这个功能,其他很多的blog商也不提供备份功能,就我所知的,只有当年我们的小百合可以下载blog,可惜小百合已经被
无耻的教育部废掉了。。。怎么办?看来只有自己来备份啦。

    以前用过几款下载网页的工具,但要么是共享软件要么效果就是不行。直到最近,发现了httrack这个好咚咚!而且这是款免费软件,不会有那些让人讨厌的30天试用期什么的。初看起来,这个软件的界面非常简陋,但功能一点也不含糊,使用起来也非常方便。这个款软件还提供了二十多个语言的界面,其中就包括简体中文。而最最主要的,是用它下载的网页效果非常好,只要设置好了,基本上你想要下的它都会帮你下好,你不想要的它也不回去画蛇添足;下载下来的网页也基本不会有死链接什么的。
    下面来说说它的用法,其实很简单,按他的提示一步步做就是了。首先点“下一步”新建一个工程,设定工程名和保存路径。然后要设定要下载的网站的URL(可以多个),还有一些操作让你选择,备份blog的话一般用“下载网站”,或者更新以前备份的内容的话就用“更新已有镜像”,其他选项还有“下载个别文件”、“下载页面中所有站点”等,还有个用来测试书签的“测试页面中的链接”。这个页面里还有个选项按钮,选项反正都是中文的,按自己需求进行设定吧。其实一般默认的选项也就可以了。下一个页面选择使用哪个网络连接,可以设定是否要“完成时断开连接”或者“完成时关闭机器”等。最后按下完成按钮,然后你就可以去喝茶了,等你回来,你的blog的所有内容就全在你硬盘上了!怎么样,很方便吧?

httrack的截图

     httrack这个软件可以在www.onlinedown.com下载到。

2005年04月26日

1.1 概念

1.1.1  
产生条件

用户按下了某些终端键(如Ctrl-c)时

硬件异常产生信号

进程用kill(2)函数给另一个进程或进程组发送信号

用户用kill(1)命令发送信号给进程

某种软件条件发生(如SIGPIPE等信号)

1.1.2  
动作

忽略此信号

捕捉信号

执行系统默认动作

1.1.3  
信号分类

不可靠信号,即普通信号。如果一个进程接收到一个普通信号,而进程的未决信号集(pending signals)中存在相同的信号,则这个新发送的信号丢失。即同一时间进程的未决信号集中只可能有一个普通信号。文件/usr/include/bits/signum.h中定义了所有普通信号。

可靠信号,即实时信号。信号值在SIGRTMIN(32)SIGRTMAX(64)之间的信号。实时信号每次都会被加入到未决信号集中。

1.2
 signal函数

1.2.1  
函数原形

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum,
sighandler_t handler);

1.2.2  
说明

handler的值可以是SIG_IGN(忽略信号)SIG_DFL(默认行为)或者是用户自定义的一个信号处理函数。返回的是之前的信号处理函数指针或者SIG_ERR

1.3
 中断的系统调用

如果进程执行一个低速系统调用而阻塞期间捕捉到一个信号,该系统调用会被中断返回出错,errno设为EINTR4.2BSD引入了自动再启动的系统调用包括:readreadvwritewritevioctlwaitwaitpid

1.4
 可再入函数

不可再入函数一般是指:a)使用了静态数据结构,b)调用了mallocfreec)标准I/O函数。如果在信号处理函数种调用不可再入函数,则可能会引起错误。

即使是可载入函数,因为errno每个进程只有一个,也有可能引起错误。因此一般调用使用errno的系统调用之前都要保存errno,在调用后再恢复。

1.5
 发送等待信号

1.5.1  
相关函数

int kill(pid_t pid, int sig);

int raise(int sig);

unsigned int alarm(unsigned int
seconds);

int pause(void);

1.5.2  
说明

kill可以给人以进程发送信号,raise则给当前进程发送信号。

alarm则设置一个闹钟值,所设置的时间超过后,产生一个SIGALARM信号,其默认动作时终止该进程。

pause是当前进程挂起直到接收到一个信号。

实例:

sleep的不完整的实现:signals/sleep1.c

1.6
 信号集

1.6.1  
相关函数

int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int
signum);

int sigdelset(sigset_t *set, int
signum);

int sigismember(const sigset_t *set,
int signum);

1.6.2  
说明

sigset是表示多个信号的数据类型。sigemptyset函数使信号集set不包含任何信号,sigfillset使set包含所有的信号。sigaddsetsigdelset添加和删除一个信号。sigismember检测信号集是否包含特定的信号。

1.7
 POSIX信号处理函数

1.7.1  
相关函数

int sigaction(int signum, const
struct sigaction *act, struct sigaction *oldact);

int sigprocmask(int how, const
sigset_t *set, sigset_t *oldset);

int sigpending(sigset_t *set);

int sigsuspend(const sigset_t *mask);

1.7.2  
说明

sigprocmask设定进程信号屏蔽字,参数how有三个可选项:SIG_BLOCKSIG_UNBLOCKSIG_SETMASK

sigpending返回对于调用进程被阻塞的和当前未决的信号集。

sigaction取代了早期的signal函数,用于设定与信号相关联的处理动作。参数act的类型为:

struct sigaction {

void (*sa_handler)(int);

void (*sa_sigaction)(int, siginfo_t
*, void *);

sigset_t sa_mask;

int sa_flags;

}

因为可能会用union实现,只能给两个信号处理函数指针sa_handlersa_sigaction中的一个赋值。sa_mask是信号处理函数执行时应该被屏蔽的信号。flag则是一些标识,要使用sa_sigaction则必须设定SA_SIGINFO,这样信号的一些信息及传递的变量才会被设置到sa_sigaction的第二个参数中。关于sa_sigaction第二个参数的使用,参看man文档。

sigsuspend以参数mask为屏蔽字是进程挂起直到接收到一个信号。相当于以原子操作实现:

    sigprocmask(SIG_SETMASK,
&mask, &oldmask);

    pause();

    sigprocmask(SIG_SETMASK,
&oldmask, NULL);

实例:

父子进程同步的实现:lib.rhlin
/tellwait.c

sigsuspend保护临界区:signals/suspend1.c

sigsuspend等待一个全局变量被设置:signals/suspend2.c

abort的实现:signals/abort.c

systemPOSIX.2实现:signals/system.c

处理SIGTSTP信号(Ctrl-z)signals/sigtstp.c

1.8
 非局部跳转

1.8.1  
相关函数

int sigsetjmp(sigjmp_buf env, int
savesigs);

void siglongjmp(sigjmp_buf env, int
val);

1.8.2  
说明

这两个函数和setjmplongjmp之间的唯一区别是sigsetjmp增加了一个参数。savesigs为非0,则sigsetjmpenv中保存进程的当前屏蔽字。

实例:

信号屏蔽及siglongjmp的使用:signals/mask.c

1.1
 进程标识

1.1.1  
相关函数

pid_t getpid(void);

pid_t getppid(void);

pid_t getuid(void);

pid_t geteuid(void);

pid_t getgid(void);

pid_t getegid(void);

1.1.2  
说明

上面这些函数分别用于获得当前进程的:进程ID、父进程ID、实际用户ID、有效用户ID、实际组ID和有效组ID

1.2
 创建子进程

1.2.1  
相关函数

pid_t fork(void);

pid_t vfork(void);

1.2.2  
说明

除了init等系统进程以外,创建进程的唯一方法就是调用fork函数。调用fork后,子进程完全复制父进程。对子进程,fork返回0;对父进程,fork返回子进程的pidvfork并不复制父进程的进程空间,在子进程调用exec或者exit之前,子进程在父进程的进程空间运行。

1.2.3  
文件共享

子进程继承了父进程打开的文件描述符及其文件位移量,所以可能产生冲突,需要进行同步,也可以父进程等待子进程完成或者父、子进程执行不同的程序段以避免冲突。

1.2.4  
fork的用法

父进程希望复制自己,使父、子进程执行不同的代码段。

一个进程要调用一个不同的程序。

1.3
 等待进程终止

1.3.1  
相关函数

pid_t wait(int *status);

pid_t waitpid(pid_t pid, int *status,
int options);

1.3.2  
说明

status返回所等待进程的返回状态,可以用一系列的宏来检查这个状态。

调用这两个函数后,当前进程可能会:

阻塞(如果其所以子进程都还在运行)

带子进程的终止状态立刻返回(如果一个子进程以终止,正在等待父进程存取其终止状态)

出错立刻返回(如果它没有任何子进程)

1.3.3  
区别

在一个子进程终止前,wait是其调用者阻塞,而waitpid有一选择项可以是调用者不阻塞。

waitpid并不等待第一个终止的子进程,它有选择项可以控制它所等待的进程。

1.4
 执行程序

1.4.1  
相关函数

int 
execve(const  char  *path,  char  *const  argv [], char *const envp[]);

int execl(const char *path,
const char *arg, …);

int execlp(const char *file,
const char *arg, …);

int 
execle(const  char  *path,  const  char  *arg 
, …, char * const envp[]);

int execv(const char *path,
char *const argv[]);

int execvp(const char *file,
char *const argv[]);

1.4.2  
区别

以上6个函数中execve时系统调用,其他的则是库函数。

根据函数名中的字符:

p表示该函数取文件名作为参数,并用PATH环境变量寻找可执行文件。没有字符p则表示参数为完全路径名。

l表示该函数取一个参数表,它和字符v互斥。v表示该函数取一个参数的数组argv[]

e表示该函数取envp[]数组作为新程序的环境变量表,没有e则使用当前进程的环境变量表。

1.5
 设置用户ID和组ID

1.5.1  
相关函数

int setuid(uid_t uid);

int setgid(uid_t gid);

 

int seteuid(uid_t euid);

int setegid(gid_t egid);

 

int setreuid(uid_t ruid, uid_t
euid);

int setregid(gid_t rgid, gid_t
egid);

1.5.2  
规则

关于用setuid函数改变用户ID的规则(也同样适用于setgid函数):

若进程具有root特权,则setuidRUIDEUID以及SUID都设置为uid

若进程没有root权限,但uid等于RUID或者SUID,则把EUID设置为uid

上述条件都不满足,则返回出错,errno设为EPERM

1.6
 执行shell程序

1.6.1  
相关函数

int system(const char *cmd);

1.6.2  
说明

相当于执行/bin/sh –c cmd.

对于有suidsgid权限的程序不能使用此函数,会产生安全漏洞。

1.7
 用户标识

1.7.1  
相关函数

char *getlogin(void);

1.7.2  
说明

得到运行该程序的用户的登录名。对于未连接到终端的进程,本函数会失败。

1.8
 进程时间

1.8.1  
相关函数

clock_t times(struct tms *buf);

1.8.2  
说明

用于得到进程自己以及终止子进程的用户CPU时间和系统CPU时间。

2005年04月22日

以前经常去IBM develperWork China,上面又很多非常好的文章,更新也比较快。但是有一个问题让我很郁闷,就是它上面的搜索和分类做的实在太烂太差了。比如主题分类,经常出现同一个专栏出现在好几个不同的主题里;而有的主题下面的文章和主题根本就不搭界。搜索就更烂了,经常搜出来一堆乱七八糟的和我要搜的东西风马牛不相及的东西,弄我得没办法,只能到google上用site关键词查IBM develperWork China上的文章。

前段时间比较忙所以都没怎么上IBM develperWork China,今天上去一看,嘿,全都变了。现在的IBM develperWork China从界面到功能都越来越向英文的原站靠拢了。界面从远来得深蓝色变成了淡蓝色,显的更加清爽一点。左边得菜单好像也变了,和英文原站一样,只用一个列表列出了所有栏目。更重要的是分类好像重新整理了,比原来更清除,这样找起某一类的文章就很方便了;搜索的结果虽然还没达到我得期望,不过也比原来好多了。不过有些东西好像还没做完,比如它说文章可以按时间或者难度排序,但我看现在好像只能按时间排序。英文原站还可以按标题排序,这应该是个很简单得功能,不知道为什么没做。

总之,这些改进虽然来得完了一点,但总比没有好,所以还是要赞一下的!我想每个程序员都会希望IBM develperWork China可以越来越好的吧:)

2005年04月21日

程序的结构应遵循7个证明原理,以确保程序的正确性、健壮性、灵活性、可重用和可读性等。

1.1     单纯原理

所谓单纯性原理是指变量或指针等的使用遵循单一化的原则,即为不同的用途使用不同的变量或指针。采用了单纯原理,程序就可以明确的反映实际的问题。

如下面的程序,从一个文件中读入数据放到另一个文件中:

FILE* fp = NULL;

fp = fopen(m_strSrcFilePath, "r");

/* 读数据 */

fclose(fp);

/* 对数据进行处理 */

fp = fopen(m_strDesFilePath, "w");

/* 写数据 */

同一个指针变量fp在一个子程序中被用来作为两个不同文件的指针,虽然没有错误,但容易造成对fp理解困难,所以最好这样:

FILE* fpSrc =
NULL; /*
源文件 */

FILE* fpDes =
NULL; /*
目标文件 */

fpSrc =
fopen(m_strSrcFilePath, "r");

/* 读数据 */

fclose(fp);

/* 对数据进行处理 */

fpDes =
fopen(m_strDesFilePath, "w");

/* 写数据 */

1.2    同型原理

同型原理是指相同逻辑的地方应该有相同的结构;能复用的代码就不要重写,用宏或者子程序实现。

例如下面这两个循环:

for(i = 0; i
< m_aLinkMan.GetSize(); i++);

for(i = 0; i
<= m_aLinkMan.GetSize()-1; i++);

仔细一看,会发现这两个循环其实是一样的,但对它们为什么形式不同会感到费解,引起阅读的障碍。

1.3    对称原理

对称原理是指成对的操作应该成对地出现,并且出现在对称的位置上。比如:内存的申请与释放、文件的打开与关闭、if语句是否需要相应的else语句等。各系统、组成成分或模块都应遵循对称原理。

Linux下,对称原理主要表现为以下几点:

-         
malloc等分配内存的函数和free函数必须成对出现,而且必须保证释放掉指针不再被使用。

-         
open/fopen等打开文件的函数和close/fclose函数必须成对出现,而且必须保证关闭的文件描述符或者流指针不再被使用。

-         
使用signal或者sigaction设置信号处理程序时,应该先保存旧的信号处理程序,等处理完毕进行恢复。

-         
还有其他一些函数也必须成对使用,如mmap/munmappthread_mutex_init/
pthread_mutex_destroy
sem_init/sem_destroy等等。

-         
对于程序中的模块、函数,如有必要,也应该保持对称。

1.4    层次原理

层次原理是形状的层次美原理。例如,意识到事物的主从关系,前后关系,本末关系等层次关系,追求事物应有的形态。必须使各个层次详细化、数据抽象化。层次的规定要彻底。

例如有如下代码:

struct p1 {};

struct p2 {

struct p1 *pp1;

};

struct p2 *pp2;

可以看出结构体p1和p2之间又很明显的层次关系,分配内存时,应先为pp2分配内存,然后为pp2->pp1分配内存;释放时,应该先释放pp2->pp1的内存,然后再释放pp2的内存。

再例如,进行多线程编程时,经常会需要进行互斥或者是信号量操作。那么应该先调用pthread_mutex_lock设置mutex进行互斥,然后再调用sem_wait进行信号量操作。若是顺序弄反了,则会引入一个race condition,从而可能产生死锁。

以上的例子只是比较简单的情况,在程序中可能存在非常复杂的情况,要注意判别。

1.5    线性原理

线性原理是指事物的形状是由直线描绘出来的。例如,某个功能,是由几个功能的重叠组合加以实现的。因此,在程序中应该尽量不使用GOTO,SCHEDULE,POST/WAIT等功能。

1.6    明证原理

逻辑的明证性原理,即应该努力的说明一些不太清除的逻辑,并且使其具有说服力。

实例:检查接收到的数据中带有的数据序号的连续性。数据序号是用2个byte表示的,从0开始,之后每个递增1,达到 0xffff后,再重头开始。还有,在0xffff上加上1就会变成0。

 

/*

* nowseq : 接受通知后的数据编号

* oldseq : 接受通知前的数据编号

* 已接受通知的数据编号,不等于接受通知前的数据编号加一,或者

* 被通知前的数据的编号为0xffff,现在已接受通知的编号不为0

*/

if ((nowseq != oldseq + 1)  ||

(oldseq == 0xffff 
&&

nowseq != 0 )) {

    错误处理;

}

这个程序好像是正确的,可是,它有很多否定形的逻辑式,所以比较难以理解。首先,用肯定形式(也就是正常的条件)表示,再加上”!”,作为错误的条件,这样做可以使人放心。按照这样的做法操作,程序会形成下述情况,比原本的程序易懂。

if (!((nowseq
== oldseq + 1) ||

(nowseq == 0 && oldseq == 0xffff))) {

错误处理;

}

原程序中有逻辑错误。也就是, nowseq0oldseq0xffff的时候,满足了if的第一个条件项的nowseq!=oldseq+1,所以虽然是正确的情况却被当成了错误。

1.7    安全原理

安全原理是指意识到必然性的原理。例如,忽略没有必然性或者含糊不清的地方,用安全的方法、思想来设计。

举例,有调用程序(Caller)和被调用程序(Callee),两者间调用接口用的参数list是P。Caller没有把P内的Pi域中的初值设定为‘0′,所以Callee侧是异常操作。

这个故障是在修正程序的时候产生的,调查故障的原因,发现原程序中也有相同的调用部分,那里是正确的代码。也就是说,把P全部清为0,(由此,Pi域的初值就设定为‘0′ ),然后调用Callee

修正程序,通过追加一个调用,修正的负责人模仿先前的调用部分,进行了编码,认为把P全体清为0是没有必要的,(可能是想把程序的步骤减少),于是,P全体清0的步骤被省略了。可是,不幸的是, Pi中也混入了错误。

这个例子中的问题是没有照搬正确操作的代码。虽然曾经把它清为0,可是不能保证之后它一直为0。使用前,把参数list清为0,是coding的基本原则。这样做是安全的,所以符合安全原理。

2005年04月13日

本来我开发JSP web应用都是在自己电脑上做。开一个eclipse开一个tomcat,然后就会发现电脑变得奇慢无比,经常切换一下桌面要半分钟,打一个字母也要半分钟后eclipse才有反应。终于我无法忍受了,组内现在有这个多Linux服务器,不用白不用。这也不是什么新方法,不过我还是要总结一下。

首先需要两个东西:

  • jdk-1_5_0_02-linux-i586.bin
  • jakarta-tomcat-5.5.7.tar.gz

我用的是tomcat最x新的5.5.7版,需要jdk1.5。如果tomcat是5.0或者更低,那么jdk1.4也就可以了。

将这两个文件拷贝到/usr/local/(当然其他目录也行)目录下,给bin文件加上可执行权限并运行,在解压tomcat包。这样生成了jdk-1_5_0_02和jakarta-tomcat-5.5.7两个目录,建立两个符号连接到此目录以方便指定目录。命令如下:
    # chmod +x jdk-1_5_0_02-linux-i586.bin
    # ./jdk-1_5_0_02-linux-i586.bin
    ……
    # tar xzf jakarta-tomcat-5.5.7.tar.gz
    # ln -s jdk-1_5_0_02 j2sdk
    # ln -s jakarta-tomcat-5.5.7 tomcat
到此jdk和tomcat就算安装完成了,但下面还需要导出JAVA_HOME、TOMCAT_HOME、CLASSPATH等环境变量,可以在/etc/profile中添加如下语句:
    JAVA_HOME=/usr/local/j2sdk
    JRE_HOME=$JAVA_HOME/jre
    TOMCAT_HOME=/usr/local/tomcat
    CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib
    PATH=$JAVA_HOME/bin:$PATH
    export JAVA_HOME JRE_HOME TOMCAT_HOME CLASSPATH PATH
重起机器(直接在bash下export上述变量也行),运行tomcat/bin目录下的startup.sh,然后在本机开个浏览器连接到服务器的地址试试,不要玩了指定8080端口,不出意外,应该可以看到tomcat的主页。

那么如何将服务器上的tomcat和本机的eclipse协同开发web应用呢?有两种方法:

  1. 在Linux服务器上新建一个目录用samba将其共享,然后在本机上将这个目录映射为网络驱动器,在eclispe中将工程checkout到这个目录中就可以进行开发了。测试的话只要将tomcat的server.xml中添加一个context指向这个共享的目录即可。
  2. 直接将工程checkout到本机进行开发。然后将这个工程的目录共享,在Linux服务器上用smbmount将其作为一个目录加载,在tomcat的server.xml中添加一个context指向这个被加载的目录即可。

自从我把tomcat移到Linux服务器上后,进行JSP开发时的速度比原来快了2到3倍,不用再坐在那儿郁闷的等着键入的字母在屏幕上显示出来了,正是爽多了。

2005年04月11日

很久没来了,因为实在受不了donews的超高的速度和超高的出错率。记得上次想发文章的时候弄了两个小时都没搞定,总是出错。一气之下,决定不上donews了。不过现在感觉速度好像很快,也不怎么出错,有空的话可以继续尝试blog一下,哈哈~~

2005年02月24日

我们做测试的时候,被测试的程序经常会死掉,然后就得用kill命令把程序杀掉。然而郁闷的是,这个程序是有好多进程通过进程见通信实现的,于是每次都得用ps命令找到这些进程,然后用kill -9一个一个的杀掉。后来不记得在什么地方看到这么个脚本,把它修改了一下就可以在Linux上用了。有这个脚本,杀起进程来可方便多了!

它是有两个脚本组成的。pick用于依次显示参数然后等待用户输入,若输入y则echo参数;若输入q则退出整个程序;若是其他其他任何字符,则程序直接跳到下一个参数。zap是主要的脚本,用法是
    zap [-SIGNAL] <pattern>
signal是kill里的那个参数,可以是-9, -TERM等等,pattern则是你要杀死的进程的关键字,zap脚本会用egrep到ps -aux的输出中查找关键字,然后会提示是否要杀掉找到的进程。输入y则杀死这个进程;输入q则不杀死进程并且脚本退出;其他任何字符则忽略这个进程而处理下一个找到的进程。

pick脚本:
#!/bin/sh
# pick:  select arguments

PATH=/bin:/usr/bin

for i
do
    echo -n “$i? ” >/dev/tty
    read response
    case $response in
    y*)    echo $i ;;
    q*)    break
    esac
done </dev/tty

zap脚本:
#! /bin/sh
# zap pattern:  kill all processes matching pattern

PATH=/bin:/usr/bin
IFS=’
‘                   # just a newline
case $1 in
“”)   echo ‘Usage: zap [-signal] pattern’ 1>&2; exit 1 ;;
-*)   SIG=$1; shift
esac

echo ‘   PID TTY TIME CMD’
kill $SIG `pick \`ps -x | egrep -i “$*” | awk ‘{if($0!~/zap/) print $0}’ \` | awk ‘{print $1}’`

脚本运行结果如下:
[root@srv0 root]# zap sleep
   PID TTY TIME CMD
 3590 pts/1    S      0:00 sleep 60? y
 3591 pts/1    S      0:00 sleep 60?
 3600 pts/1    S      0:00 sleep 60? y
 3601 pts/1    S      0:00 sleep 60? q
[2]   Terminated              sleep 60
[4]-  Terminated              sleep 60