Perl笔记14、进程管理

system 函数

system函数是Perl中启动子进程最简单的方法。如下调用linux的date命令:

system "date"
它会创建一个子进程来运行date命令,并且这个子进程继承了Perl的标准输入/输出/错误。也就是说date的输出日期时间字符串会送到目前Perl的STDOUT指向的地方。

system函数可以使用单个,或多个参数的形式,其中单个函数的形式很简单,经需要运行的命令直接写在双引号即可,但如果要用到shell下的变量就不能用双引号了,这时需要用单引号代替如下:
system 'ls -l $HOME';

多个参数的形式就更好理解了,将各个参数和变量用逗号隔开即可,如下:

my $tarfile = "something*wicked.tar" ;
my @dirs = qw (fred |flintstone betty ) ;
system "tar" , "cvf" , $tarfile , @dirs ;

说明:
1、利用system函数运行外部命令时,Perl会等待外部命令执行之后才能继续进行,否则就一直等待下去,当然可以在外部命令中使用 &符合将命令扔到后台,但这样一来Perl就不会得到程序的输出了。
2、在unix/linux系统下的命令执行后会有一个返回值,如果成功返回0,否则返回非零。这在Perl中恰恰相反,如果要判断系统中的命令是否执行成功,需要在system函数前加个!即可,如下:
!system "rm -rf files_to_delete" or die "something went wrong";

exec函数

exec函数与上面讲的system函数的使用语法一致,只不过perl在使用exec函数调用外部程序时不会等待程序完成,而是调用起来就OK,至于这个程序是否正常执行,是否有错则不会理睬了。很多时候Perl这样做主要功能是为另一个程序执行设定运行的环境而已。一般情况下我们都是配合fork一起使用,这稍后会介绍。

环境变量

Perl中的%ENV哈希存放了父进程,也就是shell中的环境变量,在Perl中可以修改这些变量,然后在启动其它外部的程序,这样外部的程序就会使用我修改后的变量内容,如下:

1
2
3
$ENV { 'PATH' } = "/home/rootbeer/bin:$ENV{'PATH'}" ;
delete $ENV { 'IFS' } ;
my $make_result = system "make" ;

说明:在Perl修改%ENV哈希中的值并不会影响shell或者其它父进程。

用反引号捕捉输出结果

不管system还是exec函数调用,被调用程序的输出都会定向到Perl的标准输出。如果我们对调用程序的输出感兴趣且要进行下一步处理的话,可以使用反引号来代替单引号或双引号:

my $now = `date` ; # 捕获date的输出
print "The time is now $now" ; # 这里不需要换行符,因为date的输出里已经包含

这个反引号就是ESC键下面的那个按键。如果懂得shell脚本的话就明白了,它和shell脚本中的应用类似,但有一点不同,shell会将输出的换行符去掉,但Perl则不会。因此我们经常的做法是使用chomp命令做一下处理:

chomp ( my $no_newline_now = `date` ) ;
print "A moment ago, it was $no_newline_now, I think.\n" ;

Perl解释反引号里面的值的方式类似于system的单参数形式,并且在解释器中会以双引号字符串形式展开,这意味着反斜线转移与变量内插都会正常处理。下面的例子要去的一系列Perl函数的说明文档,可以重复执行perldoc命令,每次使用不同的参数:

1
2
3
4
5
my @functions = qw { int rand sleep length hex eof not exit sqrt umask } ;
my �out ;
foreach ( @functions ) {
$about { $_ } = `perldoc -t -f $_` ;
}

说明:每次循环执行时$_的值都会不同,这样每次就可以执行不同的命令并得到他的输出。

在列表上下文中使用反引号

如果命令会输出很多行,在标量上下文中使用反引号会得到一个很长的文本串,其中包含换行符,然而在列表上下文中同样的反引号调用则返回输出的文本行列表。
例如 Uinx/linux下的who命令通常会用多行列出当前登录系统的每个用户:
merlyn tty/42 Dec 7 19:41
rootbeer console Dec 2 14:15
rootbeer tty/12 Dec 6 23:00
最左边的列式用户名称,中间列是tty名,其余的列则是登录的日期与时间。在标量上希望中我们在一个变量中得到所有的输出,后续工作需要自行拆开:
my $who_text = `who`;
但是在列表上下文中,则会自动取得拆成多行的数据:
my @who_text = `who`;
现在@who_lines里会有多个拆分好的以换行结尾的字符串。对这个结果调用chomp就可以删除所有元素末尾的换行符。换个思路考虑,只要用foreach就可以逐行处理,循环中默认使用$_作为控制变量:

