perlfaq5
СОДЕРЖАНИЕ
- НАЗВАНИЕ
- ВЕРСИЯ
- ОПИСАНИЕ
- Как сбросить/отменить буферизацию для выходного дескриптора файла? Почему это нужно делать?
- Как изменить, удалить или вставить строку в файл или добавить в начало файла?
- Как посчитать количество строк в файле?
- Как удалить последние N строк из файла?
- Как использовать опцию -i Perl внутри программы?
- Как скопировать файл?
- Как создать временное имя файла?
- Как обрабатывать файлы с фиксированной длиной записи?
- Как сделать дескриптор файла локальным для подпрограммы? Как передавать дескрипторы файлов между подпрограммами? Как создать массив дескрипторов файлов?
- Как использовать дескриптор файла косвенно?
- Как настроить формат подвала для использования с write()?
- Как записать в строку с помощью write()?
- Как открыть дескриптор файла для строки?
- Как вывести числа с добавленными запятыми?
- Как перевести тильды (~) в имени файла?
- Почему при открытии файла для чтения-записи он очищается?
- Почему иногда возникает ошибка "Список аргументов слишком длинный" при использовании <*>?
- Как открыть файл с ведущим ">" или хвостовыми пробелами?
- Как надежно переименовать файл?
- Как заблокировать файл?
- Почему нельзя просто открыть(FH, ">file.lock")?
- Я все еще не могу заблокировать. Мне нужно просто увеличить число в файле. Как это сделать?
- Хочу просто добавить немного текста в конец файла. Нужно ли использовать блокировку?
- Как произвольно обновить двоичный файл?
- Как получить отметку времени файла в Perl?
- Как установить отметку времени файла в Perl?
- Как печатать одновременно в несколько файлов?
- Как прочитать весь файл сразу?
- Как читать файл по абзацам?
- Как прочитать один символ из файла? С клавиатуры?
- Как узнать, есть ли ожидающий символ в дескрипторе файла?
- Как реализовать tail -f в Perl?
- Как дублировать дескриптор файла в Perl?
- Как закрыть дескриптор файла по номеру?
- Почему нельзя использовать "C:\temp\foo" в путях DOS? Почему `C:\temp\foo.exe` не работает?
- Почему glob("*.*") не получает все файлы?
- Почему Perl позволяет удалять файлы только для чтения? Почему -i перезаписывает защищенные файлы? Это ошибка в Perl?
- Как выбрать случайную строку из файла?
- Почему при выводе массива строк получаются странные пробелы?
- Как пройтись по дереву каталогов?
- Как удалить дерево каталогов?
- Как скопировать весь каталог?
- АВТОР И АВТОРСКИЕ ПРАВА
НАЗВАНИЕ
perlfaq5 - Файлы и форматы
ВЕРСИЯ
версия 5.021011
ОПИСАНИЕ
Этот раздел посвящен вводу-выводу и проблемам с "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, добавьте расширение файла:
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]); Обратите внимание, что нестабильность косвенных дескрипторов файлов не связана с тем, являются ли они строками, шаблонами, объектами или чем-либо еще. Это синтаксис основных операторов. Игры с объектами здесь ничем не помогут.
Как настроить формат подвала для использования с 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 не будет обрабатывать постраничность и т.д. В итоге вы сами переиспользуете форматы.
Как открыть дескриптор файла для строки?
(внесено 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 предоставляет похожую функциональность.
Как добавить запятые к моим числам при выводе?
(внесено 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, требуется, чтобы у вас был установлен shell, понимающий тильды. Более поздние версии 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.
Почему иногда при использовании <*> появляется ошибка "Список аргументов слишком длинный"?
Оператор <> выполняет операцию glob (см. выше). В версиях Perl, более ранних, чем v5.6.0, внутренний оператор glob() создаёт виртуальный fork csh(1) для фактического расширения glob, но csh не может обработать более 127 элементов и поэтому выдаёт сообщение об ошибке Argument list too long. У людей, которые установили tcsh как csh, этой проблемы не будет, но их пользователи могут быть удивлены этим.
Чтобы обойти это, обновите Perl до v5.6.0 или выше, сами выполните glob с readdir() и шаблонами или используйте модуль, такой как File::Glob, который не использует shell для выполнения 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 вместо этого. Просто скопируйте новый файл в новое имя (проверьте возвращаемые значения), а затем удалите старый. Это не совсем то же самое, что и rename(), которая сохраняет метаданные, такие как права, метки времени, информацию об inode и т.д.
Как заблокировать файл?
Встроенная функция Perl flock() (см. perlfunc для деталей) вызовет flock(2), если она существует, fcntl(2), если нет (в Perl версии 5.004 и выше), и lockf(3), если ни одна из двух предыдущих системных функций не существует. В некоторых системах она может даже использовать другой вид родного блокирования. Вот несколько нюансов с flock() Perl:
-
Выдаёт ошибку fatal, если ни одна из трёх системных функций (или их близкий эквивалент) не существует.
-
lockf(3) не предоставляет совместного блокирования и требует, чтобы дескриптор файла был открыт для записи (или добавления, или чтения/записи).
-
В некоторых версиях flock() нельзя блокировать файлы через сеть (например, на файловых системах NFS), поэтому нужно принудительно использовать fcntl(2) при сборке Perl. Но даже это спорно. См. запись flock в perlfunc и файл INSTALL в дистрибутиве исходных кодов для получения информации о сборке Perl для этого.
Два потенциально неочевидных, но традиционных аспекта семантики flock заключаются в том, что она ожидает неопределённое время, пока блокировка не будет предоставлена, и что её блокировки являются только рекомендательными. Такие дискреционные блокировки более гибкие, но предоставляют меньше гарантий. Это означает, что файлы, заблокированные с помощью flock(), могут быть изменены программами, которые также не используют flock(). Машины, которые останавливаются на красный свет, хорошо уживаются друг с другом, но не с машинами, которые не останавливаются на красный свет. См. руководство perlport, конкретную документацию вашего порта или локальные руководства вашей системы для получения подробностей. Лучше всего предположить традиционное поведение при написании переносимых программ. (Если вы не делаете этого, вы всегда можете писать для особенностей (иногда называемых «функциями») вашей системы. Слепое следование проблемам переносимости не должно мешать вам выполнять свою работу.)
Для получения дополнительной информации о блокировке файлов, также см. "Блокировка файлов" в perlopentut, если у вас есть (новая функция для 5.6).
Почему я не могу просто написать open(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 (и это не очень большое ограничение), то это то, что вам следует сделать.
Если вы знаете, что будете использовать только систему, которая правильно реализует добавление (т.е. не Win32), то вы можете опустить seek из кода в предыдущем ответе.
Если вы знаете, что пишете код, который будет выполняться только на операционной системе и файловой системе, которые правильно реализуют режим добавления (например, локальная файловая система на современной 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" в 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 или привязки модуля 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; <> }; Для обычных файлов вы также можете использовать функцию read.
read( $fh, $var, -s $fh ); Этот третий аргумент проверяет размер данных в байтах для $fh файлового дескриптора и считывает это количество байтов в буфер $var.
Как прочитать файл по абзацам?
Используйте переменную $/ (см. perlvar для подробностей). Вы можете установить её в значение "" для устранения пустых абзацев (например, "abc\n\n\n\ndef" обрабатывается как два, а не три абзаца) или "\n\n" для допуска пустых абзацев.
Обратите внимание, что пустая строка не должна содержать пробелов. Таким образом "fred\n \nstuff\n\n" — один абзац, но "fred\n\nstuff\n\n" — два.
Как прочитать один символ из файла? С клавиатуры?
Для большинства файловых дескрипторов вы можете использовать встроенную функцию getc(), но она не будет (легко) работать с терминальным устройством. Для STDIN используйте модуль Term::ReadKey из CPAN или пример кода в "getc" в 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.
Как сделать dup() файлового дескриптора в Perl?
Если вы проверите "open" в 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" в perlop. Неудивительно, что у вас нет файла с именем "c:(табуляция)emp(перевод страницы)oo" или "c:(табуляция)emp(перевод страницы)oo.exe" на вашей устаревшей файловой системе DOS.
END_OF_DOCUMENT_MARKERИспользуйте одинарные кавычки для строк или (предпочтительно) обратные слэши. Поскольку все версии DOS и Windows начиная с чего-то вроде MS-DOS 2.0 и далее обрабатывают / и \ одинаково в пути, вы можете использовать тот, который не конфликтует с Perl — или с оболочкой POSIX, ANSI C и C++, awk, Tcl, Java или Python, чтобы упомянуть некоторые из них. Пути POSIX также более переносимы.
Почему glob("*.*") не получает все файлы?
Потому что даже на не-Unix-портах функция glob в Perl следует стандартной семантике Unix-глоббинга. Вам понадобится glob("*") для получения всех (не скрытых) файлов. Это делает glob() переносимым даже для устаревших систем. Ваш порт может также включать собственные функции глоббинга. Проверьте документацию для получения подробностей.
Почему Perl позволяет мне удалять файлы только для чтения? Почему -i перезаписывает защищенные файлы? Разве это не ошибка в Perl?
Это подробно и тщательно описано в статье file-dir-perms в коллекции «Гораздо больше, чем вы когда-либо хотели знать» в http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz .
Краткий вывод: узнайте, как работает ваша файловая система. Разрешения файла определяют, что может произойти с данными в этом файле. Разрешения каталога определяют, что может произойти со списком файлов в этом каталоге. Если вы удаляете файл, вы удаляете его имя из каталога (следовательно, операция зависит от разрешений каталога, а не файла). Если вы пытаетесь записать в файл, разрешения файла определяют, разрешено ли вам это.
Как выбрать случайную строку из файла?
Помимо загрузки файла в базу данных или предварительного индексирования строк в файле, вы можете сделать несколько вещей.
Вот алгоритм выборки резервуара из книги Camel:
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 без них:
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–2020 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.28.3/perlfaq5