如何将其减少为单个文件打开?

时间:2017-06-29 17:19:55

标签: windows perl file append

在Windows 7中使用Strawberry Perl 5.22.0。有更多“perlish”方式来编写这段代码吗?我讨厌文件打开部分的重复,但由于需要测试创建时间,所以不能想办法让它只打开一次。

...
my $x;
my $fh;
my $sentinelfile = "Logging.yes"; #if this file exists then enable logging
my $logfile = "transfers.log";
my $log = 0; #default to NO logging

$log = 1 if -e $sentinelfile; #enable logging if sentinel file exists

if($log){

#logfile remains open after this so remember to close at end of program!
 if (-e $logfile) { #file exists
    open($fh, "<", $logfile); #open for read will NOT create if not exist
        chomp ($x = <$fh>); #grab first row
    close $fh;
    if (((scalar time - $x)/3600/24) > 30) { #when ~30 days since created
        rename($logfile, $logfile . time); #rename existing logfile
        open($fh, ">", $logfile); #open for write and truncate
        print $fh time,"\n"; #save create date
        print $fh "--------------------------------------------------\n";
    } else { #file is not older than 30 days
        open($fh, ">>", $logfile); #open for append
    }
 } else { #file not exist
    open($fh, ">", $logfile); #open new for write
    print $fh time,"\n"; #save create date
    print $fh "--------------------------------------------------\n";
 }

} #if $log
...

回顾一下:logfile记录的东西。第一行文件包含日志文件创建日期。第二行包含水平规则。其余文件包含文本。创建文件大约30天后,重命名文件并开始新文件。在上面的代码块之后,日志文件已打开并准备好记录内容。它会在程序其余部分结束时关闭。

2 个答案:

答案 0 :(得分:2)

您的代码还有其他非美容问题:a)您永远不会检查您对open的来电是否成功; b)你正在创造一个竞争条件。 -e检查失败后,该文件可能会存在。随后的open $fh, '>' ...会破坏它; c)您不检查rename呼叫是否成功等。

以下是对现有代码的部分改进:

if ($log) {
    if (open $fh, '<', $logfile) { #file exists
        chomp ($x = <$fh>);
        close $fh
            or die "Failed to close '$logfile': $!";
        if (((time - $x)/3600/24) > 30) {
            my $rotated_logfile = join '.', $logfile, time;
            rename $logfile => $rotated_logfile
                or die "Failed to rename '$logfile' to '$rotated_logfile': $!";
            open $fh, '>', $logfile
                or die "Failed to create '$logfile'";
            print $fh time, "\n", '-' x 50, "\n";
        }
        else {
            open $fh, '>>', $logfile
                or die "Cannot open '$logfile' for appending: $!";
        }
    }
    else {
        open $fh, '>', $logfile
            or die "Cannot to create '$logfile': $!";
        print $fh time, "\n", '-' x 50, "\n";
    }
}

最好将每一位离散功能抽象为适当命名的函数。

例如,这是一个完全未经测试的重写:

use autouse Carp => qw( croak );

use constant SENTINEL_FILE => 'Logging.yes';
use constant ENABLE_LOG => -e SENTINEL_FILE;

use constant HEADER_SEPARATOR => '-' x 50;
use constant SECONDS_PER_DAY => 24 * 60 * 60;
use constant ROTATE_AFTER => 30 * SECONDS_PER_DAY;

my $fh;

if (ENABLE_LOG) {
    if (my $age = read_age( $logfile )) {
        if ( is_time_to_rotate( $age ) ) {
            rotate_log( $logfile );
        }
        else {
            $fh = open_log( $logfile );
        }
    }
    unless ($fh) {
        $fh = create_log( $logfile );
    }
}

sub is_time_to_rotate {
    my $age = shift;
    return $age > ROTATE_AFTER;
}

sub rotate_log {
    my $file = shift;

    my $saved_file = join '.', $file, time;

    rename $file => $saved_file
        or croak "Failed to rename '$file' to '$saved_file': $!"

    return;
}

sub create_log {
    my $file = shift;

    open my $fh, '>', $file
        or croak "Failed to create '$file': $!";

    print $fh time, "\n", HEADER_SEPARATOR, "\n"
        or croak "Failed to write header to '$file': $!";

    return $fh;
}

sub open_log {
    my $file = shift;

    open my $fh, '>>', $file
        or croak "Failed to open '$file': $!";

    return $fh;
}

sub read_age {
    my $file = shift;

    open my $fh, '<', $file
        or return;

    defined (my $creation_time = <$fh>)
        or croak "Failed to read creation time from '$file': $!";

    return time - $creation_time;
}

答案 1 :(得分:2)

如果你需要读取一行文件,重命名然后使用它,你必须打开它两次。

但是,您也可以取消使用第一行。

在Windows上,根据perlport (Files and Filesystems) inode更改时间时间戳(ctime)“可能真的”标记文件创建时间。这可能完全适用于不会被操纵和移动的日志文件。可以使用-C file-test operator

获取
my $days_float = -C $filename;

现在你可以用30对数字进行数字测试。然后就不需要将文件的创建时间打印到第一行(但是如果它对于查看或其他工具有用,你也可以)。

此外,还有模块Win32API::File::Time,其目的是

  

提供对MSWin32

下的文件创建,修改和访问时间的最大访问权限

请务必阅读文档以获取一些警告。我没有使用它,但它似乎是根据您的需要量身定制的。

评论中提出了一个好处:显然,当文件被重命名时,操作系统会保留原始时间戳。在这种情况下,当文件太旧时,将其复制到新文件(使用新名称)并删除它,而不是使用rename。然后重新打开该日志文件,使用新的时间戳。

这是一个完整的例子

archive_log($logfile) if -f $logfile and -C $logfile > 30; 

open my $fh_log, '>>', $logfile or die "Can't open $logfile: $!";

say $fh_log "Log a line";

sub archive_log {
    my ($file) = @_;

    require POSIX; POSIX->import('strftime');
    my $ts = strftime("%Y%m%d_%H:%M:%S", localtime);  # 20170629_12:44:10

    require File::Copy; File::Copy->import('copy');
    my $archive = $file . "_$ts";     
    copy ($file, $archive) or die "Can't copy $file to $archive: $!";
    unlink $file           or die "Can't unlink $file: $!";
}

archive_log通过复制当前日志来归档当前日志,然后将其删除。 所以在那之后我们可以打开append,如果没有那就创建文件。

-C测试文件是否存在,但由于其输出用于数值测试,我们首先需要-f

由于这种情况每月发生一次,因此我需要在日志实际需要轮换后,在运行时加载模块requireimport。如果您已经使用File::Copy,那么就没有必要这样做了。至于时间戳,我投入了一些东西,使其成为一个有效的例子。

我在UNIX上对此进行了测试,将-C更改为-M并将时间戳调整为touch -t -c

更好的是,减少调用者的代码也完全将测试移动到子

my $fh_log = open_log($logfile);

say $fh_log "Log a line";

sub open_log {
    my ($file) = @_;
    if (-f $file and -C $file > 30) {
        # code from archive_log() above, to copy and unlink $file
    }
    open my $fh_log, '>>', $file  or die "Can't open $file: $!";
    return $fh_log;
}

请注意。在UNIX上,文件的创建时间不会保留在任何位置。最接近的概念是上面的ctime,但这当然是不同的。首先,它会随着许多操作而改变,例如mvlnchmodchownchgrp(可能还有其他)。