Perl IO:随机读写文件

随机读写

若是一个文件句柄是指向一个实体文件的,那么就能够对它进行随机数据的访问(包括随机读、写),随机访问表示能够读取文件中的任何一部分数据或者向文件中的任何一个位置处写入数据。实现这种随机读写的功能依赖于一个文件读写位置指针(file pointer)ios

当一个文件句柄关联到了一个实体文件后,就能够操做这个文件句柄,好比经过这个文件句柄去移动文件的读写指针,当这个指针指向第100个字节位置处时,就表示从100个字节处开始读数据,或者从100个字节处开始写数据。shell

能够经过seek()函数来设置读写指针的位置,经过tell()函数来获取文件读写指针的位置。若是愿意的话,IO::Seekable模块一样提供了一些等价的面向对象的操做方法,不过它们只是对seek、tell的一些封装,用法和工做机制是彻底同样的。app

须要注意的是,虽然说文件读写指针不是属于文件句柄的,但经过文件句柄去操做读写指针的时候,能够认为指针是属于句柄的。例如,同一个文件的多个文件句柄的读写指针是独立的,若是文件A上同时打开了A1和A2两个文件句柄,那么在A1上设置的读写指针不会影响A2句柄的读写指针。less

seek跳转文件指针位置

经过seek()函数,可让文件的指针随意转到哪一个位置。注意还有一个sysseek()函数,它们不一样,seek()是工做在buffer上的,sysseek()是底层无buffer的。函数

seek FILEHANDLE, POSITION, WHENCE

seek()有三个参数:指针

  • 第一个参数是文件句柄
  • 第二个参数是正负整数或0,它的意义由第三个参数决定
  • 第三个参数是flag,用来代表相对与哪一个位置进行跳转的,值能够是0、1和2。若是导入了Fcntl模块的seek标签(即use Fcntl qw(:seek)),则能够使用0、一、2对应的常量SEEK_SET、SEEK_CUR、SEEK_END来替代0、一、2

第三个参数的值决定第二个参数的意义。以下:日志

seek                           意义
-----------------------------------------------------------------
seek FH, $pos, 0             以相对于文件开头的位置跳转$pos个字节,
seek FH, $pos, SEEK_SET      即直接按照绝对位置的方式设置指针位置。
                             pos不能是负数,不然表示跳转到文件头的
                             前面,这会使得seek失败而返回0。例如
                             `seek FH, 0, 0`表示跳转到开头(第一个
                             字节前),`seek FH, 10, 0`表示跳转到第
                             10字节前

seek FH, $pos, 1             以相对于当前指针的位置向前(pos为正数)、
seek FH, $pos, SEEK_CUR      向后(pos为负数)跳转pos个字节,pos=0表
                             示保持原地不动。例如`seek FH, 60, 1`
                             表示指针向右(向前)移动60个字节。若是移
                             动到超出文件的位置并从这里写入数据,将
                             以空字节`\0`填充直到那个位置

seek FH, $pos, 2             以相对于文件尾部的位置跳转$pos个字节。
seek FH, $pos, SEEK_END      若是pos为负数,表示向文件头方向移动pos
                             个字节,若是pos为0则表示保持在尾部不动,
                             若是pos大于0且要写入,则会以空字节`\0`
                             填充直到那个位置。例如`seek FH, -60, 2`
                             表示从文件尾部向文件头部移动60个字节

seek在成功跳转成功时返回true,不然返回0值,例如想要跳转到文件头的前面,这时返回0且将指针放置到文件的结尾。code

好比用seek来建立一个大文件:对象

open BIGFILE, ">", "bigfile.txt";
seek BIGFILE, 100*1024, 0;  # 100K
syswrite BIGFILE, 'endendend'  # 100k + 9bytes
close BIGFILE

跳转超出文件尾部后,若是要真的让文件扩充,须要在结尾的地方写入一点数据,不然不会填充。这就至关于用相似于下面的dd命令建立一个稀疏大文件同样。进程

dd if=/dev/zero of=bigfile seek=100 count=1 bs=1K

tell()函数获取文件指针位置

tell FILEHANDLE

tell函数获取给定文件句柄当前文件指针的位置。

