如何在Perl中将文件句柄指向STDOUT(或另一个文件句柄)?

26
我想编写一个快速脚本,如果给定文件,则将其写入文件,否则写入stdout。如果我能通过指向适当的文件句柄OUTFILE来启动脚本,那将更容易。我知道可以通过在合适的时候将STDOUT指向输出文件来完成我的目标,但我不想这样做,因为我不想让后来使用脚本的人感到困惑,并想知道为什么他们的打印语句无法正常工作。此外,我仍然希望为了故障排除目的偶尔使用打印语句,并且从不想将此打印发送到输出文件。总之,我正在寻找类似于以下内容的东西:
open( OUTFILE, ($output ? ">$output" : STDOUT );
有什么想法吗?
5个回答

38

一些方法(通配符):

# Requires 2-arg open, unfortunately
open(OUTPUT, "> ".($output || '-'))  or die;

if ($output) {
   open(OUTPUT, '>', $output) or die;
} else {
   *OUTPUT = *STDOUT;
}

if ($output) {
   open(OUTPUT, '>', $output) or die;
} else {
   open(OUTPUT, '>&', \*STDOUT) or die;
}

一些方法(词法变量):

# Requires 2-arg open, unfortunately
open(my $fh, "> ".($output || '-'))  or die;

my $fh;
if ($output) {
   open($fh, '>', $output) or die;
} else {
   $fh = \*STDOUT;
}

my $fh;
if ($output) {
   open($fh, '>', $output) or die;
} else {
   open($fh, '>&', \*STDOUT) or die;
}

其他一些方式:

# Changes the default handle for <print "foo">.
if ($output) {
   open(OUTPUT, '>', $output) or die;
   select(OUTPUT);
}

2
... } else { open($fh, '>&' . fileno(STDOUT)); } - mob

11

为了读者的方便(因为 OP 已经“让它工作了”),Perl 文档“perlopentut”(perldoc perlopentut)提供了打开指向 STDOUT 的备用文件句柄的示例,但它使用裸字文件句柄和两个参数版本的 open,这两种方法有些古老。Damian Conway 的书籍“Perl Best Practices”(以及基于 Damian 的书的 Perl::Critic)强调使用三个参数的 open 和词法文件句柄(my $foo 风格)。chromatic 的“Modern Perl”也简要提到了使用词法文件句柄,并在实际情况下不鼓励使用裸字。三个参数的 open 被认为是一种更安全的形式,因为它限制了对 shell 注入的曝光。虽然这里的具体情况风险较低,但默认使用三参数的 open 是一个好习惯。

需要仔细阅读 Perl 的 open 文档中显示的示例(perldoc -f open),并稍微解释一下,在使用三个参数版本的 open 打开一个重复的指向 STDOUT 的文件句柄时正确放置 & 符号的位置。人们可能会错误地认为这应该起作用:open my $fh, '>', '&STDOUT' or die $!;,主要是因为它看起来像我们习惯看到的格式(符号名称前面有一个 & 符号)。但这种语法不是 open 需要的,而且我们习惯在完全不同的上下文中看到它:某些子例程调用。

使用三个参数的 open 打开指向 STDOUT 的重复方式是将 >& 结合到同一个第二个参数中,并将 STDOUT 留空,如下所示:

use Modern::Perl;

open my $fh, '>&', STDOUT or die "Bleah: $!";

say $fh 'Printing alternate filehandle "$fh" worked.';
say 'Implicitly printing to "STDOUT" also works.';

或者将STDOUT作为类型glob的引用传递给open:

open my $fh, '>&', \*STDOUT or ...

7
< p > - 是神奇的符号。遵循惯例,否则你会违反最小惊讶原则。

use autodie qw(:all);
use Getopt::Long qw(GetOptions);
GetOptions(\my %opt, 'out=s') or die 'usage';
die 'usage' unless $opt{out};
open my $handle, ">$opt{out}";

perl foo -o blahblah        # opens file blahblah for writing
perl foo -o -               # goes to STDOUT

相关: 在Perl中,是否有使用open(...)的两个参数形式的原因?


需要注意的是,本文不会对HTML标签进行解释,只翻译文本内容。

4

没事了,我想通了。加上一个&符号就可以了。完整的可用代码如下:
open( OUTPUT, ( $output ? ">$output" : ">&STDOUT" ) );


3
让这个答案更好的方式是解释它的运作原理。 - Ether

3
你可以将常规输出写入STDOUT,将调试语句写入STDERR。如果用户想要将输出写入文件,他们只需在命令行中将STDOUT重定向到文件即可。

就像我之前所说的,我知道我可以将文件重定向到标准输出,但这真的是我想避免做的事情,因为当其他人这样做时,我会觉得很困惑和烦恼,而且在以后处理代码时也很不方便。这是最后的办法,但我宁愿将不同的文件句柄直接指向一个输出文件或标准输出,因为我认为这样会更清晰明了。 - Eli
3
@Eli - 你不需要在代码中做任何修改。只需编写脚本以始终打印到标准输出(stdout)。当用户正常运行脚本时,他们将在屏幕上看到输出。但是,他们也可以执行 scriptname.pl > filename.txt ,这样脚本就会运行并且输出将最终出现在文件 filename.txt 中。任何标准错误(stderr)上的调试代码仍将显示在控制台上。这是大多数*nix实用程序和脚本的工作方式。 - Andrew Cooper
3
这是最简单的方法。您的脚本中没有任何代码知道不同的输出路径。代码中所做的只是写到标准输出。用户在使用脚本时决定是要在控制台看到输出,还是将其放入文件中,甚至将其管道传输到另一个命令的标准输入中。 - Andrew Cooper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接