perlfaq5
СОДЕРЖАНИЕ
- НАЗВАНИЕ
- ВЕРСИЯ
- ОПИСАНИЕ
- Как сбросить/отключить буферизацию для выходного файлового дескриптора? Зачем это нужно?
- Как изменить, удалить или вставить строку в файл или добавить в начало файла?
- Как подсчитать количество строк в файле?
- Как удалить последние N строк из файла?
- Как использовать опцию -i Perl внутри программы?
- Как скопировать файл?
- Как создать временное имя файла?
- Как обрабатывать файлы с фиксированной длиной записи?
- Как сделать файловый дескриптор локальным для подпрограммы? Как передавать файловые дескрипторы между подпрограммами? Как создать массив файловых дескрипторов?
- Как использовать файловый дескриптор косвенно?
- Как открыть файловый дескриптор для строки?
- Как настроить формат подписи для использования с write()?
- Как записать в строку с помощью write()?
- Как вывести числа с добавленными запятыми?
- Как перевести тильды (~) в имени файла?
- Почему при открытии файла на чтение-запись он очищается?
- Почему иногда появляется сообщение "Список аргументов слишком длинный" при использовании <*>?
- Как открыть файл с ведущим ">" или хвостовыми пробелами в имени?
- Как надёжно переименовать файл?
- Как заблокировать файл?
- Почему нельзя просто использовать open(FH, ">file.lock")?
- У меня всё ещё не получается блокировка. Мне нужно просто увеличить число в файле. Как это сделать?
- Я хочу только добавить немного текста в конец файла. Нужно ли всё ещё использовать блокировку?
- Как произвольно обновить двоичный файл?
- Как получить отметку времени файла в Perl?
- Как установить отметку времени файла в Perl?
- Как печатать в несколько файлов одновременно?
- Как прочитать весь файл сразу?
- Как прочитать файл по абзацам?
- Как прочитать один символ из файла? С клавиатуры?
- Как узнать, есть ли ожидающий символ в файловом дескрипторе?
- Как сделать tail -f в Perl?
- Как сделать дубликат файлового дескриптора в Perl?
- Как закрыть файловый дескриптор по номеру?
- Почему нельзя использовать "C:\temp\foo" в путях DOS? Почему `C:\temp\foo.exe` не работает?
- Почему glob("*.*") не получает все файлы?
- Почему Perl позволяет удалять файлы только для чтения? Почему -i перезаписывает защищенные файлы? Разве это ошибка в Perl?
- Как выбрать случайную строку из файла?
- Почему при выводе массива строк получаются странные пробелы?
- Как пройтись по дереву каталогов?
- Как удалить дерево каталогов?
- Как скопировать весь каталог?
- АВТОР И АВТОРСКИЕ ПРАВА
НАЗВАНИЕ
perlfaq5 - Файлы и форматы
ВЕРСИЯ
версия 5.20210520
ОПИСАНИЕ
Этот раздел посвящен вводу-выводу и проблемам с "f": файловыми дескрипторами, сбросом, форматами и подписями.
Как сбросить/отключить буферизацию для выходного файлового дескриптора? Зачем это нужно?
(предоставлено brian d foy)
Вы можете прочитать статью Марка Джейсона Доминуса "Страдания от буферизации" по адресу http://perl.plover.com/FAQs/Buffering.html .
Perl обычно буферизирует вывод, чтобы не делать системный вызов для каждого вывода. За счёт накопления вывода, он делает меньше дорогостоящих системных вызовов. Например, в этом небольшом фрагменте кода вы хотите вывести точку на экран для каждой обработанной строки, чтобы наблюдать за ходом работы программы. Вместо того, чтобы видеть точку для каждой строки, Perl буферизирует вывод, и вы ждёте, пока не увидите ряд из 50 точек сразу:
# long wait, then row of dots all at once
while( <> ) {
print ".";
print "\n" unless ++$count % 50;
#... expensive line processing operations
} Чтобы обойти это, вы должны отключить буферизацию для выходного файлового дескриптора, в данном случае, STDOUT. Вы можете установить специальную переменную $| в истинное значение (мнемоника: делать ваши файловые дескрипторы "горячими"):
$|++;
# dot shown immediately
while( <> ) {
print ".";
print "\n" unless ++$count % 50;
#... expensive line processing operations
} Переменная $| является одной из специальных переменных для каждого файлового дескриптора, поэтому каждый файловый дескриптор имеет свою копию её значения. Если вы хотите объединить стандартный вывод и стандартную ошибку, например, вы должны отключить буферизацию для каждого из них (хотя STDERR может быть по умолчанию небуферизованным):
{
my $previous_default = select(STDOUT); # save previous default
$|++; # autoflush STDOUT
select(STDERR);
$|++; # autoflush STDERR, to be sure
select($previous_default); # restore previous default
}
# now should alternate . and +
while( 1 ) {
sleep 1;
print STDOUT ".";
print STDERR "+";
print STDOUT "\n" unless ++$count % 25;
} Помимо специальной переменной $|, вы можете использовать binmode для задания файловому дескриптору :unix слоя, который не буферизован:
binmode( STDOUT, ":unix" );
while( 1 ) {
sleep 1;
print ".";
print "\n" unless ++$count % 50;
} Дополнительную информацию о слоях вывода см. в записях для binmode и open в perlfunc, а также в документации модуля PerlIO.
Если вы используете IO::Handle или один из его подклассов, вы можете вызвать метод autoflush для изменения настроек файлового дескриптора:
use IO::Handle;
open my( $io_fh ), ">", "output.txt";
$io_fh->autoflush(1); Объекты IO::Handle также имеют метод flush. Вы можете сбросить буфер в любое время без автоматической буферизации
$io_fh->flush; Как изменить, удалить или вставить строку в файл или добавить в начало файла?
(предоставлено brian d foy)
Основная идея вставки, изменения или удаления строки из текстового файла заключается в чтении и выводе файла до нужного пункта изменения, выполнении изменения, а затем чтении и выводе остальной части файла. Perl не предоставляет произвольный доступ к строкам (особенно учитывая, что разделитель ввода записей $/, является изменяемым), хотя такие модули, как Tie::File, могут его имитировать.
Программа Perl для выполнения этих задач имеет базовый вид открытия файла, вывода его строк, а затем закрытия файла:
open my $in, '<', $file or die "Can't read old file: $!";
open my $out, '>', "$file.new" or die "Can't write new file: $!";
while( <$in> ) {
print $out $_;
}
close $out; В этот базовый вид добавьте части, необходимые для вставки, изменения или удаления строк.
Чтобы добавить строки в начало, выведите их до входа в цикл, который выводит существующие строки.
open my $in, '<', $file or die "Can't read old file: $!";
open my $out, '>', "$file.new" or die "Can't write new file: $!";
print $out "# Add this line to the top\n"; # <--- HERE'S THE MAGIC
while( <$in> ) {
print $out $_;
}
close $out; Чтобы изменить существующие строки, вставьте код для изменения строк внутри цикла while. В этом случае код находит все строчные версии "perl" и делает их прописными. Это происходит для каждой строки, поэтому убедитесь, что вы хотите это сделать для каждой строки!
open my $in, '<', $file or die "Can't read old file: $!";
open my $out, '>', "$file.new" or die "Can't write new file: $!";
print $out "# Add this line to the top\n";
while( <$in> ) {
s/\b(perl)\b/Perl/g;
print $out $_;
}
close $out; Чтобы изменить только определённую строку, полезен номер вводимой строки $.. Сначала прочитайте и выведите строки до той, которую вы хотите изменить. Затем прочитайте строку, которую хотите изменить, измените её и выведите. После этого прочитайте и выведите оставшиеся строки:
while( <$in> ) { # print the lines before the change
print $out $_;
last if $. == 4; # line number before change
}
my $line = <$in>;
$line =~ s/\b(perl)\b/Perl/g;
print $out $line;
while( <$in> ) { # print the rest of the lines
print $out $_;
} Чтобы пропустить строки, используйте управляющие элементы цикла. next в данном примере пропускает строки комментариев, а last прекращает всю обработку после обнаружения __END__ или __DATA__.
while( <$in> ) {
next if /^\s+#/; # skip comment lines
last if /^__(END|DATA)__$/; # stop at end of code marker
print $out $_;
} Сделайте то же самое для удаления определённой строки, используя next, чтобы пропустить строки, которые не должны отображаться на выходе. В этом примере пропускается каждая пятая строка:
while( <$in> ) {
next unless $. % 5;
print $out $_;
} Если по какой-то причине вы действительно хотите увидеть весь файл сразу, а не обрабатывать строки по одной, вы можете его "словить" (пока вы можете поместить его весь в память!):
open my $in, '<', $file or die "Can't read old file: $!"
open my $out, '>', "$file.new" or die "Can't write new file: $!";
my $content = do { local $/; <$in> }; # slurp!
# do your magic here
print $out $content; Модули, такие как Path::Tiny и Tie::File, также могут помочь в этом. Однако, если возможно, избегайте чтения всего файла сразу. Perl не вернет эту память операционной системе до завершения процесса.
Вы также можете использовать однострочные скрипты Perl для изменения файла на месте. Следующий пример изменяет все вхождения "Fred" на "Barney" в файле inFile.txt, перезаписывая файл новым содержимым. С флагом -p Perl обернёт while цикл вокруг указанного вами кода с -e, а -i активирует редактирование на месте. Текущая строка находится в $_. С -p Perl автоматически выводит значение $_ в конце цикла. Подробнее см. perlrun.
perl -pi -e 's/Fred/Barney/' inFile.txt Чтобы создать резервную копию inFile.txt, добавьте расширение файла к -i.
perl -pi.bak -e 's/Fred/Barney/' inFile.txt Чтобы изменить только пятую строку, вы можете добавить проверку $., номера строки ввода, и выполнить операцию только в случае успешной проверки:
perl -pi -e 's/Fred/Barney/ if $. == 5' inFile.txt Чтобы добавить строки перед определённой строкой, добавьте строку (или строки!) перед выводом $_ Perl:
perl -pi -e 'print "Put before third line\n" if $. == 3' inFile.txt Вы даже можете добавить строку в начало файла, так как текущая строка выводится в конце цикла:
perl -pi -e 'print "Put before first line\n" if $. == 1' inFile.txt Чтобы вставить строку после уже существующей в файле, используйте флаг -n. Он похож на -p, но не выводит $_ в конце цикла, поэтому вам нужно сделать это самостоятельно. В этом случае, сначала выведите $_, а затем строку, которую нужно добавить.
perl -ni -e 'print; print "Put after fifth line\n" if $. == 5' inFile.txt Чтобы удалить строки, выведите только нужные.
perl -ni -e 'print if /d/' inFile.txt Как посчитать количество строк в файле?
(внесено brian d foy)
По сути, самый простой способ подсчитать строки в файле – просто прочитать и посчитать их:
my $count = 0;
while( <$fh> ) { $count++; } Однако, вам не обязательно считать их самостоятельно, так как Perl уже делает это с помощью переменной $., которая представляет собой текущий номер строки из последнего файла, прочитанного из файла:
1 while( <$fh> );
my $count = $.; Если вы хотите использовать $., вы можете упростить его до одной строки, например, так:
% perl -lne '} print $.; {' file
% perl -lne 'END { print $. }' file Однако, это может быть неэффективно. Если это недостаточно быстро, вы можете читать куски данных и подсчитывать количество символов новой строки:
my $lines = 0;
open my($fh), '<:raw', $filename or die "Can't open $filename: $!";
while( sysread $fh, $buffer, 4096 ) {
$lines += ( $buffer =~ tr/\n// );
}
close $fh; Однако, это не работает, если разделитель строк не является новой строкой. Вы можете изменить tr/// на s///, чтобы подсчитать количество раз, когда разделитель записи ввода $/ встречается:
my $lines = 0;
open my($fh), '<:raw', $filename or die "Can't open $filename: $!";
while( sysread $fh, $buffer, 4096 ) {
$lines += ( $buffer =~ s|$/||g; );
}
close $fh; Если вы не против использования внешней утилиты, команда wc обычно является самой быстрой, даже с дополнительной нагрузкой на взаимодействие между процессами. Убедитесь, что имя файла не содержит вредоносного кода:
#!perl -T
$ENV{PATH} = undef;
my $lines;
if( $filename =~ /^([0-9a-z_.]+)\z/ ) {
$lines = `/usr/bin/wc -l $1`
chomp $lines;
} Как удалить последние N строк из файла?
(внесено brian d foy)
Самое простое концептуальное решение – посчитать строки в файле, а затем начать с начала и вывести в новый файл количество строк (минус последние N).
Чаще всего, реальный вопрос в том, как удалить последние N строк, не делая больше одного прохода по файлу, или как сделать это без большого количества копирования. Простое понятие превращается в сложную реальность, когда в файле могут быть миллионы строк.
Один из способов – использовать File::ReadBackwards, который начинает с конца файла. Этот модуль предоставляет объект, который оборачивает реальный файл, чтобы легко перемещаться по файлу. Как только вы дойдёте до нужного места, вы можете получить фактический дескриптор файла и работать с ним как обычно. В данном случае, вы получаете позицию файла в конце последней строки, которую нужно сохранить, и обрезаете файл до этой точки:
use File::ReadBackwards;
my $filename = 'test.txt';
my $Lines_to_truncate = 2;
my $bw = File::ReadBackwards->new( $filename )
or die "Could not read backwards in [$filename]: $!";
my $lines_from_end = 0;
until( $bw->eof or $lines_from_end == $Lines_to_truncate ) {
print "Got: ", $bw->readline;
$lines_from_end++;
}
truncate( $filename, $bw->tell ); Модуль File::ReadBackwards также имеет преимущество в установке разделителя ввода-вывода на регулярное выражение.
Вы также можете использовать модуль Tie::File, который позволяет обращаться к строкам через связанный массив. Вы можете использовать обычные операции с массивами для изменения файла, включая установку последнего индекса и использование splice.
Как использовать опцию -i Perl внутри программы?
-i устанавливает значение переменной Perl $^I, которая, в свою очередь, влияет на поведение <>; см. perlrun для получения более подробной информации. Изменяя соответствующие переменные напрямую, вы можете получить такое же поведение внутри большей программы. Например:
# ...
{
local($^I, @ARGV) = ('.orig', glob("*.c"));
while (<>) {
if ($. == 1) {
print "This line should appear at the top of each file\n";
}
s/\b(p)earl\b/${1}erl/i; # Correct typos, preserving case
print;
close ARGV if eof; # Reset $.
}
}
# $^I and @ARGV return to their old values here Этот блок изменяет все .c файлы в текущей директории, создавая резервную копию исходных данных каждого файла в новом файле .c.orig.
Как скопировать файл?
(внесено brian d foy)
Используйте модуль File::Copy. Он поставляется с Perl и может выполнять истинное копирование между файловыми системами, а также делает это в переносимом формате.
use File::Copy;
copy( $original, $new_copy ) or die "Copy failed: $!"; Если вы не можете использовать File::Copy, вам придётся сделать это самостоятельно: открыть исходный файл, открыть файл назначения, затем выводить в файл назначения, читая из исходного. Вам также нужно запомнить копирование разрешений, владельца и группы в новый файл.
Как создать временное имя файла?
Если вам не нужно знать имя файла, вы можете использовать open() с undef вместо имени файла. В Perl 5.8 или более поздних версиях функция open() создаёт анонимный временный файл:
open my $tmp, '+>', undef or die $!; В противном случае, вы можете использовать модуль File::Temp.
use File::Temp qw/ tempfile tempdir /;
my $dir = tempdir( CLEANUP => 1 );
($fh, $filename) = tempfile( DIR => $dir );
# or if you don't need to know the filename
my $fh = tempfile( DIR => $dir ); Модуль File::Temp является стандартным модулем с Perl 5.6.1. Если у вас установлена несовременная версия Perl, используйте метод класса new_tmpfile из модуля IO::File, чтобы открыть дескриптор файла для чтения и записи. Используйте его, если вам не нужно знать имя файла:
use IO::File;
my $fh = IO::File->new_tmpfile()
or die "Unable to make new temporary file: $!"; Если вы твердо намерены создать временный файл вручную, используйте идентификатор процесса и/или текущее значение времени. Если вам нужно много временных файлов в одном процессе, используйте счётчик:
BEGIN {
use Fcntl;
use File::Spec;
my $temp_dir = File::Spec->tmpdir();
my $file_base = sprintf "%d-%d-0000", $$, time;
my $base_name = File::Spec->catfile($temp_dir, $file_base);
sub temp_file {
my $fh;
my $count = 0;
until( defined(fileno($fh)) || $count++ > 100 ) {
$base_name =~ s/-(\d+)$/"-" . (1 + $1)/e;
# O_EXCL is required for security reasons.
sysopen $fh, $base_name, O_WRONLY|O_EXCL|O_CREAT;
}
if( defined fileno($fh) ) {
return ($fh, $base_name);
}
else {
return ();
}
}
} Как манипулировать файлами с фиксированной длиной записей?
Самый эффективный способ – использовать pack() и unpack(). Это быстрее, чем использование substr() при работе с множеством строк.
Вот фрагмент кода для разбиения и повторного объединения строк ввода с фиксированным форматом, в данном случае из вывода обычной команды ps в стиле Berkeley:
# sample input line:
# 15158 p5 T 0:00 perl /home/tchrist/scripts/now-what
my $PS_T = 'A6 A4 A7 A5 A*';
open my $ps, '-|', 'ps';
print scalar <$ps>;
my @fields = qw( pid tt stat time command );
while (<$ps>) {
my %process;
@process{@fields} = unpack($PS_T, $_);
for my $field ( @fields ) {
print "$field: <$process{$field}>\n";
}
print 'line=', pack($PS_T, @process{@fields} ), "\n";
} Мы использовали срез хеша, чтобы легко обрабатывать поля каждой строки. Хранение ключей в массиве упрощает работу с ними как группой или проход по ним с помощью for. Это также предотвращает загрязнение программы глобальными переменными и использование символических ссылок.
Как сделать дескриптор файла локальным для подпрограммы? Как передавать дескрипторы файлов между подпрограммами? Как создать массив дескрипторов файлов?
Начиная с perl5.6, open() автоматически создаёт ссылки на дескрипторы файлов и каталогов, если вы передаёте ему неинициализированную скалярную переменную. Затем вы можете передавать эти ссылки, как и любые другие скаляры, и использовать их вместо именованных дескрипторов.
open my $fh, $file_name;
open local $fh, $file_name;
print $fh "Hello World!\n";
process_file( $fh ); Если хотите, вы можете хранить эти дескрипторы файлов в массиве или хеше. Если вы обращаетесь к ним напрямую, они не являются простыми скалярами, и вам нужно немного помочь print, поместив ссылку на дескриптор файла в фигурные скобки. Perl может определить это самостоятельно только в случае простой скалярной ссылки.
my @fhs = ( $fh1, $fh2, $fh3 );
for( $i = 0; $i <= $#fhs; $i++ ) {
print {$fhs[$i]} "just another Perl answer, \n";
} До perl5.6 вам приходилось иметь дело с различными идиомами typeglob, которые вы можете увидеть в более старом коде.
open FILE, "> $filename";
process_typeglob( *FILE );
process_reference( \*FILE );
sub process_typeglob { local *FH = shift; print FH "Typeglob!" }
sub process_reference { local $fh = shift; print $fh "Reference!" } Если вы хотите создать много анонимных дескрипторов, обратите внимание на модули Symbol или IO::Handle.
Как использовать дескриптор файла косвенно?
Косвенный дескриптор файла – это использование чего-то помимо символа в месте, где ожидается дескриптор файла. Вот способы получения косвенных дескрипторов файлов:
$fh = SOME_FH; # bareword is strict-subs hostile
$fh = "SOME_FH"; # strict-refs hostile; same package only
$fh = *SOME_FH; # typeglob
$fh = \*SOME_FH; # ref to typeglob (bless-able)
$fh = *SOME_FH{IO}; # blessed IO::Handle from *SOME_FH typeglob Или же, вы можете использовать метод new из одного из модулей IO::* для создания анонимного дескриптора файла и сохранения его в скалярной переменной.
use IO::Handle; # 5.004 or higher
my $fh = IO::Handle->new(); Затем используйте любой из них, как обычный дескриптор файла. В любом месте, где Perl ожидает дескриптор файла, вместо него можно использовать косвенный дескриптор файла. Косвенный дескриптор файла – это просто скалярная переменная, содержащая дескриптор файла. Функции, такие как print, open, seek, или оператор <FH> диамонда, будут принимать либо именованный дескриптор файла, либо скалярную переменную, содержащую его:
($ifh, $ofh, $efh) = (*STDIN, *STDOUT, *STDERR);
print $ofh "Type it: ";
my $got = <$ifh>
print $efh "What was that: $got"; Если вы передаёте дескриптор файла в функцию, вы можете написать функцию двумя способами:
sub accept_fh {
my $fh = shift;
print $fh "Sending to indirect filehandle\n";
} Или же она может локализовать typeglob и использовать дескриптор файла напрямую:
sub accept_fh {
local *FH = shift;
print FH "Sending to localized filehandle\n";
} Оба стиля работают как с объектами, так и с typeglob реальных дескрипторов файлов. (Они также могут работать со строками в некоторых случаях, но это рискованно.)
accept_fh(*STDOUT);
accept_fh($handle); В приведённых выше примерах мы присваивали дескриптор файла скалярной переменной перед его использованием. Это связано с тем, что только простые скалярные переменные, а не выражения или индексы хешей или массивов, могут использоваться с встроенными функциями, такими как print, printf, или оператором диамонда. Использование чего-либо помимо простой скалярной переменной в качестве дескриптора файла незаконно и даже не будет компилироваться:
my @fd = (*STDIN, *STDOUT, *STDERR);
print $fd[1] "Type it: "; # WRONG
my $got = <$fd[0]> # WRONG
print $fd[2] "What was that: $got"; # WRONG С print и printf вы обойдёте эту проблему, используя блок и выражение, где вы бы разместили дескриптор файла:
print { $fd[1] } "funny stuff\n";
printf { $fd[1] } "Pity the poor %x.\n", 3_735_928_559;
# Pity the poor deadbeef. Этот блок – это обычный блок, поэтому вы можете поместить в него более сложный код. Это сообщение отправляется в одно из двух мест:
my $ok = -x "/bin/cat";
print { $ok ? $fd[1] : $fd[2] } "cat stat $ok\n";
print { $fd[ 1+ ($ok || 0) ] } "cat stat $ok\n"; Этот подход к обработке print и printf как вызовов методов объекта не работает с оператором диамонда. Это связано с тем, что это реальный оператор, а не просто функция с аргументом без запятой. Предполагая, что вы храните typeglob в вашей структуре, как мы сделали выше, вы можете использовать встроенную функцию под названием readline для чтения записи так же, как и <>. Учитывая инициализацию @fd, показанную выше, это сработает, но только потому, что readline() требует typeglob. Это не работает с объектами или строками, что, возможно, является ошибкой, которую мы ещё не исправили.
$got = readline($fd[0]); Отметим, что ненадёжность косвенных дескрипторов файлов не связана с тем, являются ли они строками, typeglob, объектами или чем-либо ещё. Это синтаксис основных операторов. Игра с объектами здесь совершенно бесполезна.
Как открыть дескриптор файла для строки?
(внесено Peter J. Holzer, hjp-usenet2@hjp.at)
Начиная с Perl 5.8.0, дескриптор файла, ссылающийся на строку, можно создать, вызвав open с ссылкой на эту строку вместо имени файла. Этот дескриптор файла можно затем использовать для чтения из или записи в строку:
open(my $fh, '>', \$string) or die "Could not open string for writing";
print $fh "foo\n";
print $fh "bar\n"; # $string now contains "foo\nbar\n"
open(my $fh, '<', \$string) or die "Could not open string for reading";
my $x = <$fh>; # $x now contains "foo\n" В более старых версиях Perl модуль IO::String обеспечивает аналогичные возможности.
Как настроить формат подвала для использования с write()?
Нет встроенного способа сделать это, но perlform имеет несколько приемов, которые могут помочь храброму хакеру.
Как записать в строку с помощью write()?
(внесено brian d foy)
Если вы хотите write в строку, вам нужно открыть дескриптор файла для строки, что Perl может делать с версии Perl 5.6:
open FH, '>', \my $string;
write( FH );Поскольку вы хотите быть хорошим программистом, вы, вероятно, захотите использовать лексический дескриптор файла, даже если форматы предназначены для работы с обычными именами дескрипторов файлов, поскольку имена форматов по умолчанию принимают имя дескриптора файла. Однако вы можете управлять этим с помощью некоторых специальных переменных Perl для каждого дескриптора файла: $^, которая задает формат начала страницы, и $~, который показывает формат строки. Вам необходимо изменить дескриптор файла по умолчанию, чтобы установить эти переменные:
open my($fh), '>', \my $string;
{ # set per-filehandle variables
my $old_fh = select( $fh );
$~ = 'ANIMAL';
$^ = 'ANIMAL_TOP';
select( $old_fh );
}
format ANIMAL_TOP =
ID Type Name
.
format ANIMAL =
@## @<<< @<<<<<<<<<<<<<<
$id, $type, $name
.Хотя write может работать с лексическими или пакетными переменными, любые переменные, которые вы используете, должны быть в области действия формата. Вероятно, вы захотите локализовать некоторые пакетные переменные:
{
local( $id, $type, $name ) = qw( 12 cat Buster );
write( $fh );
}
print $string;Также есть несколько трюков, которые можно применить с formline и переменной аккумулятора $^A, но вы теряете много преимуществ форматов, так как formline не будет обрабатывать постраничную разметку и т. д. В итоге вы переопределяете форматы, когда используете их.
Как вывести числа с добавленными запятыми?
(внесено brian d foy и Benjamin Goldberg)
Вы можете использовать Number::Format для разделения разрядов числа. Он обрабатывает информацию о локали для тех из вас, кто хочет вставлять точки (или что-то ещё).
Эта подпрограмма добавит запятые к вашему числу:
sub commify {
local $_ = shift;
1 while s/^([-+]?\d+)(\d{3})/$1,$2/;
return $_;
}Этот регулярный выражение от Benjamin Goldberg добавит запятые к числам:
s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;Его легче понять с комментариями:
s/(
^[-+]? # beginning of number.
\d+? # first digits before first comma
(?= # followed by, (but not included in the match) :
(?>(?:\d{3})+) # some positive multiple of three digits.
(?!\d) # an *exact* multiple, not x * 3 + 1 or whatever.
)
| # or:
\G\d{3} # after the last group, get three digits
(?=\d) # but they have to have more digits after them.
)/$1,/xg;Как перевести тильды (~) в имени файла?
Используйте оператор <> (glob()) , описанный в perlfunc. В версиях Perl, более старых чем 5.6, необходимо, чтобы была установлена оболочка, которая понимает тильды. Более поздние версии Perl имеют эту функцию встроенной. Модуль File::KGlob (доступен с CPAN) предоставляет более переносимые функции glob.
Внутри Perl можно использовать это напрямую:
$filename =~ s{
^ ~ # find a leading tilde
( # save this in $1
[^/] # a non-slash character
* # repeated 0 or more times (0 means me)
)
}{
$1
? (getpwnam($1))[7]
: ( $ENV{HOME} || $ENV{LOGDIR} )
}ex;Почему при открытии файла на чтение и запись он очищается?
Потому что вы используете что-то вроде этого, что обнуляет файл перед предоставлением доступа на чтение и запись:
open my $fh, '+>', '/path/name'; # WRONG (almost always)Ошибочка. Вместо этого используйте это, что завершится ошибкой, если файл не существует:
open my $fh, '+<', '/path/name'; # open for updateИспользование ">" всегда перезаписывает или создаёт. Использование "<" никогда ничего не делает. "+" не изменяет этого.
Вот примеры различных способов открытия файлов. Те, которые используют sysopen, предполагают, что вы загрузили константы из Fcntl:
use Fcntl;Чтобы открыть файл для чтения:
open my $fh, '<', $path or die $!;
sysopen my $fh, $path, O_RDONLY or die $!;Чтобы открыть файл для записи, создать новый файл, если нужно, или же обнулить старый файл:
open my $fh, '>', $path or die $!;
sysopen my $fh, $path, O_WRONLY|O_TRUNC|O_CREAT or die $!;
sysopen my $fh, $path, O_WRONLY|O_TRUNC|O_CREAT, 0666 or die $!;Чтобы открыть файл для записи, создать новый файл, файл не должен существовать:
sysopen my $fh, $path, O_WRONLY|O_EXCL|O_CREAT or die $!;
sysopen my $fh, $path, O_WRONLY|O_EXCL|O_CREAT, 0666 or die $!;Чтобы открыть файл для добавления, создать, если необходимо:
open my $fh, '>>', $path or die $!;
sysopen my $fh, $path, O_WRONLY|O_APPEND|O_CREAT or die $!;
sysopen my $fh, $path, O_WRONLY|O_APPEND|O_CREAT, 0666 or die $!;Чтобы открыть файл для добавления, файл должен существовать:
sysopen my $fh, $path, O_WRONLY|O_APPEND or die $!;Чтобы открыть файл для обновления, файл должен существовать:
open my $fh, '+<', $path or die $!;
sysopen my $fh, $path, O_RDWR or die $!;Чтобы открыть файл для обновления, создать файл, если необходимо:
sysopen my $fh, $path, O_RDWR|O_CREAT or die $!;
sysopen my $fh, $path, O_RDWR|O_CREAT, 0666 or die $!;Чтобы открыть файл для обновления, файл не должен существовать:
sysopen my $fh, $path, O_RDWR|O_EXCL|O_CREAT or die $!;
sysopen my $fh, $path, O_RDWR|O_EXCL|O_CREAT, 0666 or die $!;Чтобы открыть файл без блокировки, создавая его при необходимости:
sysopen my $fh, '/foo/somefile', O_WRONLY|O_NDELAY|O_CREAT
or die "can't open /foo/somefile: $!":Обратите внимание, что ни создание, ни удаление файлов не гарантируются как атомарные операции над NFS. То есть, два процесса могут оба успешно создать или удалить один и тот же файл! Поэтому O_EXCL не так эксклюзивен, как хотелось бы.
См. также perlopentut.
Почему иногда появляется ошибка "Список аргументов слишком длинный" при использовании <*>?
Оператор <> выполняет операцию подстановки (см. выше). В версиях Perl до v5.6.0 внутренний оператор glob() запускает csh(1) для фактического расширения подстановки, но csh не может обрабатывать более 127 элементов и поэтому выводит сообщение об ошибке Argument list too long. У людей, которые установили tcsh как csh, такой проблемы не будет, но их пользователи могут быть удивлены этим.
Чтобы обойти это, обновите Perl до версии 5.6.0 или новее, выполните подстановку самостоятельно с помощью readdir() и шаблонов или используйте модуль, такой как File::Glob, который не использует оболочку для подстановки.
Как открыть файл с ведущим ">" или хвостовыми пробелами?
(внесено Brian McCauley)
Специальная форма функции open() Perl с двумя аргументами игнорирует хвостовые пробелы в именах файлов и выводит режим из определенных ведущих символов (или заключительного "|"). В старых версиях Perl это была единственная форма open() и поэтому она широко используется в старом коде и книгах.
Если у вас нет особых причин использовать форму с двумя аргументами, следует использовать форму с тремя аргументами open(), которая не рассматривает символы в имени файла как особые.
open my $fh, "<", " file "; # filename is " file "
open my $fh, ">", ">file"; # filename is ">file"Как надежно переименовать файл?
Если ваша операционная система поддерживает утилиту mv(1) или её функциональный эквивалент, это работает:
rename($old, $new) or system("mv", $old, $new);Для большей переносимости лучше использовать модуль File::Copy. Скопируйте файл в новое имя (проверяя возвращаемые значения), а затем удалите старый. Это не совсем то же самое, что и переименование, которое сохраняет метаданные, такие как права доступа, временные метки, информацию об inode и т. д.
Как заблокировать файл?
Встроенная функция Perl flock() (подробнее см. perlfunc) вызывает flock(2), если она существует, fcntl(2), если нет (в Perl версии 5.004 и выше), и lockf(3), если не существует ни одна из предыдущих системных функций. На некоторых системах она может даже использовать другой вид системной блокировки. Вот несколько моментов, связанных с Perl flock():
-
Вызывает критическую ошибку, если ни одна из трех системных функций (или их близкие аналоги) не существует.
-
lockf(3) не предоставляет совместную блокировку и требует, чтобы дескриптор файла был открыт для записи (или добавления, или чтения/записи).
-
В некоторых версиях flock() нельзя блокировать файлы через сеть (например, на файловых системах NFS), поэтому при компиляции Perl нужно принудительно использовать fcntl(2). Но даже это под вопросом. См. запись flock в perlfunc и файл INSTALL в исходном дистрибутиве для получения информации о компиляции Perl для этого.
Два потенциально неявных, но традиционных аспекта flock – это ожидание бесконечно до получения блокировки, и что блокировки являются только рекомендательными. Такие условные блокировки более гибкие, но предоставляют меньше гарантий. Это означает, что файлы, заблокированные с помощью flock(), могут быть изменены программами, которые также не используют flock(). Автомобили, которые останавливаются на красный свет, хорошо уживаются друг с другом, но не с автомобилями, которые не останавливаются на красный свет. См. страницу man perlport, документацию вашей платформы или локальные страницы man вашей системы для получения подробностей. Лучше всего предположить традиционное поведение, если вы пишете переносимые программы. (Если вы не пишете переносимые программы, вы всегда можете чувствовать себя вправе писать для особенностей своей системы («функций»). Слишком большое следование переносимости не должно мешать вам выполнить свою работу.)
Для получения дополнительной информации о блокировке файлов, см. также "Блокировка файлов" в perlopentut, если она у вас есть (новая в 5.6).
Почему нельзя просто открыть(FH, ">file.lock")?
Распространенный код, который НЕ НУЖНО ИСПОЛЬЗОВАТЬ, выглядит так:
sleep(3) while -e 'file.lock'; # PLEASE DO NOT USE
open my $lock, '>', 'file.lock'; # THIS BROKEN CODEЭто классический пример гонки: два шага для выполнения чего-то, что должно выполняться за один. Вот почему аппаратное обеспечение компьютера предоставляет атомарную инструкцию «тест-и-установка». Теоретически, это «должно» работать:
sysopen my $fh, "file.lock", O_WRONLY|O_EXCL|O_CREAT
or die "can't open file.lock: $!";но, к сожалению, создание (и удаление) файлов не является атомарным над NFS, поэтому это не будет работать (по крайней мере, не всегда) через сеть. Предлагались различные схемы с использованием link(), но они, как правило, включают ожидание, что также нежелательно.
Я всё ещё не понимаю блокировку. Я просто хочу увеличить число в файле. Как это сделать?
Разве вам никто не говорил, что счётчики посещений веб-страниц бесполезны? Они не подсчитывают количество посещений, это пустая трата времени и служат только для удовлетворения тщеславия автора. Лучше выбрать случайное число; они более реалистичны.
В любом случае, вот что можно сделать, если вы не можете удержаться.
use Fcntl qw(:DEFAULT :flock);
sysopen my $fh, "numfile", O_RDWR|O_CREAT or die "can't open numfile: $!";
flock $fh, LOCK_EX or die "can't flock numfile: $!";
my $num = <$fh> || 0;
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: $!";Вот гораздо лучший счётчик посещений веб-страниц:
$hits = int( (time() - 850_000_000) / rand(1_000) );Если счётчик не впечатляет ваших друзей, то код, возможно, впечатлит. :-)
Всё, что я хочу сделать, это добавить небольшое количество текста в конец файла. Нужно ли мне всё ещё использовать блокировку?
Если вы используете систему, которая правильно реализует flock, и используете пример добавления кода из «perldoc -f flock», всё будет нормально, даже если операционная система, на которой вы работаете, не поддерживает режим добавления должным образом (если такая система существует). Таким образом, если вы довольны, ограничиваясь системами, которые поддерживают flock (а это не очень большая ограничение), то это то, что вам нужно сделать.
Если вы знаете, что будете использовать только систему, которая правильно поддерживает режим добавления (например, локальную файловую систему на современной Unix-подобной системе), и вы сохраняете файл в режиме с буферизацией блоков, и вы записываете меньше одного буфера вывода между каждым ручным сбросом буфера, то каждый буфер, скорее всего, будет записан в конец файла одним куском без смешивания с выводом других пользователей. Вы также можете использовать функцию syswrite, которая просто является обёрткой вокруг системной функции write(2).
По-прежнему существует небольшая теоретическая вероятность того, что сигнал прервёт операцию на уровне системы write() до завершения. Также существует возможность, что некоторые реализации STDIO могут вызывать несколько системных write() даже если буфер был пустым изначально. Возможны системы, где эта вероятность сводится к нулю, и это не проблема при использовании :perlio вместо вашей системной STDIO.
Как случайным образом обновить двоичный файл?
Если вы просто пытаетесь подправить двоичный файл, во многих случаях достаточно простого:
perl -i -pe 's{window manager}{window mangler}g' /usr/bin/emacs Однако, если у вас есть записи фиксированного размера, то вы, возможно, сделаете что-то более похожее на это:
my $RECSIZE = 220; # size of record, in bytes
my $recno = 37; # which record to update
open my $fh, '+<', 'somewhere' or die "can't update somewhere: $!";
seek $fh, $recno * $RECSIZE, 0;
read $fh, $record, $RECSIZE == $RECSIZE or die "can't read record $recno: $!";
# munge the record
seek $fh, -$RECSIZE, 1;
print $fh $record;
close $fh; Блокировка и проверка ошибок оставлены как упражнение для читателя. Не забывайте об этом, иначе пожалеете.
Как получить отметку времени файла в Perl?
Если вы хотите получить время последнего чтения, записи или изменения метаданных (владелец и т. д.) файла, используйте операции проверки файлов -A, -M или -C, как описано в perlfunc. Они возвращают возраст файла (измеренный относительно времени начала вашей программы) в днях в виде числа с плавающей точкой. Некоторые платформы могут не предоставлять все эти времена. Подробности см. в perlport. Чтобы получить «сырое» время в секундах с эпохи, вы бы вызвали функцию stat, а затем использовали localtime(), gmtime(), или POSIX::strftime() для преобразования этого в удобочитаемый формат.
Вот пример:
my $write_secs = (stat($file))[9];
printf "file %s updated at %s\n", $file,
scalar localtime($write_secs); Если вы предпочитаете что-то более удобочитаемое, используйте модуль File::stat (входит в стандартное распределение с версии 5.004 и выше):
# error checking left as an exercise for reader.
use File::stat;
use Time::localtime;
my $date_string = ctime(stat($file)->mtime);
print "file $file updated at $date_string\n"; Подход POSIX::strftime() имеет преимущество в том, что он, теоретически, независим от текущего региональных настроек. Подробности см. в perllocale.
Как установить отметку времени файла в Perl?
Используйте функцию utime(), документация по которой представлена в "utime" in perlfunc. В качестве примера, вот небольшая программа, которая копирует время чтения и записи из первого аргумента во все остальные.
if (@ARGV < 2) {
die "usage: cptimes timestamp_file other_files ...\n";
}
my $timestamp = shift;
my($atime, $mtime) = (stat($timestamp))[8,9];
utime $atime, $mtime, @ARGV; Как обычно, проверка ошибок оставлена как упражнение для читателя.
В документации perldoc для utime также есть пример, который имеет тот же эффект, что и touch(1) для файлов, которые уже существуют.
Некоторые файловые системы имеют ограниченные возможности хранения времени файла с ожидаемой точностью. Например, файловые системы FAT и HPFS не могут создавать даты файлов с точностью выше двух секунд. Это ограничение файловой системы, а не функции utime().
Как выводить данные сразу в несколько файлов?
Для подключения одного файлового дескриптора к нескольким выходным файловым дескрипторам, вы можете использовать модули IO::Tee или Tie::FileHandle::Multiplex.
Если это нужно сделать только один раз, вы можете выводить данные в каждый файловый дескриптор по отдельности.
for my $fh ($fh1, $fh2, $fh3) { print $fh "whatever\n" } Как прочитать весь файл сразу?
Обычный подход Perl для обработки всех строк файла — делать это по одной строке:
open my $input, '<', $file or die "can't open $file: $!";
while (<$input>) {
chomp;
# do something with $_
}
close $input or die "can't close $file: $!"; Это намного эффективнее, чем чтение всего файла в память как массива строк и обработка его по одному элементу за раз, что часто — если не всегда — является неправильным подходом. Всякий раз, когда вы видите такое:
my @lines = <INPUT>; Вы должны долго и тщательно обдумать, почему вам нужно загружать все сразу. Это просто не масштабируемое решение.
Если вы «mmap» файл с помощью модуля File::Map из CPAN, вы можете практически загрузить весь файл в строку без фактического хранения его в памяти:
use File::Map qw(map_file);
map_file my $string, $filename; После отображения вы можете обращаться к $string так же, как к любой другой строке. Поскольку вам не обязательно загружать данные, mmap может быть очень быстрым и не увеличивать ваш объем памяти.
Возможно, вам будет интереснее использовать стандартный модуль Tie::File или bindings модуля DB_File $DB_RECNO, которые позволяют привязать массив к файлу, так что обращение к элементу массива фактически обращается к соответствующей строке в файле.
Если вы хотите загрузить весь файл, вы можете использовать модуль Path::Tiny, чтобы сделать это в одном простом и эффективном шаге:
use Path::Tiny;
my $all_of_it = path($filename)->slurp; # entire file in scalar
my @all_lines = path($filename)->lines; # one line per element Или вы можете прочитать содержимое всего файла в скаляр, как в этом:
my $var;
{
local $/;
open my $fh, '<', $file or die "can't open $file: $!";
$var = <$fh>;
} Это временно отключает разделитель записей и автоматически закроет файл при выходе из блока. Если файл уже открыт, просто используйте это:
my $var = do { local $/; <$fh> }; Вы также можете использовать локальную @ARGV для устранения open.
my $var = do { local( @ARGV, $/ ) = $file; <> }; Как читать файл по абзацам?
Используйте переменную $/ (см. perlvar для получения дополнительных сведений). Вы можете установить ее в значение "" для устранения пустых абзацев (например, "abc\n\n\n\ndef" обрабатывается как два абзаца, а не три) или в значение "\n\n" для принятия пустых абзацев.
Обратите внимание, что пустая строка не должна содержать пробелов. Таким образом, "fred\n \nstuff\n\n" — один абзац, а "fred\n\nstuff\n\n" — два.
Как прочитать один символ из файла? С клавиатуры?
Для большинства файловых дескрипторов можно использовать встроенную функцию getc(), но она (легко) не будет работать с терминальным устройством. Для STDIN используйте модуль Term::ReadKey из CPAN или пример кода в "getc" in perlfunc.
Если ваша система поддерживает портативный интерфейс программирования операционных систем (POSIX), вы можете использовать следующий код, в котором, как вы заметите, отключается также обработка эха.
#!/usr/bin/perl -w
use strict;
$| = 1;
for (1..4) {
print "gimme: ";
my $got = getone();
print "--> $got\n";
}
exit;
BEGIN {
use POSIX qw(:termios_h);
my ($term, $oterm, $echo, $noecho, $fd_stdin);
my $fd_stdin = fileno(STDIN);
$term = POSIX::Termios->new();
$term->getattr($fd_stdin);
$oterm = $term->getlflag();
$echo = ECHO | ECHOK | ICANON;
$noecho = $oterm & ~$echo;
sub cbreak {
$term->setlflag($noecho);
$term->setcc(VTIME, 1);
$term->setattr($fd_stdin, TCSANOW);
}
sub cooked {
$term->setlflag($oterm);
$term->setcc(VTIME, 0);
$term->setattr($fd_stdin, TCSANOW);
}
sub getone {
my $key = '';
cbreak();
sysread(STDIN, $key, 1);
cooked();
return $key;
}
}
END { cooked() } Модуль Term::ReadKey из CPAN может быть проще в использовании. Недавние версии также включают поддержку непортативных систем.
use Term::ReadKey;
open my $tty, '<', '/dev/tty';
print "Gimme a char: ";
ReadMode "raw";
my $key = ReadKey 0, $tty;
ReadMode "normal";
printf "\nYou said %s, char number %03d\n",
$key, ord $key; Как узнать, есть ли ожидающий символ в файловом дескрипторе?
Прежде всего, вы должны получить расширение Term::ReadKey из CPAN. Как мы упоминали ранее, оно теперь даже имеет ограниченную поддержку непортативных систем (читай: не открытые системы, закрытые, проприетарные, не POSIX, не Unix и т. д.).
Также следует проверить список часто задаваемых вопросов в comp.unix.*, так как ответ в сущности тот же. Он очень зависит от системы. Вот одно решение, которое работает на системах BSD:
sub key_ready {
my($rin, $nfd);
vec($rin, fileno(STDIN), 1) = 1;
return $nfd = select($rin,undef,undef,0);
} Если вы хотите узнать, сколько символов ожидает обработки, можно также использовать вызов ioctl FIONREAD. Инструмент h2ph, который поставляется с Perl, пытается преобразовать файлы заголовков C в код Perl, что может быть require. FIONREAD в конечном итоге определен как функция в файле sys/ioctl.ph:
require './sys/ioctl.ph';
$size = pack("L", 0);
ioctl(FH, FIONREAD(), $size) or die "Couldn't call ioctl: $!\n";
$size = unpack("L", $size); Если h2ph не был установлен или не работает для вас, вы можете вручную grep файлы заголовков:
% grep FIONREAD /usr/include/*/*
/usr/include/asm/ioctls.h:#define FIONREAD 0x541B Или написать небольшую программу на C с помощью редактора чемпионов:
% cat > fionread.c
#include <sys/ioctl.h>
main() {
printf("%#08x\n", FIONREAD);
}
^D
% cc -o fionread fionread.c
% ./fionread
0x4004667f А затем захардкодить её, оставив портирование в качестве упражнения для вашего преемника.
$FIONREAD = 0x4004667f; # XXX: opsys dependent
$size = pack("L", 0);
ioctl(FH, $FIONREAD, $size) or die "Couldn't call ioctl: $!\n";
$size = unpack("L", $size); FIONREAD требует файлового дескриптора, подключенного к потоку, то есть сокеты, каналы и устройства tty работают, но не файлы.
Как сделать tail -f в Perl?
Первая попытка
seek($gw_fh, 0, 1); Выражение seek($gw_fh, 0, 1) не изменяет текущую позицию, но оно очищает состояние конца файла в дескрипторе, так что следующий <$gw_fh> заставит Perl снова попытаться прочитать что-то.
Если это не работает (это зависит от функций вашей реализации stdio), вам нужно что-то более похожее на это:
for (;;) {
for ($curpos = tell($gw_fh); <$gw_fh>; $curpos =tell($gw_fh)) {
# search for some stuff and put it into files
}
# sleep for a while
seek($gw_fh, $curpos, 0); # seek to where we had been
} Если это все еще не работает, посмотрите на метод clearerr из IO::Handle, который сбрасывает состояние ошибки и конца файла в дескрипторе.
Также есть модуль File::Tail из CPAN.
Как дублировать файловый дескриптор в Perl?
Если вы проверите "open" in perlfunc, вы увидите, что несколько способов вызова open() должны сработать. Например:
open my $log, '>>', '/foo/logfile';
open STDERR, '>&', $log; Или даже с числовым дескриптором в явном виде:
my $fd = $ENV{MHCONTEXTFD};
open $mhcontext, "<&=$fd"; # like fdopen(3S) Обратите внимание, что "<&STDIN" создает копию, а "<&=STDIN" создает псевдоним. Это означает, что если вы закрываете псевдоним, все псевдонимы становятся недоступными. Это не относится к скопированному.
Проверка ошибок, как всегда, оставлена как упражнение для читателя.
Как закрыть файловый дескриптор по номеру?
Если по какой-либо причине у вас есть файловый дескриптор вместо файлового дескриптора (возможно, вы использовали POSIX::open), вы можете использовать функцию close() из модуля POSIX:
use POSIX ();
POSIX::close( $fd ); Это редко необходимо, так как функция Perl close() должна использоваться для вещей, которые Perl открыл сам, даже если это был дубликат числового дескриптора, как в MHCONTEXT выше. Но если вам действительно нужно, вы можете сделать это:
require './sys/syscall.ph';
my $rc = syscall(SYS_close(), $fd + 0); # must force numeric
die "can't sysclose $fd: $!" unless $rc == -1; Или просто используйте функцию fdopen(3S) из open():
{
open my $fh, "<&=$fd" or die "Cannot reopen fd=$fd: $!";
close $fh;
} Почему я не могу использовать "C:\temp\foo" в путях DOS? Почему `C:\temp\foo.exe` не работает?
Упс! Вы только что вставили табуляцию и символ перевода страницы в имя файла! Помните, что в строках с двойными кавычками ("как\это") обратный слеш является управляющим символом. Полный список таких символов находится в "Quote and Quote-like Operators" in perlop. Неудивительно, что у вас нет файла под названием "c:(tab)emp(formfeed)oo" или "c:(tab)emp(formfeed)oo.exe" на вашей старой файловой системе DOS.
Используйте одинарные кавычки для строк или (предпочтительно) используйте прямые слеши. Поскольку все версии DOS и Windows с момента версии MS-DOS 2.0 или около того обрабатывали / и \ одинаково в пути, вы можете использовать тот, который не конфликтует с Perl — или с оболочкой POSIX, ANSI C и C++, awk, Tcl, Java или Python, чтобы упомянуть лишь несколько. POSIX пути также более переносимы.
Почему glob("*.*") не получает все файлы?
Потому что даже на непортативных платформах, функция glob Perl следует стандартной семантике Unix globbing. Вам потребуется glob("*") для получения всех (не скрытых) файлов. Это делает glob() переносимым даже на устаревшие системы. В вашей системе может быть реализована проприетарная функция globbing. Проверьте ее документацию для получения дополнительной информации.
Почему Perl позволяет мне удалять файлы только для чтения? Почему -i перезаписывает защищенные файлы? Разве это ошибка в Perl?
Это подробно и тщательно описано в статье file-dir-perms в коллекции «Far More Than You Ever Wanted To Know» в http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz.
Резюме: узнайте, как работает ваша файловая система. Разрешения на файл определяют, что может произойти с данными в этом файле. Разрешения на каталог определяют, что может произойти со списком файлов в этом каталоге. Если вы удаляете файл, вы удаляете его имя из каталога (следовательно, операция зависит от разрешений каталога, а не файла). Если вы пытаетесь записать в файл, разрешения на файл определяют, разрешено ли вам это.
Как выбрать случайную строку из файла?
Если не загружать файл в базу данных или не предварительно индексировать строки в файле, можно сделать несколько вещей.
Вот алгоритм отбора из резервуара из книги «Camel Book»:
srand;
rand($.) < 1 && ($line = $_) while <>; Это имеет существенное преимущество в плане места по сравнению с чтением всего файла. Доказательство этого метода можно найти в книге «Искусство программирования вычислительных машин», том 2, раздел 3.4.2, Дональда Кнута.
Вы можете использовать модуль File::Random, который предоставляет функцию для этого алгоритма:
use File::Random qw/random_line/;
my $line = random_line($filename); Другой способ — использовать модуль Tie::File, который рассматривает весь файл как массив. Просто обратитесь к элементу массива случайным образом.
Почему при печати массива строк появляются странные пробелы?
(представлено brian d foy)
Если при печати массива вы видите пробелы между элементами массива, вы, вероятно, интерполируете массив в двойных кавычках:
my @animals = qw(camel llama alpaca vicuna);
print "animals are: @animals\n"; Это двойные кавычки, а не print, делают это. Всякий раз, когда вы интерполируете массив в контексте двойных кавычек, Perl соединяет элементы пробелами (или тем, что в $", по умолчанию это пробел):
animals are: camel llama alpaca vicuna Это отличается от печати массива без интерполяции:
my @animals = qw(camel llama alpaca vicuna);
print "animals are: ", @animals, "\n"; Теперь вывод не содержит пробелов между элементами, потому что элементы @animals просто становятся частью списка для print:
animals are: camelllamaalpacavicuna Вы можете заметить это, когда каждый из элементов @array заканчивается новой строкой. Вы ожидаете напечатать один элемент в строке, но обратите внимание, что каждая строка после первой имеет отступ:
this is a line
this is another line
this is the third line Этот дополнительный пробел происходит от интерполяции массива. Если вы не хотите ничего помещать между элементами массива, не используйте массив в двойных кавычках. Вы можете передать его на печать без них:
print @lines; Как пройтись по дереву каталогов?
(представлено brian d foy)
Модуль File::Find, который поставляется с Perl, выполняет всю тяжелую работу по прохождению структуры каталогов. Он поставляется с Perl. Вы просто вызываете подпрограмму find с обратной подпрограммой и каталогами, по которым нужно пройтись:
use File::Find;
find( \&wanted, @directories );
sub wanted {
# full path in $File::Find::name
# just filename in $_
... do whatever you want to do ...
} Модуль File::Find::Closures, который можно загрузить из CPAN, предоставляет множество готовых подпрограмм, которые вы можете использовать с File::Find.
Модуль File::Finder, который можно загрузить из CPAN, может помочь вам создать обратную подпрограмму, используя синтаксис, более близкий к синтаксису find утилиты командной строки:
use File::Find;
use File::Finder;
my $deep_dirs = File::Finder->depth->type('d')->ls->exec('rmdir','{}');
find( $deep_dirs->as_options, @places ); Модуль File::Find::Rule, который можно загрузить из CPAN, имеет аналогичный интерфейс, но также выполняет проход:
use File::Find::Rule;
my @files = File::Find::Rule->file()
->name( '*.pm' )
->in( @INC ); Как удалить дерево каталогов?
(представлено brian d foy)
Если у вас есть пустой каталог, вы можете использовать встроенную в Perl функцию rmdir. Если каталог не пустой (т. е. с файлами или подкаталогами), вам нужно либо очистить его самостоятельно (много работы), либо использовать модуль, который поможет вам.
Модуль File::Path, который поставляется с Perl, имеет функцию remove_tree, которая может выполнить всю тяжелую работу за вас:
use File::Path qw(remove_tree);
remove_tree( @directories ); Модуль File::Path также имеет устаревший интерфейс к старой подпрограмме rmtree.
Как скопировать весь каталог?
(представлено Shlomi Fish)
Чтобы выполнить эквивалент cp -R (т. е. рекурсивно скопировать все дерево каталогов) в переносимом Perl, вам нужно будет либо написать что-то самостоятельно, либо найти хороший модуль CPAN, такой как File::Copy::Recursive.
АВТОР И АВТОРСКИЕ ПРАВА
Авторские права (c) 1997-2010 Tom Christiansen, Nathan Torkington и другие авторы, как указано. Все права защищены.
Данная документация бесплатна; вы можете распространять и/или изменять ее на тех же условиях, что и Perl.
Независимо от его распространения, все примеры кода здесь находятся в общественном достоянии. Вам разрешается и рекомендуется использовать этот код и любые производные от него в своих программах для развлечения или с выгодой, как вам заблагорассудится. Простая ссылка в коде с указанием на часто задаваемые вопросы будет вежливой, но не обязательной.
© 1993–2023 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.38.0/perlfaq5