1
2
3
4
5
my %ttys ;
foreach ( `who` ) {
my ( $user , $tty , $date ) = /(\S+)\s+(\S+)\s+(.*)/ ;
$ttys { $user } .= "$tty at $date\n" ;
}

●这里用三次循环覆盖以上的数据库,这里用了正则表达式进行匹配,但并没有明确使用绑定操作符(=~),而是直接针对$_进行匹配。这样写更简洁。
●另外请注意,这个正则表达式会寻找一个非空白的单词、数个空白、一个非空白的单词、数个空白,接着是剩余的所有字符,但不包括换行符。这个模式能够匹配$_代表的数据。例如处理第一行的时候,$_会是merlyn,$2是tty/42,$3则是Dec 7 19:41,这是成功的捕获。
●循环中的第二条语句只是用来存储tty与日期信息,之所以对哈希值进行追加是考虑到同一个用户可能多次登录的情况。

将进程视为文件句柄

到目前为止我们看到的都是由Perl同步控制子进程:启动一个命令,然后等着它结束,或者获得其输出,实际上Perl可以启动一个异步运行的子进程,并和它保持通信,直到子进程终止。
要启动并发运行的子进程,需要将命令放在open调用的文件名部分,并在它前面或后面加上竖线(管道符号)。这种调用也称作打开管道。

open DATE , "date|" or die "cannot pipe from date: $!" ;
open MAIL , "|mail merlyn" or die "cannot pipe to mail: $!" ;

其实上例很好理解,如果管道符放在后面代表命令的输出放到了文件句柄中(该句柄只读),管道符在后面则代表需要往句柄中写入信息(该句柄只写)。如果无法创建子进程open就会失败,但不会立即报错,只有在关闭文件句柄时才会报错。
那么如何读取和写入这些句柄呢?对于以读取模式打开的文件句柄,如下形式读即可:
my $now = ;
只写模式打开的文件句柄用print命令向其写入:
print MAIL "The time is now $now";

总之可以假设这些文件句柄都连接了一种虚幻的文件,一个包含了date命令的输出内容,另一个则可以自动用mail命令发送邮件。

注意:

1、如果进程连接到某个以读取模式打开的文件句柄,然后它结束运行了,则文件句柄就会返回文件结尾,就跟读取正常文件一样
2、当关闭用来写入数据到某个进程的文件句柄时,该进程会读到文件结尾。所以,要提交邮件并发送,直接关闭这个句柄即可:

close MAIL ;
die "mail:non-zero exit of $?" if $? ;

注意这里的$?变量和shell中的是一致的,0为正常退出非零为错误。它指示上一条命令是否正常退出。

下面要考虑的问题:为什么要使用文件句柄的方式来和进程打交道呢?如果就像得到进程的输出来做进一步的处理,那用反引号无疑是最简洁快速的方式。然而如果子进程的输出是间断性的是一个输出的过程的话,那就不需要用这种文件句柄的方式了。另外还有一点就是子进程的异步,这个在前文已经说过了。来看一下使用find命令查找指定文件,再用perl格式化输出的例子:

#!/usr/bin/perl -w
use strict ;
open F , "find . -atime +90 -size +1b -print|" or die "fork: $!" ;
while ( ) {
chomp ;
printf "%s size %.2fK last accessed on %s\n" ,
$_ , ( - s $_ ) / 1024 , -A $_ ;
}

--find命令这次运行时要查找那些90天内未被访问过超过1kb的文件,这个find过程是一个连续的过程,如果用反引号来运行的话,程序要等到都查找完了之后才能放到一个变量中然后使用循环读取,而使用文件句柄就可以找到一个文件,然后就格式化输出文件。改程序中利用了Perl笔记10中介绍的文件测试命令。

发送及接收信号

linux/unix中针对进程的信号有很多,如果对shell了解的话大家都应该清楚。下面是man kill看到的说明,只挑选了简单几个常用的:

NameNumActionDescription
ALRM14exit
HUP1exit
INT2exit
KILL9exitcannot be blocked
PIPE13exit
TERM15exit

