Приглашаем посетить
Гоголь (gogol-lit.ru)

7.7. Создание фильтра

Назад
Глава 7 Доступ к файлам
Вперед

7.7. Создание фильтра

Проблема

Вы хотите написать программу, которая получает из командной строки список файлов. Если файлы не заданы, входные данные читаются из STDIN. При этом пользователь должен иметь возможность передать программе "-" для обозначения STDIN или "someprog ram |" для получения выходных данных другой программы. Программа может непосредственно модифицировать файлы или выводить результаты на основании входных данных.

Решение

Читайте строки оператором, оператор о:
while (<>) {
# Сделать что-то со строкой
}

Комментарий

Встречая конструкцию:
while (<>) {
# . . .
}
Perl преобразует ее к следующему виду':
unshift(@ARGV, o-o) unless @ARGV;
while($ARGV = shift @ARGV) {
unless (open(ARGV, $ARGV)) {
\\ программе показанный фрагмент не будет работать из-за внутренней специфики ARGV.
warn "Can't open $ARGV: $!\n";
next;
} while (defined($_ = )) {
#.. .
}
}
Внутри цикла с помощью ARGV и $ARGV можно получить дополнительные данные или узнать имя текущего обрабатываемого файла. Давайте посмотрим, как это делается. Общие принципы Если пользователь не передает аргументы, Perl заносит в @ARGV единственную строку, "-". Это сокращенное обозначение соответствует STDIN при открытии для чтения и STDOUT - для записи. Кроме того, пользователь может передать "-" в командной строке вместо имени файла для получения входных данных из STDIN. Далее в цикле из @ARGV последовательно извлекаются аргументы, а имена файлов копируются в глобальную переменную $ARGV. Если файл не удается открыть, Perl переходит к следующему файлу. В противном случае начинается циклическая обработка строк открытого файла. После завершения обработки открывается следующий файл, и процесс повторяется до тех пор, пока не будет исчерпано все содержимое @ARGV. При вызове open не используется форма open (ARGV, "> $ARGV"). Это позволяет добиться интересных эффектов - например, передать в качестве аргумента строку "gzip -de file.gz |", чтобы программа получила в качестве входных данных результаты команды "gzip -de file.gz". Такое применение open рассматривается в рецепте 16.15. Массив @ARGV может изменяться перед циклом или внутри него. Предположим, вы хотите, чтобы при отсутствии аргументов входные данные читались не из STDIN, а из всех программных и заголовочных файлов С и C++. Вставьте следующую строку перед началом обработки :
@ARGV = glob("*.[cch]") unless @argv;

Перед началом цикла следует обработать аргументы командной строки - либо с помощью модулей Getopt (см. главу 15 "Пользовательские интерфейсы"), либо вручную: # Аргументы 1: Обработка необязательного флага -с
if (@ARGV && $ARGV[0] eq o-c') {
$chop_first++;
shift;
}
# Аргументы 2: Обработка необязательного флага -NUMBER
if (OARGV && $ARGV[0] =~ /"-(\d+)$/) {
$columns = $1;
shift;
}
# Аргументы 3: Обработка сгруппированных флагов -a, -i -n, и -u
while (OARGV && $ARGV[0] ="" /"-(.+)/ & (shift, ($_ = $1), 1)) {
next if /"$/;
s/a// && (++$append, redo);
s/i// && (++$ignore_ints, redo);
s/n// && (++$nostdout, redo);
s/u// && (++$unbuffer, redo);
die "usage: $0 [-ainu] [filenames] ...\n";
ЕСЛИ не считать неявного перебора аргументов командной строки, о не выделяется ничем особенным. Продолжают действовать все специальные переменные, управляющие процессом ввода/вывода (см. главу 8). Переменная $/ определяет разделитель записей, а $. содержит номер текущей строки (записи). Если $/ присваивается неопределенное значение, то при каждой операции чтения будет получено не объединенное содержимое всех файлов, а полное содержимое одного файла:
undef $/;
while (<>) {
# Теперь в $_ находится полное содержимое файла, " ,
# имя которого хранится в $ARGV
}
}

