0

我有代码:

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"。为什么是这样? *已编辑*打印我想在控制台上写的消息 no to file

4

5 回答 5

4

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

print STDERR "file changed";
于 2012-04-26T10:01:03.997 回答
3

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

所以声明

print "This is a message";

print STDOUT "This is a message";

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

于 2012-04-26T10:26:19.330 回答
3

这是因为你已经关闭了存储在 中的文件句柄STDOUT,所以print不能再使用它了。一般来说,打开一个新的文件句柄到一个预定义的句柄名称中并不是一个好主意,因为它必然会导致混乱。使用词法文件句柄或只是为输出文件使用不同的名称要清楚得多。是的,您必须在print调用中指定文件句柄,但是您不会对STDOUT.

于 2012-04-26T10:10:01.917 回答
2

您似乎对在 perl 中如何完成文件 IO 操作感到困惑,所以我建议您阅读一下。

什么地方出了错?

你正在做的是:

  • 打开文件进行阅读
  • 读取整个文件并关闭它
  • 使用 STDOUT 文件句柄打开同一个文件进行覆盖(org 文件被截断)。
  • 调整默认打印句柄,以便在您显示的代码中甚至未打开的文件句柄上设置自动刷新。
  • 对所有行执行替换并打印它们
  • 关闭 STDOUT,然后在一切完成后打印一条消息。

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

您的其他错误:

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

快速修复:

现在,正如我在评论中所说,这 - 或者更确切地说,你想要-p的,因为这段代码是错误的 - 与命令行开关的惯用用法几乎相同:

perl -pi.bak -e 's/(.*?xsl.*?)xsl/$1xslt/' file.txt

这个简短的小片段实际上完成了您的程序所做的所有事情,但做得更好。解释:

  • -pswitch 自动假定您提供的代码在while (<>) { }循环内,并在您的代码执行后打印每一行。
  • -iswitch 告诉 perl 对文件进行就地编辑,将备份副本保存在“file.txt.bak”中。

因此,该单行相当于这样的程序:

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

这相当于:

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

inplace-edit 选项实际上创建了一个单独的文件,然后将其复制到原始文件上。如果使用备份选项,则首先备份原始文件。您不需要知道这些信息,只需知道使用-i开关将导致-p(and -n) 选项实际对原始文件执行更改。

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

正则表达式

s/(.*?xsl.*?)xsl/$1xslt/;

您搜索包含“xsl”的字符串两次。的用法.*?在第二种情况下很好,但在第一种情况下不好。每当您发现自己使用通配符字符串开始正则表达式时,您可能做错了什么。除非你试图捕捉那部分。

但是,在这种情况下,您将其捕获并删除它,然后将其放回原处,这完全没有用。因此,首要任务是将那部分去掉:

s/(xsl.*?)xsl/$1xslt/;

现在,移除某些东西并将其放回去实际上只是一个根本不移除它的魔术。我们不需要这样的魔术,因为我们一开始就不能删除它。使用环视断言,您可以实现这一点。

在这种情况下,由于你有一个可变长度的表达式并且需要一个向断言,我们必须使用\K(mnemonic: Keep) 选项,因为没有实现可变长度的后视。

s/xsl.*?\Kxsl/xslt/;

所以,由于我们没有取出任何东西,我们不需要使用$1. 现在,您可能会注意到,“嘿,如果我将 'xsl' 替换为 'xslt',我根本不需要删除 'xsl'。” 这是真的:

s/xsl.*?xsl\K/t/;

您可以考虑为此正则表达式使用选项,例如/i,这会导致它忽略大小写,从而也匹配诸如“XSL FOO XSL”之类的字符串。或者/g允许它每行执行所有可能匹配的选项,而不仅仅是第一个匹配。在perlop中阅读更多内容。

结论

完成的单线是:

perl -pi.bak -e 's/xsl.*?xsl\K/t/' file.txt
于 2012-04-29T16:15:25.150 回答
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";
于 2012-04-26T10:09:46.363 回答