一个Perl子程序如何区分文件名、文件句柄、*DATA和*STDIN?

5

如果我有一个函数可能会传递一个文件名或各种文件句柄或类型环境,该函数如何区分这些参数-包括区分例如* DATA* STDIN 之间的区别?

基于收到的答案更新的代码 谢谢大家。

use strict;
use warnings;
use FileHandle;

sub file_thing_type {
    my ($f) = shift;
    my $type;
    my $r = ref $f;
    if ($r eq 'GLOB' or ref(\$f) eq 'GLOB'){
        # Regular and built-in file handles.
        my $fn = fileno $f;
        if (defined $fn){
            my %built_in = (
                'STDIN'  => fileno(*STDIN),
                'STDOUT' => fileno(*STDOUT),
                'STDERR' => fileno(*STDERR),
                'DATA'   => fileno(*DATA),
            );
            for my $k (keys %built_in){
                if (defined $built_in{$k} and $built_in{$k} == $fn){
                    $type = $k;
                    last;
                }
            }
            $type = 'regular file handle' unless defined $type;
        }
        else {
            $type = 'non-IO glob';
        }
    }
    elsif ($r){
        # A reference of some kind.
        $type = $r;
        # Might be an IO object. Has it been opened?
        {
            no warnings 'unopened';
            $type .= ' opened' if -f $f;
        }
    }
    else {
        # File name or just some other value?
        $type = -f $f ? 'file name' : 'other';
    }
    return $type;
}

open(my $h, '<', $0) or die $!;

printf "%12s => %s\n",
       $_->[0],
       file_thing_type($_->[1])
for (
    [ 'handle',     $h                  ], # regular file handle
    [ 'DATA',       *DATA               ], # DATA if source has DATA section; else non-IO glob
    [ 'STDIN',      *STDIN              ], # STDIN
    [ 'STDOUT',     *STDOUT             ], # STDOUT
    [ 'STDERR',     *STDERR             ], # STDERR
    [ 'FOO',        *FOO, *FOO          ], # non-IO glob
    [ 'FileHandle', FileHandle->new     ], # FileHandle
    [ 'FileHandle', FileHandle->new($0) ], # FileHandle opened
    [ 'file name',  $0                  ], # file name
    [ 'not file',   ''                  ], # other
    [ 'misc',       {bar=>1}            ], # HASH
);

__END__

1
它们都是文件句柄。你到底想要测试什么?您可以在句柄上使用“-t”测试,以检查它是否来自/去往终端(TTY),除非被管道引导,否则通常为STDIN和STDOUT设为true。 - amphetamachine
请让我们了解您想要做什么的更广泛的背景。为什么您需要能够区分“DATA”和“STDIN”? - Greg Bacon
1
@gbacon 说实话,我不确定。昨晚我在做一些东西,认为能够区分它们可能会有所帮助。然后我注意到 Data::Dumper 能够在某种程度上将它们区分开来,所以我认为这个问题可能有一个简单的答案,并在 SO 上提出了这个问题。自那时以来,我的项目思路已经发生了变化,现在我们只是出于好奇心。 :) - FMc
3个回答

2

更新:区分一个可能被赋值为*DATA*STDIN全局变量的任务是由fileno完成的:

sub data_or_stdin {
  my $x = shift;
  if (fileno($x) == fileno(DATA)) {
    return "DATA";
  } elsif (fileno($x) == fileno(STDIN)) {
    return "STDIN";
  } else {
    return "NEITHER";
  }
}

print "DATA:  ", data_or_stdin(*DATA), "\n";
print "STDIN: ", data_or_stdin(*STDIN), "\n";
open(ZZZ, ">>", "zzz"); close ZZZ;
open(ZZZ, "<", "zzz"); print "ZZZ: ", data_or_stdin(*ZZZ), "\n"; close ZZZ;
open($fh, "<", "zzz"); print "\$fh=ZZZ: ", data_or_stdin($fh), "\n"; close $fh;
$fh = *DATA; print "\$fh=DATA: ", data_or_stdin($fh), "\n";
$fh = *STDIN; print "\$fh=STDIN: ", data_or_stdin($fh), "\n";
__END__ stuff;
$ perl data_or_stdin.pl
DATA:  DATA
STDIN: DATA
ZZZ: NEITHER
$fh=ZZZ: NEITHER
$fh=DATA: DATA
$fh=STDIN: DATA

如果$f是一个文件句柄,则ref $fref \$f将会是"GLOB"。如果$f是一个标量变量,则ref \$f将会是"SCALAR"

sub filehandle_or_scalar {
  my $x = shift;
  if (ref $x eq "GLOB" || ref \$x eq "GLOB") {
      return "filehandle";
  } elsif (ref \$x eq "SCALAR") {
      return "scalar";
  } else {
      return "not filehandle or scalar";
  }
}

print "STDIN: ", filehandle_or_scalar(*STDIN), "\n";
print "\$_: ", filehandle_or_scalar($_), "\n";
open($fh, ">", "zzz");
print "\$fh: ", filehandle_or_scalar($fh), "\n";
print "string: ", filehandle_or_scalar('file.txt'), "\n";
print "ref: ", filehandle_or_scalar(\$x), "\n"

###########################################

$ perl filehandle_or_scalar.pl
STDIN: filehandle
$_: scalar
$fh: filehandle
string: scalar
ref: not filehandle or scalar

子程序 is_filehandle {应该改为子程序 filehandle_or_scalar { - Gavin Brock

1

mobrule的方法看起来很有前途:

perl -E 'open $fh, "<", "/dev/null"; say ref $fh;'

会输出GLOB。但是,以下也会。

perl -E 'say ref \*FOO;'

一个“真正的”文件句柄也会有一个相关联的文件描述符,您可以使用fileno来确定它:
perl -MData::Dumper -E 'open $fh, "<", "/dev/null"; say Data::Dumper::Dumper([fileno $fh, fileno \*STDIN, fileno \*FOO])'

将输出类似于:

$VAR1 = [
          3,
          0,
          undef
        ];

您可以使用此功能来区分用于文件I/O的GLOB和其他GLOB。在UNIX系统中,标准输入流通常与文件描述符0相关联。

另一个需要注意的是与文件句柄相关的类必须实现特定的接口,您可以使用can进行测试。有关该接口的详细信息,请参见perlfunc中的tie VARIABLE,CLASSNAME,LIST条目。


1

你可以在字符串化的文件句柄上使用模式匹配,如*STDIN、*DATA等...

if ($f =~ /\bSTDIN$/) {
    return "STDIN";
} elsif ($f =~ /\bDATA$/) {
    return "DATA";
}

有点取巧,但可能足够了...


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