Если значение $/ локализовано, старое значение автоматически восстанавливается при выходе из блока:
{ # Блок для local
local $/; # Разделитель записей становится неопределенным
while (<>) {
# Сделать что-то; в вызываемых функциях и значение $/ остается неопределенным
}
} # Восстановить $/
Поскольку при обработке файловые манипуляторы никогда не закрываются явно, номер записи $. не сбрасывается. Если вас это не устраивает, самостоятельно организуйте явное закрытие файлов для сброса $.:
while (<>) {
print "$ARGV:$.:$_";
close ARGV if eof;
}

Функция eof проверяет достижение конца файла при последней операции чтения. Поскольку последнее чтение выполнялось через манипулятор ARGV, eof сообщает, что мы находимся в конце текущего файла. В этом случае файл закрывается, а переменная $, сбрасывается. С другой стороны, специальная запись eof() с круглыми скобками, но без аргументов проверяет достижение конца всех файлов при обработке . Параметры командной строки В Perl предусмотрены специальные параметры командной строки - -n, -р и -i, упрощающие написание фильтров и однострочных программ. Параметр -п помещает исходный текст программы внутрь цикла while (<>). Обычно он используется в фильтрах типа дгер или программах, которые накапливают статистику по прочитанным данным. Пример 7.1. ffndlogini
#!/usr/bin/perl
# findlogini - вывести все строки, содержащие подстроку "login"
while (<>) { # Перебор файлов в командной строке print if /login/;
}
Программу из примера 7.1 можно записать так, как показано в примере 7.2.
Пример 7.2. rindlogin2
#!/usr/bin/perl -n
# findlogin2 - вывести все строки, содержащие подстроку "login"
print if /login/;

