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;
相關文章
相關標籤/搜索