在Perl中,为什么在关闭STDOUT后打印不生成任何输出?

时间:2012-04-26 09:53:46

标签: perl stdout

我有代码:

open(FILE, "<$new_file") or die "Cant't open file \n";
@lines=<FILE>;

close FILE;

open(STDOUT, ">$new_file") or die "Can't open file\n";
$old_fh = select(OUTPUT_HANDLE);
$| = 1;
select($old_fh);

for(@lines){
    s/(.*?xsl.*?)xsl/$1xslt/;
    print;
}
close(STDOUT);  
STDOUT -> autoflush(1);
print "file changed";

关闭STDOUT关闭程序后,不会写下最后一次打印print "file changed"。为什么是这样? * 已编辑 * 打印消息我想在控制台上写入否文件

5 个答案:

答案 0 :(得分:4)

我认为这是因为print默认文件句柄是STDOUT,此时它已经关闭。您可以重新打开它,或打印到其他文件句柄,例如STDERR

print STDERR "file changed";

答案 1 :(得分:3)

这是因为您已关闭STDOUT中存储的文件句柄,因此print无法再使用它。一般来说,将一个新的文件句柄打开到一个预定义的句柄名称中并不是一个好主意,因为它必然会导致混淆。使用词法文件句柄或输出文件只是一个不同的名称要清楚得多。是的,您必须在print电话中指定文件句柄,但是您对STDOUT发生的事情没有任何疑惑。

答案 2 :(得分:3)

print语句将输出STDOUT中的字符串,这是默认的输出文件句柄。

所以声明

print "This is a message";

相同

print STDOUT "This is a message";

在您的代码中,您已关闭STDOUT,然后打印消息,这将无效。重新打开STDOUT文件句柄或不要关闭它。当脚本结束时,文件句柄将自动关闭

答案 3 :(得分:2)

open OLDOUT, ">&", STDOUT;
close STDOUT;

open(STDOUT, ">$new_file") or die "Can't open file\n";
...
close(STDOUT);  
open (STDOUT, ">&",OLDOUT);
print "file changed";

答案 4 :(得分:2)

您似乎对perl中file IO operations的完成情况感到困惑,所以我建议您仔细阅读。

出了什么问题?

你在做的是:

  • 打开文件进行阅读
  • 阅读整个文件并将其关闭
  • 使用STDOUT文件句柄打开相同的文件以进行覆盖(org文件被截断)。
  • 在默认打印句柄周围进行操作,以便在文件句柄上设置autoflush,该文件句柄甚至不会在您显示的代码中打开。
  • 在所有行上执行替换并打印它们
  • 关闭STDOUT,然后在完成所有操作后打印一条消息。

您最大的主要错误是尝试重新打开默认输出文件句柄STDOUT。我认为这是因为你不知道print是如何工作的,即你可以提供一个文件句柄来打印到print FILEHANDLE "text"。或者您不知道STDOUT是预定义的文件句柄。

您的其他错误:

  • 您没有使用use strict; use warnings;。你写的程序不应该没有这些。它们会阻止您做坏事,并向您提供有关错误的信息,并且可以节省您数小时的调试时间。
  • 你永远不应该&#34;啜饮&#34;一个文件(将整个文件读取到一个变量),除非你真的需要,因为这是无效的,而且速度慢,对于大文件会导致你的程序由于内存不足而崩溃。
  • 永远不要重新分配默认文件句柄STDIN,STDOUT,STDERR,除非A)你真的需要,B)你知道你在做什么。
  • select设置打印的默认文件句柄,阅读文档。这很少是您需要关注的事情。变量$|为当前选定的文件句柄 设置autoflush(如果设置为真值) 。所以你实际上没有做到什么,因为OUTPUT_HANDLE是一个不存在的文件句柄。如果您跳过select语句,则会为STDOUT设置autoflush。 (但你不会注意到任何差异)
  • print使用打印缓冲区,因为它很有效。我假设您正在尝试autoflush,因为您认为您的打印件被缓冲区捕获,这是不正确的。一般来说,这不是你需要担心的事情。程序结束时,所有打印缓冲区都会自动刷新。
  • 在大多数情况下,您不需要显式关闭文件句柄。文件句柄超出范围或程序结束时会自动关闭。
  • 使用词汇文件句柄,例如open my $fh, ...代替全球,例如由于之前的陈述,建议使用open FILE, ..,因为避免全局变量总是一个好主意。
  • 建议使用三参数打开:open FILEHANDLE, MODE, FILENAME。这是因为您在文件名中冒着元字符的风险会损坏您的open语句。

