若是一个文件句柄是指向一个实体文件的,那么就能够对它进行随机数据的访问(包括随机读、写),随机访问表示能够读取文件中的任何一部分数据或者向文件中的任何一个位置处写入数据。实现这种随机读写的功能依赖于一个文件读写位置指针(file pointer)。shell
当一个文件句柄关联到了一个实体文件后,就能够操做这个文件句柄,好比经过这个文件句柄去移动文件的读写指针,当这个指针指向第100个字节位置处时,就表示从100个字节处开始读数据,或者从100个字节处开始写数据。bash
能够经过seek()函数来设置读写指针的位置,经过tell()函数来获取文件读写指针的位置。若是愿意的话,IO::Seekable
模块一样提供了一些等价的面向对象的操做方法,不过它们只是对seek、tell的一些封装,用法和工做机制是彻底同样的。app
须要注意的是,虽然说文件读写指针不是属于文件句柄的,但经过文件句柄去操做读写指针的时候,能够认为指针是属于句柄的。例如,同一个文件的多个文件句柄的读写指针是独立的,若是文件A上同时打开了A1和A2两个文件句柄,那么在A1上设置的读写指针不会影响A2句柄的读写指针。less
经过seek()函数,可让文件的指针随意转到哪一个位置。注意还有一个sysseek()函数,它们不一样,seek()是工做在buffer上的,sysseek()是底层无buffer的。函数
seek FILEHANDLE, POSITION, WHENCE
复制代码
seek()有三个参数:ui
Fcntl
模块的seek
标签(即use Fcntl qw(:seek)
),则可使用0、一、2对应的常量SEEK_SET、SEEK_CUR、SEEK_END来替代0、一、2第三个参数的值决定第二个参数的意义。以下:spa
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且将指针放置到文件的结尾。指针
好比用seek来建立一个大文件:日志
open BIGFILE, ">", "bigfile.txt";
seek BIGFILE, 100*1024, 0; # 100K
syswrite BIGFILE, 'endendend' # 100k + 9bytes
close BIGFILE
复制代码
跳转超出文件尾部后,若是要真的让文件扩充,须要在结尾的地方写入一点数据,不然不会填充。这就至关于用相似于下面的dd命令建立一个稀疏大文件同样。code
dd if=/dev/zero of=bigfile seek=100 count=1 bs=1K
复制代码
tell FILEHANDLE
复制代码
tell函数获取给定文件句柄当前文件指针的位置。
惟一须要注意的一点是,若是文件句柄指向的文件描述符不是一个实体文件,好比套接字句柄,tell将返回-1。注意不是返回undef,尽管咱们可能更期待它返回undef来判断。
$pos = tell MYHANDLE;
print "POS is", $pos > -1 ? $pos : "not file", "\n";
复制代码
IO::Seekable
模块提供了seek和tell的封装方法。例如:
$fh->seek($pos, 0); # SEEK_SET
$fh->seek($pos, SEEK_CUR);
$pos = $fh->tell();
复制代码
就像实现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的必要,可是这提供了一种连续从尾部读取数据的思路。
典型的是写日志文件,要不断地向文件尾部追加一行行日志数据。可是,多个进程可能会互相覆盖数据,由于不一样进程的写真正是互相独立的,谁也不知道谁的指针在哪里。若是使用的是追加式写入方式,则多进程间不会出现数据覆盖的问题,由于每次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()函数便可(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;
复制代码