ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
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() с круглыми скобками, но без аргументов проверяет достижение конца всех файлов при обработке #!/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. > Смотри также --------------------------------
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-й строкой файла:
Обратите внимание: функция rename работает лишь в пределах одного каталога, поэтому временный файл должен находиться в одном каталоге с модифицируемым. Программист-перестраховщик непременно заблокирует файл на время обновления. > Смотри также --------------------------------
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{ Конечно, имеется в виду лень творческая, а не греховная.
Dear Sir/Madam/Ravenous Beast,
Dear Sir/Madam/Ravenous Beast,
> Смотри также -------------------------------
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. Этот вариант подходит лишь для самых решительных. Он сложен в написании, расходует больше памяти (теоретически - намного больше), не сохраняет резервной копии и может озадачить других программистов, которые попытаются читать данные из обновляемого файла. Как правило, он не оправдывает затраченных усилий. Если вы особо мнительны, не забудьте заблокировать файл. > Смотри также --------------------------------
7.11. Блокировка файлаПроблемаНесколько процессов одновременно пытаются обновить один и тот же файл.РешениеОрганизуйте условную блокировку с помощью функции flock:open(FH, "+< $path") or die "can't open $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 } # Совместная блокировка (для чтения)
Блокировки делятся на две категории: совместные (shared) и монопольные
(exclusive). Термин "монопольный" может ввести вас в заблуждение, поскольку
процессы не обязаны соблюдать блокировку файлов. Иногда говорят, что flock
реализует условную блокировку, чтобы операционная система могла приостано-
вить все операции записи в файл до того момента, когда с ним закончит работу
последний процесс чтения. Условная блокировка напоминает светофор на перекрестке.
Светофор работает лишь в том случае, если люди обращают внимание на цвет
сигнала: красный или зеленый - или желтый для условной блокировки. Красный
цвет не останавливает движение; он всего лишь сообщает, что движение следует
прекратить. Отчаянный, невежественный или просто наглый водитель проедет
через перекресток независимо от сигнала светофора. Аналогично работает
и функция flock - она тоже блокирует другие вызовы flock, а не процессы,
выполняющие ввод/вывод. Правила должны соблюдаться всеми, иначе могут произойти
(и непременно произойдут) несчастные случаи. Добропорядочный процесс сообщает
о своем намерении прочитать данные из файла, запрашивая блокировку LOCK_SH.
Совместная блокировка файла может быть установлена сразу несколькими процессами,
поскольку они (предположительно) не будут изменять данные. Если процесс
собирается произвести запись в файл, он должен запросить монопольную блокировку
с помощью 1_ОСК_ЕХ. Затем операционная система приостанавливает этот процесс
до снятия блокировок остальными процессами, после чего приостановленный
процесс получает блокировку и продолжает работу. Можно быть уверенным в
том, что на время сохранения блокировки никакой другой процесс не сможет
выполнить flock(FH, LOCK_EX) для того же файла. Это похоже на другое утверждение
- "в любой момент для файла может быть установлена лишь одна монопольная
блокировка", но не совсем эквивалентно ему. В некоторых системах дочерние
процессы, созданные функцией fork, наследуют от своих родителей не только
открытые файлы, но и установленные блокировки. Следовательно, при наличии
монопольной блокировки и вызове fork без ехес производный процесс может
унаследовать монопольную блокировку файла. Функция flock по умолчанию приостанавливает
процесс. Указывая флаг LOCK_NB, при запросе можно получить блокировку без
приостановки. Благодаря этому можно предупредить пользователя об ожидании
снятия блокировок другими процессами:
# Блокировка получена, можно выполнять ввод/вывод $num = Закрытие файлового манипулятора приводит к очистке буферов и снятию блокировки с файла. Функция truncate описана в главе 8. С блокировкой файлов дело обстоит сложнее, чем можно подумать - и чем нам хотелось бы. Блокировка имеет условный характер, поэтому если один процесс использует ее, а другой - нет, все идет прахом. Никогда не используйте факт существования файла в качестве признака блокировки, поскольку между проверкой существования и созданием файла может произойти вмешательство извне. Более того, блокировка файлов подразумевает концепцию состояния и потому не соответствует моделям некоторых сетевых 4зайловых систем - например, NFS. Хотя некоторые разработчики утверждают, что fcnti решает эти проблемы, практический опыт говорит об обратном. В блокировках NFS участвует как сервер, так и клиент. Соответственно, нам не известен общий механизм, гарантирующий надежную блокировку в NFS. Это возможно в том случае, если некоторые операции заведомо имеют атомарный характер в реализации сервера или клиента. Это возможно, если и сервер, и клиент поддерживают flock или fcnti; большинство не поддерживает. На практике вам не удастся написать код, работающий в любой системе. Не путайте функцию Perl flock с функцией SysV lockf. В отличие от lockf flock блокирует сразу весь файл. Perl не обладает непосредственной поддержкой lockf. Чтобы заблокировать часть файла, необходимо использовать функцию fcnti (см. программу lockarea в конце главы). > Смотри также --------------------------------
7.12. Очистка буфераПроблемаОперация вывода через файловый манипулятор выполняется не сразу. Из-за этого могут возникнуть проблемы в сценариях CGI на некоторых Web-серверах, враждебных по отношению к программисту. Если Web-сервер получит предупреждение от Perl до того, как увидит (буферизованный) вывод вашего сценария, он передает броузеру малосодержательное сообщение 500 Server Error. Проблемы буферизации возникают при одновременном доступе к файлам со стороны нескольких программ и при взаимодействии с устройствами или сокетами.РешениеЗапретите бусреризацию, присвоив истинное значение (обычно 1) переменной $ | на уровне файлового манипулятора:$old_fh = select(OUTPUT_HANDLE);
КомментарийВ большинстве реализации 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
Если программа запускается без аргументов, 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) управляет автоматической
очисткой буфера для конкретного выходного манипулятора:
Если вас не пугают странности косвенной записи (см. главу 13 "Классы,
объекты и связи"), можно написать нечто похожее на обычный английский текст:
#!/usr/bin/perl
Ни один из рассмотренных нами типов буферизации не позволяет управлять буферизацией ввода. Для этого обращайтесь к рецептам 15.6 и 15.8. > Смотри также -------------------------------
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 10::Select; $select = 10::Select->new(); # Следующая строка повторяется для всех опрашиваемых манипуляторов $select->add(*FILEHANDLE): if (@>ready = $select->can_read(0)) { # Имеются данные на манипуляторах из массива @ready } КомментарийФункция select в действительности объединяет сразу две функции. Вызванная с одним аргументом, она изменяет текущий манипулятор вывода по умолчанию (см. рецепт 7.12). При вызове с четырьмя аргументами она сообщает, какие файловые манипуляторы имеют входные данные или готовы получить вывод. В данном рецепте рассматривается только 4-аргументный вариант select. Первые три аргумента select представляют собой строки, содержащие битовые векторы. Они определяют состояние файловых дескрипторов, ожидающих ввода, вывода или сообщений об ошибках (например, сведений о выходе данных за пределы диапазона для срочной передачи сокету). Четвертый аргумент определяет тайм-аут - интервал, в течение которого select ожидает изменения состояния. Нулевой тайм-аут означает немедленный опрос. Тайм-аут также равен вещественному числу секунд или undef. В последнем варианте select ждет, пока состояние изменится:$rin = o o;
[> Смотри также --------------------------------
|