快速修复:

现在,正如我在评论中所说,这个 - 或者更确切地说,你想要的,因为这段代码是错误的 - 与{{1}的习惯用法非常相似命令行开关:

-p

这个简短的小片实际上完成了你的程序所做的一切,但做得更好。说明:

  • perl -pi.bak -e 's/(.*?xsl.*?)xsl/$1xslt/' file.txt switch会自动假定您提供的代码位于-p循环内,并在执行代码后打印每一行。
  • while (<>) { }开关告诉perl对文件进行inplace-edit,将备份副本保存在&#34; file.txt.bak&#34;。

所以,这个单行程相当于这样的程序:

-i

这相当于:

$^I = ".bak"; # turns inplace-edit on
while (<>) {  # diamond operator automatically uses STDIN or files from @ARGV
    s/(.*?xsl.*?)xsl/$1xslt/;
    print;
}

inplace-edit选项实际上会创建一个单独的文件,然后将其复制到原始文件上。如果使用备份选项,则首先备份原始文件。您不需要 知道此信息,只需知道使用my $file = shift; # first argument from @ARGV -- arguments open my $fh, "<", $file or die $!; open my $tmp, ">", "/tmp/foo.bar" or die $!; # not sure where tmpfile is while (<$fh>) { # read lines from org file s/(.*?xsl.*?)xsl/$1xslt/; print $tmp $_; # print line to tmp file } rename($file, "$file.bak") or die $!; # save backup rename("/tmp/foo.bar", $file) or die $!; # overwrite original file 开关将导致-i(和-p)选项实际执行原始文件的更改。

使用-n开关并且不需要激活备份选项(Windows除外),但建议使用。一个好主意是在没有选项的情况下运行单行,因此输出将打印到屏幕上,然后在看到输出正常后添加它。

正则表达式

-i

您搜索包含&#34; xsl&#34;的字符串两次。 s/(.*?xsl.*?)xsl/$1xslt/; 的使用在第二种情况下是好的,但在第一种情况下则不然。每当你发现自己用通配符字符串开始正则表达式时,你可能做错了什么。 除非您试图捕获该部分。

在这种情况下,你捕获并删除它,只是把它放回去,这是完全没用的。所以第一项业务就是把这部分拿出来:

.*?

现在,删除一些东西然后把它放回去真的只是一个不删除它的魔术。我们不需要这样的魔术技巧,当我们不能首先删除它时。使用look-around assertions,您可以实现此目标。

在这种情况下,由于你有一个可变长度的表达式并需要一个look- 后面的断言,我们必须使用s/(xsl.*?)xsl/$1xslt/; (助记符:保持)选项,因为可变长度后视镜未实现。

\K

所以,既然我们没有采取任何措施,我们就不需要使用s/xsl.*?\Kxsl/xslt/; 来回馈任何东西。现在,您可能会注意到,&#34;嘿,如果我更换了&#39; xsl&#39;使用&#39; xslt&#39;,我不需要删除&#39; xsl&#39;在所有。&#34;这是真的:

$1

您可以考虑使用此正则表达式的选项,例如s/xsl.*?xsl\K/t/; ,这会导致它忽略大小写,从而也匹配诸如&#34; XSL FOO XSL&#34;等字符串。或/i选项允许它每行执行所有可能的匹配,而不仅仅是第一个匹配。阅读perlop中的更多内容。

结论

完成的单行是:

/g