Параметр -n может объединяться с -е для выполнения кода Perl из командной строки:
% perl -ne 'print if /login/'
Параметр -р аналогичен -n, однако он добавляет print в конец цикла. Обычно он используется в программах для преобразования входных данных.
Пример 7.3. lowercasel
#!/usr/bin/perl
# lowercase - преобразование всех строк в нижний регистр
use locale;
while (<>) { # Перебор в командной строке
s/(["\WO-9_])/\l$1/g; # Перевод всех букв в нижний регистр
print;
}
Программу из примера 7.3 можно записать так, как показано в примере 7.4.
Пример 7.4. lowercase2
#!/usr/bin/perl -р
# lowercase - преобразование всех строк в нижний регистр
use locale;
s/(["\WO-9_])/\l$1/g; # Перевод всех букв в нижний регистр
Или непосредственно в командной строке следующего вида:
% perl -Miocale -pe 's/(["\WO-9_])/\1$1/g'
При использовании -п или -р для неявного перебора входных данных для всего цикла негласно создается специальная метка LINE:. Это означает, что из внутреннего цикла можно перейти к следующей входной записи командой next LINE (аналог next в awk). При закрытии ARGV происходит переход к следующему файлу (аналог next file в awk). Обе возможности продемонстрированы в примере 7.5. Пример 7.5. countchunks
#!/usr/bin/perl -n
# countchunks - подсчет использованных слов
# с пропуском комментариев. При обнаружении __END__ или __DATA__
# происходит переход к следующему файлу.
for (split /\W+/) {
next LINE if /"#/;
close ARGV if /__(DATA|END)__/;
$chunks++:
} ED { print "Found $chunks chunks\n" }
В файле .history, создаваемым командным интерпретатором tcsh, перед каждой строкой указывается время, измеряемое в секундах с начала эпохи:
#+0894382237 less /etc/motd "+0894382239 vi '/.exrc
#+0894382242 date
#+0894382239 who
#+0894382288 telnet home

Простейшая однострочная программа приводит его к удобному формату:
%perl -pe 's/"#\+(\d+)\n/localtime($1) . " "/е'
Tue May 5 09:30:37 1998 less /etc/motd
Tue May 5 09:30:39 1998 vi "/.exi-c
Tue May 5 09:30:42 1998 date
Tue May 5 09:30:42 1998 who
Tue May 5 09:30:28 1998 telnet home

Параметр -i изменяет каждый файл в командной строке. Он описан в рецепте 7.9 и обычно применяется в сочетании с -р. Для работы с национальными наборами символов используется директива use locale.

Смотри также: perlmn(1); рецепты 7.9; 16.6.

7.8. Непосредственная модификация файла с применением временной копии

Проблема

Требуется обновить содержимое файла на месте. При этом допускается применение временного файла.

Решение

Прочитайте данные из исходного файла, запишите изменения во временный файл и затем переименуйте временный файл в исходный:
open(OLD, "< $old") or die "can't open $old: $!";
open(NEW, "< $new") or die "can't open $new: $!";
select(NEW); N Новый файловый манипулятор,
# используемый print по умолчанию
while () {
# Изменить $_, затем...
print NEW $_ or die "can't write $new: $!";
}
close(OLD) or die "can't close $old: $!";
close(NEW) or die "can't close $new: $!";
rename($old, "$old,orig") or die "can't rename $old to $old.orig: $!";
rename($new, $old) or die "can't rename $new to Sold: $!";


Такой способ лучше всего приходит для обновления файлов "на месте". Комментарий Этот метод требует меньше памяти, чем другие подходы, не использующие временных файлов. Есть и другие преимущества - наличие резервной копии файла, надежность и простота программирования. Показанная методика позволяет внести в файл те же изменения, что и другие версии, не использующие временных файлов. Например, можно вставить новые строки перед 20-й строкой файла:
while () {
if ($. == 20) {
print NEW "Extra line 1\n";
print NEW "Extra line 2\n":
} print NEW $_;
}
Или удалить строки с 20 по 30:
while () {
next if 20 .. 30;
print NEW $_;
}


Обратите внимание: функция rename работает лишь в пределах одного каталога, поэтому временный файл должен находиться в одном каталоге с модифицируемым. Программист-перестраховщик непременно заблокирует файл на время обновления.

Смотри также: Рецепты 7.1; 7.9-7.10

7.9. Непосредственная модификация файла с помощью параметра -i

Проблема

Требуется обновить файл на месте из командной строки, но вам лень' возиться с файловыми операциями из рецепта 7.8.

Решение

Воспользуйтесь параметрами -i и -р командной строки Perl. Запишите свою программу в виде строки: % perl -i.orig -p 'ФИЛЬТР' файл"! файл2 файлЗ ... Или воспользуйтесь параметрами в самой программе:
#!/usr/bin/perl -i.orig -p
# Фильтры

Комментарий

Параметр командной строки -i осуществляет непосредственную модификацию файлов. Он создает временный файл, как и в предыдущем рецепте, однако Perl берет на себя все утомительные хлопоты с слайдами. Используйте -i в сочетании с -р (см. рецепт 7.7), чтобы превратить:
% perl -pi.orig -e 's/DATE/localtime/e' в следующий фрагмент:
while (<>) {
if ($ARGV ne $oldargv) { # Мы перешли к следующему файлу?
rename($ARGV, $ARGV . '.orig');
open(ARGVOUT, ">$ARGV"); # Плюс проверка ошибок
select(ARGVOUT);
$oldargv = $argv;
} s/DATE/localtime/e;
} continue{


Конечно, имеется в виду лень творческая, а не греховная.
print;
} select (STDOUT); # Восстановить стандартный вывод
Параметр -i заботится о создании резервных копий (если вы не желаете сохранять исходное содержимое файлов, используйте -i вместо -i.orig), а -р заставляет Perl перебирать содержимое файлов, указанных в командной строке (или STDIN при их отсутствии). Приведенная выше однострочная программа приводит данные:

Dear Sir/Madam/Ravenous Beast,
As of DATE, our records show your account is overdue. Please settle by the end of the month. Yours in cheerful usury, --A. Moneylender
к следующему виду:

Dear Sir/Madam/Ravenous Beast,
As of Sat Apr 25 12:28:33 1998, our records show your account is overdue. Please settle by the end of the month. Yours in cheerful usury, --A. Moneylender
Этот параметр заметно упрощает разработку и чтение программ-трансляторов. Например, следующий фрагмент заменяет все изолированные экземпляры "hisvar" на "hervar" во всех файлах С, C++ и у асе:
%perl -i.old -pe 's{\bhisvar\b}{hervar}g' *.[Cchy]
%рег1 -i.old -ne 'print unless /"STARTS/ .. /"END$/' bigfile.text

Действие -i может включаться и выключаться с помощью специальной переменной $"1. Инициализируйте @ARGV и затем примените о так, как применили бы -i для командной строки:
# Организовать перебор файлов *.с в текущем каталоге,
# редактирование на месте и сохранение старого файла с расширением .orig
local $"I = '.orig'; # Эмулировать -i.orig
local @>ARGV = glob("*.c"); # Инициализировать список файлов
while (<>) {
if ($. - 1) {
print "This line should appear at the top of each file\n";
}
s/\b(p)earl\b/{1}erl/ig; # Исправить опечатки с сохранением регистра
print;
} continue {close ARGV if eof}

Учтите, что при создании резервной копии предыдущая резервная копня унпч тожается.

Смотри также: Описание переменных $"1 и $. в perlvar(1) описание оператора . . в разделе "Range Operator" perlop(1); perlmn(1).

7.10. Непосредственная модификация файла без применения временного файла

Проблема

Требуется вставить, удалить или изменить одну или несколько строк файла. При этом вы не хотите (или не можете) создавать временный файл.

Решение

Откройте файл в режиме обновления ("+<"), прочитайте все его содержимое в массив строк, внесите необходимые изменения в массиве, после чего перезапишите файл и выполните усечение до текущей позиции.
open(FH, "+< FILE" or die "Opening: $!";
@ARRAY = ;
# Модификация массива ARRAY
seek(FH,0,0) or die "Seeking: $!";
print FH OARRAY or die "Printing: $!";
truncate(FH,tell(FH)) or die "Truncating: $!";
close(FH) or die "Closing; $!";

Комментарий

Как сказано во введении, операционная система интерпретирует файлы как неструктурированные потоки байтов. Из-за этого вставка, непосредственная модификация или изменение отдельных битов невозможны (кроме особого случая, рассматриваемого в рецепте 8.13 - файлов с записями фиксированной длины). Для хранения промежуточных данных можно воспользоваться временным файлом. Другой вариант - прочитать файл в память, модифицировать его и записать обратно. Чтение в память всего содержимого подходит для небольших файлов, но с большими возникают сложности. Попытка применить его для 800-мегабайтных файлов журналов на Web-сервере приведет либо к переполнению виртуальной памяти, либо общему сбою системы виртуальной памяти вашего компьютера. Однако для файлов малого объема подойдет такое решение:
open(F, "+< $infile") or die "can't read $infile: $!";
$out = '';
while () {
s/DATE/localtime/eg;
$out .= $_, } seek(f, 0, 0) or die "seeking: $!";
print F $out or die "Printing: $!";
truncate(F, tell(F)) or die "Truncating: $!";
close(F) or die "Closing: $!";
Другие примеры операций, которые могут выполняться на месте, приведены и рецептах главы 8. Этот вариант подходит лишь для самых решительных. Он сложен в написании, расходует больше памяти (теоретически - намного больше), не сохраняет резервной копии и может озадачить других программистов, которые попытаются читать данные из обновляемого файла. Как правило, он не оправдывает затраченных усилий. Если вы особо мнительны, не забудьте заблокировать файл.

Смотри также: Описание функций seek, truncate, open и sysopen в perlfunc(1); рецепты 7.8-7.9.

7.11. Блокировка файла

Проблема

Несколько процессов одновременно пытаются обновить один и тот же файл.

Решение

Организуйте условную блокировку с помощью функции flock:

open(FH, "+< $path") or die "can't open $path: $!";
flock(FH,2) or die "can't flock $path: $!";
# Обновить файл, затем... close(FH)
or die "can't close $path: $!";

Комментарий

Операционные системы сильно отличаются по типу и степени надежности используемых механизмов блокировки. Perl старается предоставить программисту рабочее решение даже в том случае, если операционная система использует другой базовый механизм. Функция flock получает два аргумента: файловый манипулятор и число, определяющее возможные действия с данным манипулятором. Числа обычно представлены символьными константами типа LOCK_EX, имена которых можно получить из модуля Fcnti или IO::File. Символические константы LOCK_SH, LOCK_EX, LOCK_UN и LOCK_NB появились в модуле Fcnti лишь начиная с версии 5.004, но даже теперь они доступны лишь по специальному запросу с тегом : flock. Они равны соответственно 1, 2, 4 и 8, и эти значения можно использовать вместо символических констант. Нередко встречается следующая запись:

sub LOCK_SH() { 1 } # Совместная блокировка (для чтения)
sub LOCK_EX() { 2 } # Монопольная блокировка (для записи)
sub LOCK_NB() { 4 } # Асинхронный запрос блокировки
sub LOCK_UN() { 8 } # Снятие блокировки (осторожно!)


Блокировки делятся на две категории: совместные (shared) и монопольные (exclusive). Термин "монопольный" может ввести вас в заблуждение, поскольку процессы не обязаны соблюдать блокировку файлов. Иногда говорят, что flock реализует условную блокировку, чтобы операционная система могла приостано- вить все операции записи в файл до того момента, когда с ним закончит работу последний процесс чтения. Условная блокировка напоминает светофор на перекрестке. Светофор работает лишь в том случае, если люди обращают внимание на цвет сигнала: красный или зеленый - или желтый для условной блокировки. Красный цвет не останавливает движение; он всего лишь сообщает, что движение следует прекратить. Отчаянный, невежественный или просто наглый водитель проедет через перекресток независимо от сигнала светофора. Аналогично работает и функция flock - она тоже блокирует другие вызовы flock, а не процессы, выполняющие ввод/вывод. Правила должны соблюдаться всеми, иначе могут произойти (и непременно произойдут) несчастные случаи. Добропорядочный процесс сообщает о своем намерении прочитать данные из файла, запрашивая блокировку LOCK_SH. Совместная блокировка файла может быть установлена сразу несколькими процессами, поскольку они (предположительно) не будут изменять данные. Если процесс собирается произвести запись в файл, он должен запросить монопольную блокировку с помощью 1_ОСК_ЕХ. Затем операционная система приостанавливает этот процесс до снятия блокировок остальными процессами, после чего приостановленный процесс получает блокировку и продолжает работу. Можно быть уверенным в том, что на время сохранения блокировки никакой другой процесс не сможет выполнить flock(FH, LOCK_EX) для того же файла. Это похоже на другое утверждение - "в любой момент для файла может быть установлена лишь одна монопольная блокировка", но не совсем эквивалентно ему. В некоторых системах дочерние процессы, созданные функцией fork, наследуют от своих родителей не только открытые файлы, но и установленные блокировки. Следовательно, при наличии монопольной блокировки и вызове fork без ехес производный процесс может унаследовать монопольную блокировку файла. Функция flock по умолчанию приостанавливает процесс. Указывая флаг LOCK_NB, при запросе можно получить блокировку без приостановки. Благодаря этому можно предупредить пользователя об ожидании снятия блокировок другими процессами:
unless (flock(FH, LOCK_EX|LOCK_NB)) {
warn "can't immediately write-lock the file ($!), blocking ...";
unless (flock(FH, LOCK_EX)) {
die "can't get write-lock on numfile: $!"; }
}

Если при использовании LOCK_NB вам было отказано в совместной блокировке, следовательно, кто-то другой получил LOCK_EX и обновляет файл. Отказ в монопольной блокировке означает, что другой процесс установил совместную или монопольную блокировку, поэтому пытаться обновлять файл не следует. Блокировки исчезают с закрытием файла, что может произойти лишь после завершения процесса. Ручное снятие блокировки без закрытия файла - дело рискованное. Это связано с буферизацией. Если между снятием блокировки и очисткой буфера проходит некоторое время, то данные, заменяемые содержимым буфера, могут быть прочитаны другим процессом. Более надежный путь выглядит так:
if ($] < 5.004) { # Проверить версию Perl
my $old_fh = select(FH);
local $|=1; # Разрешить буферизацию команд
local $\ = ''; # Очистить разделитель выходных записей
print ""; # Вызвать очистку буфера
select($old_fh); # Восстановить предыдущий манипулятор
}
flock(FH, LOCK_UN);
До появления Perl версии 5.004 очистку буфера приходилось выполнять принудительно. Программисты часто забывали об этом, поэтому в 5.004 снятие блокировки изменилось так, чтобы несохраненные буферы очищались непосредственно перед снятием блокировки. А вот как увеличить число в файле с применением flock:
use Fcnti qw(:DEFAULT :flock);
sysopen(FH, "numfile", 0_RDWR|0_CREAT)
or die "can't open numfile: $!";
flock(FH, LOCK_EX) or die "can't write-lock numfile: $!";

# Блокировка получена, можно выполнять ввод/вывод

$num = || 0; # HE ИСПОЛЬЗУЙТЕ "or" ! !
seek(FH, 0, 0) or die "can't rewind numfile : $!";
truncate(FH, 0) or die "can't truncate numfile: $!";
print FH $num+1, "\n" or die "can't write numfile: $!";
close(FH) or die "can't close numfile: $!":


Закрытие файлового манипулятора приводит к очистке буферов и снятию блокировки с файла. Функция truncate описана в главе 8. С блокировкой файлов дело обстоит сложнее, чем можно подумать - и чем нам хотелось бы. Блокировка имеет условный характер, поэтому если один процесс использует ее, а другой - нет, все идет прахом. Никогда не используйте факт существования файла в качестве признака блокировки, поскольку между проверкой существования и созданием файла может произойти вмешательство извне. Более того, блокировка файлов подразумевает концепцию состояния и потому не соответствует моделям некоторых сетевых 4зайловых систем - например, NFS. Хотя некоторые разработчики утверждают, что fcnti решает эти проблемы, практический опыт говорит об обратном. В блокировках NFS участвует как сервер, так и клиент. Соответственно, нам не известен общий механизм, гарантирующий надежную блокировку в NFS. Это возможно в том случае, если некоторые операции заведомо имеют атомарный характер в реализации сервера или клиента. Это возможно, если и сервер, и клиент поддерживают flock или fcnti; большинство не поддерживает. На практике вам не удастся написать код, работающий в любой системе. Не путайте функцию Perl flock с функцией SysV lockf. В отличие от lockf flock блокирует сразу весь файл. Perl не обладает непосредственной поддержкой lockf. Чтобы заблокировать часть файла, необходимо использовать функцию fcnti (см. программу lockarea в конце главы).

Смотри также: Описание функций flock и fcnti в perlfunc(1) документация по стандартным модулям Fcnti и DB_File; рецепт 7.21-7.22.

7.12. Очистка буфера

Проблема

Операция вывода через файловый манипулятор выполняется не сразу. Из-за этого могут возникнуть проблемы в сценариях CGI на некоторых Web-серверах, враждебных по отношению к программисту. Если Web-сервер получит предупреждение от Perl до того, как увидит (буферизованный) вывод вашего сценария, он передает броузеру малосодержательное сообщение 500 Server Error. Проблемы буферизации возникают при одновременном доступе к файлам со стороны нескольких программ и при взаимодействии с устройствами или сокетами.

Решение

Запретите бусреризацию, присвоив истинное значение (обычно 1) переменной $ | на уровне файлового манипулятора:

$old_fh = select(output_handle);
$1 = 1;
select($old_fh);
Или, если вас не пугают последствия, вообще запретите буферизацию вызовом метода autoflush из модулей 10:
use IO::Handle;
OUTPUT_HANDLE->autoflush( 1);

Комментарий

В большинстве реализации stdio буферизация определяется типом выходного устройства. Для дисковых файлов применяется блочная буферизация с размером буфера, превышающим 2 Кб. Для каналов (pipes) и сокетов часто при меняется буфер размера от 0,5 до 2 Кб. Последовательные устройства, к числ\ которых относятся терминалы, модемы, мыши и джойстики, обычно буферизуются построчно; stdio передает всю строку лишь при получении перевода строки. Функция Perl print не поддерживает по-настоящему небуферизованного вывода - физической записи каждого отдельного символа. Вместо этого поддерживается командная буферизация, при которой физическая запись выполняется после каждой отдельной команды вывода. По сравнению с полным отсутствием буферизации обеспечивается более высокое быстродействие, при этом выходные данные получаются сразу же после вывода. Для управления буферизацией вывода используется специальная переменная $|. Присваивая ей true, вы тем самым разрешаете командную буферизацию. На ввод она не влияет (небуферизованный ввод рассматривается в рецептах 15.6 и 15.8). Если $| присваивается false, будет использоваться стандартная буферизация stdio. Отличия продемонстрированы в примере 7.6. Пример 7.6. seeme

#!/usr/bin/perl -w
# seeme - буферизация вывода в stdio
$| = (@argv > 0); # Командная буферизация при наличии аргументов
print "Now you don't see it...";
sleep 2;
print "now you do\n";


Если программа запускается без аргументов, STDOUT не использует командную буферизацию. Терминал (консоль, окно, сеанс telnet и т. д.) получит вывод лишь после завершения всей строки, поэтому вы ничего не увидите в течение 2 секунд, после чего будет выведена полная строка "Now you don't see it...now you do". В сомнительном стремлении к компактности кода программисты включают возвращаемое значение select (файловый манипулятор, который был выбран в настоящий Момент) в другой вызов select:

select((select(OUTPuT_HANDLE), $| = 1)[0]);
Существует и другой выход. Модули FileHandle и 10 содержат метод autoflush. Его вызов с аргументом true или false (по умолчанию используется true) управляет автоматической очисткой буфера для конкретного выходного манипулятора:
use FileHandle;
STDERR->autoflush; # Уже небуферизован в stdio
$filehandle->autoflush(0);


Если вас не пугают странности косвенной записи (см. главу 13 "Классы, объекты и связи"), можно написать нечто похожее на обычный английский текст:
use IO::Handle;
# REMOTE_CONN - манипулятор интерактивного сокета,
# a DISK_FILE - манипулятор обычного файла.
autoflush REMOTE_CONN 1; # Отказаться от буферизации для ясности
autoflush DISK_FILE 0; # Буферизовать для повышения быстродействия
Мы избегаем жутких конструкций select, и программа становится более понятной. К сожалению, при этом увеличивается время компиляции, поскольку включение модуля IO::Handle требует чтения и компиляции тысяч строк кода. Научитесь напрямую работать с $ |, этого будет вполне достаточно. Чтобы выходные данные оказались в нужном месте в нужное время, необходимо позаботиться о своевременной очистке буфера. Это особенно важно для соке-тов, каналов и устройств, поскольку они нередко участвуют в интерактивном вводе/выводе, а также из-за того, что вы не сможете полагаться на построчную буферизацию. Рассмотрим программу из примера 7.7. Пример 7.7. getcomidx

#!/usr/bin/perl
# getpcomidx - получить документ index.html с www.perl.com
use IO::Socket;
$sock = new IO::socket::inet (peeraddr => 'www.perl.com', PeerPort => 'http(80)');
die "Couldn't create socket: $@>" unless $sock;
# Библиотека не поддерживает $!; в ней используется $@
$sock->autoflush(1);
# На Mac \n\n "обязательно* заменяется последовательностью \015\012\015\012.
# Спецификация рекомендует это и для других систем,
# однако в реализациях рекомендуется поддерживать и "\cJ\cJ".
# Наш опыт показывает, что именно так и получается.
$sock->print("GET /index.html http/1.1\n\n");
$document = join('', $sock->getlines());
print "DOC IS: $document\n";


Ни один из рассмотренных нами типов буферизации не позволяет управлять буферизацией ввода. Для этого обращайтесь к рецептам 15.6 и 15.8.

Смотри также: Описание переменной $ | в perlvar(1); описание функции select в perlfunc(1) документация по стандартным модулям FileHandle и IO::Handle.

7.13. Асинхронное чтение из нескольких манипуляторов

Проблема

Вы хотите узнавать о наличии данных для чтения, вместо того чтобы приостанавливать процесс в ожидании ввода, как это делает о. Такая возможность пригодится при получении данных от каналов, сокетов, устройств и других программ. Решение Если вас не смущают операции с битовыми векторами, представляющими наборы файловых дескрипторов, воспользуйтесь функцией select с нулевым тайм аутом:
$rin = ' o;

# Следующая строка повторяется для всех опрашиваемых манипуляторов
vec($rin, fileno(FH-l), 1) = 1:
vec($rin, fileno(FH2), 1) = 1;
vec($rin, fileno(FH3), 1) = 1;
$nfound = select($rout=$rin, undef, undef, 0);
if ($nfound) { # На одном или нескольких манипуляторах имеются входные данные
if (vec($r,fileno(FH1),1)) {
# Сделать что-то с FH1 } if (vec($r,fileno(FH2),1)) {
it Сделать что-то с FH2 } if (vec($r,fileno(FH3),1)) {
# Сделать что-то с FH3
}
}

Модуль IO::Select позволяет абстрагироваться от операций с битовыми векторами:
use IO::Select;
$select = IO::select->new();
# Следующая строка повторяется для всех опрашиваемых манипуляторов
$select->add(*FILEHANDLE):
if (@>ready = $select->can_read(0)) { # Имеются данные на манипуляторах из массива
@ready }

Комментарий

Функция select в действительности объединяет сразу две функции. Вызванная с одним аргументом, она изменяет текущий манипулятор вывода по умолчанию (см. рецепт 7.12). При вызове с четырьмя аргументами она сообщает, какие файловые манипуляторы имеют входные данные или готовы получить вывод. В данном рецепте рассматривается только 4-аргументный вариант select. Первые три аргумента select представляют собой строки, содержащие битовые векторы. Они определяют состояние файловых дескрипторов, ожидающих ввода, вывода или сообщений об ошибках (например, сведений о выходе данных за пределы диапазона для срочной передачи сокету). Четвертый аргумент определяет тайм-аут - интервал, в течение которого select ожидает изменения состояния. Нулевой тайм-аут означает немедленный опрос. Тайм-аут также равен вещественному числу секунд или undef. В последнем варианте select ждет, пока состояние изменится:

$rin = o o;
vec($rin, fileno(FILEHANDLE), 1) = 1;
$nfound = select($rin, undef, undef, 0); # Обычная проверка
if ($nfound) {
$line = ;
print "I read $line";
}

Однако такое решение не идеально. Если среди передаваемых символов не встретится символ перевода строки, программа переходит в ожидание в . Чтобы справиться с этой проблемой, мы последовательно читаем по одному символу и обрабатываем готовую строку при получении "\п". При этом отпадает необходимость в синхронном вызове . Другое решение (без проверки файлов) описано в рецепте 7.15. Модуль IO::Select скрывает от вас операции с битовыми векторами. Метод 10: : Select ->new() возвращает новый объект, для которого можно вызвать метод add, чтобы дополнить набор новыми файловыми манипуляторами. После включениях всех интересующих вас манипуляторов вызываются функции сап_геаа, can_write и can_exception. Функции возвращают список манипуляторов, ожидающих чтения, записи или непрочитанных срочных данных (например, информации о нарушении диапазона TCP). Вызовы 4-аргументной версии select не должны чередоваться с вызовами каких-либо функций буферизованного вывода, перечисленных во введении (read, о, seek, tell и т. д.). Вместо этого следует использовать sys read - вместе с sysseek, если вы хотите изменить позицию внутри файла для данного манипулятора. Чтение данных из сокета или канала с немедленным продолжением работы описано в рецепте 17.13. Асинхронному чтению с терминала посвящены рецепты 15.6 и 15.8.

Смотри также: Описание функции select в perlfunc(1); документация по стандартному модулю IO::Select; рецепт 7.14.


Назад
Вперед