在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天后,重命名文件并开始新文件。在上面的代码块之后,日志文件已打开并准备好记录内容。它会在程序其余部分结束时关闭。
答案 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
。
由于这种情况每月发生一次,因此我需要在日志实际需要轮换后,在运行时加载模块require
和import
。如果您已经使用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
,但这当然是不同的。首先,它会随着许多操作而改变,例如mv
,ln
,chmod
,chown
,chgrp
(可能还有其他)。