惟一须要注意的一点是,若是文件句柄指向的文件描述符不是一个实体文件,好比套接字句柄,tell将返回-1。注意不是返回undef,尽管咱们可能更期待它返回undef来判断。

$pos = tell MYHANDLE;
print "POS is", $pos > -1 ? $pos : "not file", "\n";

IO::Seekable

IO::Seekable模块提供了seek和tell的封装方法。例如:

$fh->seek($pos, 0);         # SEEK_SET
$fh->seek($pos, SEEK_CUR);
$pos = $fh->tell();

seek在EOF处读

就像实现tail -f同样监控每秒写入到文件尾部的数据并输出。若是使用seek来实现这个功能的话,参考以下:

#!/usr/bin/perl
use strict;
use warnings;

die "give me a file" unless(@ARGV and -f $ARGV[0])
open my $taillog, $ARGV[0];

while(1){
    while(<$tailog>){print "$.: $_";}
    seek $taillog, 0, 1;
    sleep 1;
}

上面的程序中,先读取出文件中的数据,而后将文件的指针保持在原地以便下次循环继续从这里开始读取,睡一秒后继续,这个逻辑并不难。

固然,对于上面简单的tail -f来讲,根本没使用seek的必要,可是这提供了一种连续从尾部读取数据的思路。

seek在EOF处写

典型的是写日志文件,要不断地向文件尾部追加一行行日志数据。可是,多个进程可能会互相覆盖数据,由于不一样进程的写真正是互相独立的,谁也不知道谁的指针在哪里。若是使用的是追加式写入方式,则多进程间不会出现数据覆盖的问题,由于每次append数据以前都会将指针放到文件的最结尾处。可是多个进程的append没法保证每行数据写入的顺序。

若是要保证某进程某次两行数据的写入是紧连在一块儿的,那么须要使用锁的方式,例如使用flock文件锁。

下面是一个简单的日志写入程序示例:

#!/usr/bin/perl
use strict;
use warnings;
use Fcntl qw(:flock :seek);

sub logtofile {
    die "give me two args" if @_ < 1;
    my $logfile = shift;
    my @msg = @_;

    open LOGFILE, ">>", $logfile or die "open failed: $!";

    flock LOGFILE, LOCK_EX;
    seek LOGFILE, 0, SEEK_END;
    print LOGFILE @msg;
    close LOGFILE;
}

logtofile "/tmp/mylog.log", "msgA\n", "msgB\n", "msgC\n";

truncate截断文件

若是要截断文件为某个空间大小,直接使用truncate()函数便可(shell下也有truncate命令来截断文件)。

它的第一个参数是文件句柄,第二个参数是截断后的文件大小,单位字节。注意,truncate是从当前指针位置开始向后截断的,其指针前面(左边)的数据不会动可是会计算到截断后的大小。若是指定的截断大小超过文件大小,则会使用空字节\0填充到给定大小(这个行为默认没有定义)。

由于要截断,这个文件句柄的模式必须是可写的,且若是是使用">"模式,将首先被截断为空文件。因此,应该使用+<>>+>>这类模式。为了保证截断效果,若是使用的是后两种open模式,应该在每次截断前使用"seek"将指针指到文件的头部。

例如,截断文件为100字节大小。

open FILE, ">>", "bigfile";
seek FILE, 0, 0;
truncate FILE, 100;
close FILE;

按行截断文件

truncate只能按字节截断文件,不过有时候咱们想按照行数来截断文件。

例如,想要保留前10行数据。实现的逻辑很简单,先按行读取10行(判断行号或使用一个行号计数器),而后记录下当前的指针位置,最后使用truncate截断到这个位置。

#!/usr/bin/perl

use strict;
use warnings;

die "give me a file" unless @ARGV;
die "give me a line num" unless (defined($ARGV[1]) and $ARGV[1] >= 0);

my $file = $ARGV[0];
my $trunc_to = int($ARGV[1]);

# 读取到前X行
open READ, $file or die "open failed: $!";
while(<READ>){
    last if $. == $trunc_to;
}

my $trunc_size = tell READ;
exit if $. < $trunc_to;    # total line less than $trunc_to
close READ;

# truncate
open WRITE, "+<", $file or die "open failed: $!";
truncate WRITE, $trunc_size or die "truncate failed: $!";
close WRITE;
相关文章
相关标签/搜索