Perl IO:操作系统层次的IO
Posted f-ck-need-u
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Perl IO:操作系统层次的IO相关的知识,希望对你有一定的参考价值。
sysopen()
open()和sysopen()都打开文件句柄,open()是比较高层次的打开文件句柄,sysopen()相对要底层一点。但它们打开的文件句柄并没有区别,只不过sysopen()有一些自己的特性:可以使用几个open()没有的flag,可以指定文件被创建时的权限等。
一定要注意的是,io buffer和open()、sysopen()无关,而是和读、写的方式有关,例如read()、getc()以及行读取都使用io buffer,而sysread、syswrite则直接绕过io buffer。
例如:
sysopen HANDLE, "file.txt", O_RDONLY;
open HANDLE, $filename, O_WRONLY|O_CREAT, 0644;
open HANDLE, $filename, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;
其中sysopen支持的flag部分有以下几种:
# 三种基本读写模式
O_RDONLY
O_RDWR
O_WRONLY
# 配合基本读写模式的额外模式
O_APPEND
O_TRUNC
O_CREAT
O_EXCL 只能配合CREAT使用,只有文件
不存在时才创建,文件存在时直
接失败而不是打开它
O_BINARY 二进制模式,表示不做换行符转换
O_TEXT 文本模式,表示做换行符转换
O_NONBLOCK 非阻塞模式
O_NDELAY 同上,另一种表示方式,为了可移
植性,建议使用上面的模式
非阻塞读写
sysopen()比open()多出的一个好用的特性就是O_NONBLOCK
。在使用非阻塞的IO时,可以在等待过程中去执行其它任务。例如:
use Fcntl;
open HANDLE, '/dev/ttyS0', O_RDONLY | O_NONBLOCK;
# sysread一个字符,但不会阻塞
my $key;
while(sysread HANDLE, $key, 1){
if (defined $key){
print "got $key
";
} else {
do other tasks;
}
sleep 1;
}
close HANDLE;
O_NONBLOCK
在无法成功返回时,将返回EAGAIN,并设置给$!
变量。所以,更优写法如下:
use Fcntl;
open HANDLE, '/dev/ttyS0', O_RDONLY | O_NONBLOCK;
# sysread一个字符,但不会阻塞
my $key;
while(sysread HANDLE, $key, 1){
if (defined $key){
print "got $key
";
} else {
if($! == 'EAGAIN'){
do other tasks;
sleep 1;
} else {
warn "Error read: $!";
last;
}
}
}
close HANDLE;
IO::File自动探测(sys)open
使用IO::File
模块的new()方法可以根据提供的参数方式自动探测要调用open()还是sysopen()来打开文件句柄:
- 如果使用字符串的模式(
< > >> +> +< +>>
),则调用open()
- 如果使用数值格式或
O_XXX
或使用了权限位,则调用sysopen()
例如:
# open()
my $fh = IO::File->new($filename, "+<");
# sysopen()
my $fh = IO::File->new($filename, O_RDWR);
my $fh = IO::File->new($file, O_RDWR, 0644);
sysread()
sysread()实现操作系统层的读操作,它通过read()系统调用直接读取文件描述符,会直接绕过IO Buffer层。
sysread FILEHANDLE,SCALAR,LENGTH,OFFSET
sysread FILEHANDLE,SCALAR,LENGTH
表示从FILEHANDLE文件句柄中读取LENGTH字节长度的数据保存到标量SCALAR中,如果指定了OFFSET,则从标量的OFFSET位置处开始写入读取到的数据。sysread()的返回值是读取到的字节数。
下面是一个结合O_NONBLOCK修饰符的sysread()。
#!/usr/bin/perl
use strict;
use warnings;
use Fcntl;
sysopen my $fh, $ARGV[0], O_RDONLY | O_NONBLOCK
or die "open failed: $!";
my $data;
my $size = sysread $fh, $data, 20;
if ($size == 20 ){
# 已读取20字节
# 继续读取30字节追加到$data尾部
$size += sysread $fh, $data, 30, 20;
print "已读数据为: $data
";
if ($size < 50){
print "文件大小为$size,数据不足50字节
";
}else{
print "已读字节数:$size
";
}
} elsif($size > 0) {
print "文件大小为$size,数据不足20字节
";
} else {
print "空文件
";
}
在上面的代码中,主要是sysread()和O_NONBLOCK需要解释下。如果没有O_NONBLOCK,那么sysread()在读取不到20字节、50字节时将被阻塞等待。但现在的情况下,如果数据不足20、50字节时,sysread()将直接返回,并返回读取到的字节数(小于20或30)。
这里如果sysread()替换成read(),它们的区别是,sysread()只读取20或50字节数据,不会多读任何一个字节,而read()则可能多读一些数据到IO Buffer中,但是只取其中的20或50字节,剩余的数据遗留在IO Buffer中。
于是,文件IO的指针就出现了不同值,对于sysread,他的指针就是20或50位置处,但是read()读取数据时,底层的文件指针将可能出现在1000字节处,但IO buffer中的IO指针将在20或50处。根据这个差值,可以计算出IO Buffer中保存了多少字节的数据。见后文sysseek()。
最后注意,不存在syseof()这样的函数来判断是否读取到了文件的结尾。但是,可以通过读取数据的返回值(即读了多少字节)来判断是否到了文件结尾。
syswrite()
syswrite()实现操作系统层的写操作,它通过write()系统调用直接向文件描述符中写入数据,它会直接绕过IO Buffer层。
syswrite FILEHANDLE,SCALAR
syswrite FILEHANDLE,SCALAR,LENGTH
syswrite FILEHANDLE,SCALAR,LENGTH,OFFSET
如果只有两个参数,则直接将标量SCALAR代表的数据写入到FILEHANDLE中,如果指定了LENGTH,则从SCALAR中取LENGTH字节长度的数据写入到FILEHANDLE中,如果指定了OFFSET,则表示从标量的OFFSET处开始截取LENGTH长度的数据写入到FILEHANDLE中。OFFSET可以指定为负数,这表示从尾部开始数写入OFFSET个字节的数据。如果LENGTH大于从OFFSET开始计算可以获取到的数据,则能获取到多少数据就写入多少数据。
syswrite()返回实际写入的字节数,如果出错了则返回undef。
例如:
# 写入abcdef
syswrite STDOUT, "abcdef";
# 写入abc
syswrite STDOUT, "abcdef", 3;
# 写入cde
syswrite STDOUT, "abcdef", 3, 2;
# 写入cde
syswrite STDOUT, "abcdef", 3, -4;
实际上,不适用syswrite(),直接使用print也可以绕过io buffer,但前提是设置文件句柄的IO层为unix
层,因为unix层是文件句柄到文件描述符的最底层,它会禁用所有上层,包括buffer。
binmod(FILEHANDLE, ":unix");
例如下面的例子中,在10秒内将每秒输出一个点,如果把binmode那行删除,将在10秒之后一次性输出10个点。如果删除binmode,还可以将print "."
改为syswrite STDOUT, ".";
,它将同样每秒输出一个点。
#!/usr/bin/perl
binmode(STDOUT, ":unix");
for (0..9){
print "."; # syswrite STDOUT, ".";
sleep 1;
}
print "
";
sysseek()
sysseek FILEHANDLE,POSITION,WHENCE
sysseek()通过lseek()系统调用设置或返回IO指针的位置,它直接绕过IO Buffer操作文件描述符。它和seek()的语法上没什么区别:
# seek using whence numbers
sysseek HANDLE, 0, 0; # rewind to start
sysseek HANDLE, 0, 2; # seek to end of file
sysseek HANDLE, 20, 1; # seek forward 20 characters
# seek using Fcntl symbols
use Fcntl qw(:seek);
sysseek HANDLE, 0, SEEK_SET; # rewind to start
sysseek HANDLE, 0, SEEK_END; # seek to end of file
sysseek HANDLE, 20, SEEK_CUR; # seek forward 20 characters
sysseek()返回设置IO指针位置后的新位置。例如原来IO指针位置为第三个字节处,向前移动5字节后,sysseek()将返回8。所以,可以通过sysseek()来实现tell()函数的功能,只需将sysseek()相对于当前位置移动0字节即可:
sysseek(HANDLE, 0, SEEK_CUR);
除了绕过IO buffer,sysseek()和seek()基本相同。但正是它绕过了io buffer,导致了sysseek()和tell()的结果可能大不一样,tell()获取的是IO Buffer中IO指针的位置,而sysseek(HANDLE, 0, SEEK_CUR)
获取的是文件描述符层次的IO指针位置。所以在使用IO Buffer类的读函数时,可以通过sysseek() - tell()
计算出缓冲在IO Buffer中的数据长度。
下面是一个说明tell()和sysseek()区别的示例:
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use Fcntl q(:seek);
open my $fh, "<", "abc.log";
my $readed;
read $fh, $readed, 5;
print "tell pos: ", tell($fh);
print "sysseek pos: ", sysseek($fh, 0, SEEK_CUR);
向abc.log中写入10个字节(实际为11个字节,因为echo自动加换行符)的数据:
$ echo "0123456789" >abc.log
执行上面的Perl程序:
tell pos: 5
sysseek pos: 11
上面的程序中,使用read()函数读取了5个字节数据,但是read()是使用IO Buffer的,它会从文件描述符中多读取一些数据到IO Buffer中(一般是几K的数据),例如这里最多只能读11字节到io buffer,然后从io buffer中返回指定数量5字节的数据保存到$readed
标量中。因为已经从文件描述符中读取了11个字节的数据,所以sysseek()的返回值为11,而tell()则是io buffer中读取数据的位置,即5字节。所以,read()结束后,io buffer中还剩下6字节的数据。
以上是关于Perl IO:操作系统层次的IO的主要内容,如果未能解决你的问题,请参考以下文章
java缓冲字符字节输入输出流:java.io.BufferedReaderjava.io.BufferedWriterjava.io.BufferedInputStreamjava.io.(代码片段