我有 Perl 脚本,它每 3 秒在现有文件中添加一个新行。此外,还有一个从该文件读取的 C++ 应用程序。
问题是应用程序在脚本完成并且文件句柄关闭后开始读取文件。为了避免这种情况,我想在每行追加后刷新。我怎样才能做到这一点?
我有 Perl 脚本,它每 3 秒在现有文件中添加一个新行。此外,还有一个从该文件读取的 C++ 应用程序。
问题是应用程序在脚本完成并且文件句柄关闭后开始读取文件。为了避免这种情况,我想在每行追加后刷新。我怎样才能做到这一点?
TL/DR:用途IO::Handle
和flush
方法,例如:
use IO::Handle;
$myfile->flush();
首先,你需要决定你想要它的“冲洗”程度。可以有相当多的缓冲层:
Perl 在文件句柄上的内部缓冲区。其他程序在离开此缓冲区之前无法看到数据。
“脏”文件块的文件系统级缓冲。其他程序仍然可以看到这些更改,它们似乎是“编写的”,但如果操作系统或机器崩溃,它们就会丢失。
写入的磁盘级回写缓冲。操作系统认为这些已写入磁盘,但磁盘实际上只是将它们存储在驱动器上的易失性内存中。如果操作系统崩溃,数据不会丢失,但如果电源出现故障,除非磁盘可以先将其写出,否则可能会丢失。这是廉价消费级 SSD 的一个大问题。
当涉及到 SAN、远程文件系统、RAID 控制器等时,情况会变得更加复杂。如果您通过管道编写,则还需要考虑管道缓冲区。
如果您只想刷新 Perl 缓冲区,您可以close
使用文件、print
包含字符串"\n"
(因为 Perl 似乎在换行符上刷新),或使用IO::Handle
'sflush
方法。
您还可以根据perl faq使用binmode
或使用$|
来使文件句柄无缓冲。这与刷新缓冲句柄不同,因为排队一堆缓冲写入然后执行单个刷新比写入无缓冲句柄具有低得多的性能成本。
如果要刷新文件系统回写缓冲区,则需要使用系统调用,如fsync()
,以模式打开文件O_DATASYNC
,或使用众多其他选项之一。这非常复杂,正如PostgreSQL 有自己的工具来测试文件同步方法这一事实所证明的那样。
如果你想确保它真的、真的、真的在永久存储的硬盘上,你必须将它刷新到程序中的文件系统中。您还需要配置硬盘驱动器/SSD/RAID 控制器/SAN/在操作系统要求时真正刷新的任何内容。这可能会非常复杂,并且非常特定于操作系统/硬件。强烈建议进行“即插即用”测试,以确保您确实做对了。
来自'man perlfaq5':
$old_fh = select(OUTPUT_HANDLE);
$| = 1;
select($old_fh);
如果您只想刷新标准输出,您可能可以这样做:
$| = 1;
但是请查看常见问题解答以获取有关为您提供更好用抽象的模块的详细信息,例如IO::Handle
.
这就是答案——真正的答案。
在进程的生命周期内停止维护此文件的打开文件句柄。
开始将您的文件附加操作抽象为一个子程序,该子程序以附加模式打开文件,写入文件并关闭它。
# Appends a new line to the existing file
sub append_new_line{
my $linedata = shift;
open my $fh, '>>', $fnm or die $!; # $fnm is file-lexical or something
print $fh $linedata,"\n"; # Flavor to taste
close $fh;
}
观察文件的进程将遇到一个关闭的文件,该文件在调用该函数时会被修改。
所有建议设置 autoflush 的解决方案都忽略了一个基本事实,即大多数现代操作系统都在缓冲文件 I/O,而不管 Perl 正在做什么。
强制将数据提交到磁盘的唯一可能性是关闭文件。
我陷入了同样的困境,我们遇到了正在写入的日志的轮换问题。
PerlDoc 中有一篇关于此的文章:如何刷新/取消缓冲输出文件句柄?为什么我必须这样做?
两种解决方案:
$|
IO::Handle
如果您正在使用它或其子类之一,请调用 autoflush 方法。要自动刷新输出,您可以在输出到文件句柄之前$|
按照其他人的描述设置 autoflush/ 。
如果您已经输出到文件句柄并且需要确保它到达物理文件,则需要使用 IO::Handleflush
和sync
方法。
另一种方法是在 Perl 脚本和 C++ 程序之间使用命名管道,而不是当前使用的文件。
对于那些正在搜索使用会话文件 (*.cse)将输出逐行刷新到Ansys CFD Post中的文件的解决方案的人,这是唯一对我有用的解决方案:
! $file="Test.csv";
! open(OUT,"+>>$file");
! select(OUT);$|=1; # This is the important line
! for($i=0;$i<=10;$i++)
! {
! print out "$i\n";
! sleep(3);
! }
请注意,在包含 Perl 脚本的每一行的开头都需要感叹号。sleep(3);
仅用于演示原因。use IO::Handle;
不需要。
我遇到了同样的问题,唯一的区别是用新内容一遍又一遍地编写同一个文件。"$| = 1" 和 autoflush 的这种关联对我有用:
open (MYFILE, '>', '/internet/web-sites/trot/templates/xml_queries/test.xml');
$| = 1; # Before writing!
print MYFILE "$thisCardReadingContentTemplate\n\n";
close (MYFILE);
MYFILE->autoflush(1); # After writing!
祝你好运。H