Как я могу сбросить дескриптор файла, получающий стандартный ввод в Perl?

Когда я запускаю сценарий Perl, некоторый стандартный ввод, например.

$ awk '{print $1"\t"$2}' foo.txt | myScript.pl

У меня есть скрипт, который содержит ошибку. Он читает первую строку стандартного ввода, получает что-то из этой первой строки, а затем анализирует только строки со 2 по n стандартного ввода при последующем чтении:

open (FH, "< $input") or die $?;
my $firstLine = <FH>; // reads first line

...

while (my $line = <FH>) {
    // reads lines 2 through n
}
close (FH);

Поэтому я добавил в этот скрипт оператор seek, чтобы попытаться сбросить дескриптор файла в начало файла:

use Fcntl qw(:seek);

...

open (FH, "< $input") or die $?;
my $firstLine = <FH>; // reads first line
seek (FH, 0, SEEK_SET) or die "error: could not reset file handle\n"; // should reset file handle

...

while (my $line = <FH>) {
    // reads lines 1 through n (in theory)
}
close (FH);

Добавление этого оператора seek работает для файлового ввода, но не для стандартного ввода. Скрипт вылетает с ошибкой:

 error: could not reset file handle 

Как я могу правильно прочитать первую строку стандартного ввода в начале скрипта, сбросить дескриптор файла, а затем прочитать все строки стандартного ввода с 1 по n?

Я думаю, что могу написать специальный случай для хранения и обработки первой строки перед циклом while, но я надеюсь, что есть более чистое решение этой проблемы, которое позволит мне обрабатывать как стандартный ввод, так и файловый ввод.


person Alex Reynolds    schedule 11.12.2009    source источник


Ответы (3)


Есть простой способ:

open (FH, "< $input") or die $?;
my $line = <FH>; // reads first line

//do stuff with first line

do {
    //stuff
} while ($line = <FH>);
person Anon.    schedule 11.12.2009
comment
Конечно! Я совсем забыл о do-while. Очень элегантно, спасибо. - person Alex Reynolds; 11.12.2009
comment
Просто помните, что next/last не рассматривают do{} как блок; см. perldoc.perl.org/perlsyn.html#Statement-Modifiers. стандартные обходные пути. - person ysth; 11.12.2009

EDIT: Недостаток моего первого подхода заключается в том, что он должен загрузить все строки из FH в память перед обработкой списка. Этот подход, использующий открытую для скалярной ссылки функцию (доступную с версии 5.8.0), не будет иметь этой проблемы:

my $firstline = <FH>;
open(my $f1, '<', \$firstline);

...

while (my $line = <$f1> || <FH>) {
    # process line 1 through n
}

Как насчет:

for my $line ($firstline, <FH>) {
    # process lines 1 through n
}
person mob    schedule 11.12.2009
comment
Мне нравится использовать while, потому что я работаю с очень большими файлами. Использование for требует, как вы заметили, сохранения всех входных данных в памяти перед их обработкой. - person Alex Reynolds; 11.12.2009

Я не думаю, что вы можете сбросить (или искать (0)) для STDIN. Это не обычный дескриптор файла, как STDIN. Поскольку на самом деле это не файл, вам потребуется, чтобы STDIN выполнял сбрасываемую буферизацию.

Я думаю, вам нужно будет специально обрабатывать чтение и повторное использование строки 1.

person Greg Fenton    schedule 11.12.2009
comment
Проблема не в том, что это STDIN, а в том, что это не файл. Поиск не будет работать с другими файловыми дескрипторами, которые являются конвейерами или другими типами без поиска; seek будет успешным даже на STDIN, если это файл, например: seq 9 >foo; perl -we'seek STDIN, 12, 0; print scalar <>' <foo печатает 7, а не 1. - person ysth; 11.12.2009