信号可以是系统发给进程的,也可以是进程发给进程的。因此我么可以编写Perl程序来给别的进程发信号,前提就是必须知道目标进程的ID。假如知道了目标进程的ID为4201,则可以通过下面的语句来发送信号:
kill 2, 4201 or die "Cannot signal 4201 with SIGINT: $!";
发送信号的命令使用kill,因为2信号就是SIGINT,也可以是简写的INT,如果进程已经不存在了,就会报错。这个技巧也可以判断指定的进程是否存在,通过发送信号零来判断。如下:
[cce_perl]
unless (kill 0, $pid) {
warn "$pid has gone away!";
}
上面介绍了发送信号,接下来讲一下接收信号,为什么要接收信号呢?假设我有一个程序需要临时写入一些文件,等到程序结束时再删除之,但当程序执行一半时我按下了Ctrl+C键终止了程序,这样在程序末尾的清理临时文件的部分没有执行,垃圾文件就没有正常清除。那么如果我们能捕获这个Ctrl+C信号,针对这个信号编写一个清理程序不就解决问题了吗。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
my $temp_directory = "/tmp/myprog.$$" ; # 这里定义临时目录
mkdir $temp_directory , 0700 or die "Cannot create $temp_directory: $!" ;
sub clean_up {
unlink glob "$temp_directory/*" ;
rmdir $temp_directory ;
}
sub my_int_handler {
&clean_up ;
die "interrupted, exiting...\n" ;
}
$SIG { 'INT' } = 'my_int_handler' ;
#.
#. do some things
#.
# 这里是正常的退出
&clean_up ;

对特殊哈希%SIG进行赋值就能设置信号处理程序。哈希键是信号名称,这里不用写固定的前缀SIG。哈希的值是子程序名,注意,子程序不用写“与号”。现在只要收到SIGINT信号,Perl就会将程序转到信号处理程序,这里的子程序会清理临时文件并退出。程序的末尾也有调用了清理子程序。

如果INT信号的处理程序没有退出操作,而是直接返回,那么用户按的Ctrl+C 就没有效果了。程序仍然会运行下去。有些时候我们需要的就是这种操作,也就是说我们利用信号处理程序处理一些任务之后,不退出,然后接着做下面的工作。下面的例子,假设处理文件里的每行都要花费很长时间,而你想要在收到信号时停止处理,却不想让等待中的这一行中断,这时,只要在信号处理程序中设置一个标记,然后在每行处理结束时检查它即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
my $int_count ;
sub my_int_handler { $int_count ++ }
$SIG { 'INT' } = 'my_int_handler' ;
...
$int_count = 0 ;
while ( ) {
... some processing that takes a few seconds ...
if ( $int_count ) {
# interrupt was seen!
print "[processing interrupted...]\n" ;
last ;
}
}

本章练习

1、写一个程序,它会进入某个特定的目录,比如系统根目录。然后执行ls -l命令获得该目录内容的详细报告。如果非unix系统,请使用该系统上相应命令去的详细列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/perl -w

use strict ;

my $dir = $ARGV [ 0 ] ;

open DIR , "ls -l $dir|" or die "can't open $dir:$!" ;

while (

) {
if ( /^d/ ) {
print "\e[32m$_\e[0m" ;
} else {
print "$_" ;
}
}

执行:perl ex_14_1.pl ~
drwxr-xr-x 4 root root 4096 May 13 10:29 bin
lrwxrwxrwx 1 root root 15 Jun 1 16:25 cooper -> /backup/cooper/
drwx------ 2 root root 4096 Jun 9 16:46 Mail
-rw------- 1 root root 3401 Jun 9 16:46 mbox
-rw-r--r-- 1 root root 179316 Feb 4 10:14 MySQL-zrm-2.2.0-1.noarch.rpm
文件夹会显示绿色,其余白色

2、接上面的程序,让它将命令的输出送到当前目录下的ls.out 文件,错误暑促则送到 ls.err文件。

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/perl -w

use strict ;

my $dir = $ARGV [ 0 ] ;

open STDOUT , ">ls.out" die "can't open file:$!" ;
open STDERR , ">ls.err" die "can't open file:$!" ;

chdir "$dir" or die "cant change dir to $dir :$!" ;
exec "ls -l" or die "can't execute ls -l command:$!" ;

3、写依程序,它会解析date命令的输出以判断今天是星期几。如果是工作日,则输出get to work,否则输出go play。

1
2
3
4
5
6
7
8
9
#!/usr/bin/perl -w

use strict ;

if ( `date +\%A` =~/^S / ) {
print "go play\n" ;
} else {
print "get to work\n" ;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值