perlfaq5
СОДЕРЖАНИЕ
- ИМЯ
- ВЕРСИЯ
- ОПИСАНИЕ
- Как выполнить сброс/отключение буферизации для выходного файлового дескриптора? Зачем это нужно?
- Как изменить, удалить или вставить строку в файл или добавить ее в начало?
- Как подсчитать количество строк в файле?
- Как удалить последние N строк из файла?
- Как использовать опцию -i Perl внутри программы?
- Как скопировать файл?
- Как создать временное имя файла?
- Как обработать файлы с фиксированной длиной записи?
- Как сделать файловый дескриптор локальным для подпрограммы? Как передавать файловые дескрипторы между подпрограммами? Как создать массив файловых дескрипторов?
- Как использовать файловый дескриптор косвенно?
- Как открыть файловый дескриптор для строки?
- Как настроить формат подвала, используемый с write()?
- Как выполнить write() в строку?
- Как вывести числа с добавлением запятых?
- Как перевести тильды (~) в имени файла?
- Почему при открытии файла для чтения-записи он очищается?
- Почему иногда появляется сообщение "Список аргументов слишком длинный" при использовании <*>?
- Как открыть файл с ведущими ">" или хвостовыми пробелами в имени?
- Как надежно переименовать файл?
- Как заблокировать файл?
- Почему нельзя просто использовать open(FH, ">file.lock")?
- У меня по-прежнему не получается блокировка. Я хочу только увеличить число в файле. Как это сделать?
- Я хочу только добавить небольшой текст в конец файла. Нужно ли использовать блокировку?
- Как выполнить произвольное обновление бинарного файла?
- Как получить временную метку файла в Perl?
- Как установить временную метку файла в Perl?
- Как печатать в более чем один файл одновременно?
- Как прочитать весь файл сразу?
- Как прочитать файл по абзацам?
- Как прочитать один символ из файла? Из клавиатуры?
- Как определить, есть ли ожидающий символ на файловом дескрипторе?
- Как выполнить tail -f в Perl?
- Как выполнить dup() для файлового дескриптора в Perl?
- Как закрыть файловый дескриптор по номеру?
- Почему нельзя использовать "C:\temp\foo" в путях DOS? Почему `C:\temp\foo.exe` не работает?
- Почему glob("*.*") не получает все файлы?
- Почему Perl позволяет удалять только для чтения файлы? Почему -i перезаписывает защищенные файлы? Это ошибка в Perl?
- Как выбрать случайную строку из файла?
- Почему при выводе массива строк появляются странные пробелы?
- Как пройтись по дереву каталогов?
- Как удалить дерево каталогов?
- Как скопировать весь каталог?
- АВТОР И АВТОРСКИЕ ПРАВА
ИМЯ
perlfaq5 - Файлы и форматы
ВЕРСИЯ
версия 5.20200523
ОПИСАНИЕ
Этот раздел посвящен вводу-выводу и проблемам с "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]); Обратите внимание, что непостоянство косвенных дескрипторов файлов не связано с тем, являются ли они строками, шаблонами, объектами или чем-то еще. Это синтаксис основных операторов. Использование объектов здесь нисколько не поможет.
Как открыть дескриптор файла для строки?
(предоставлено Питером Дж. Хольцером, 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 в строку, вам нужно просто
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 не будет обрабатывать разворот и так далее. В итоге вы переопределяете форматы при их использовании.
Как вывести числа с добавленными запятыми?
(предоставлено Брайаном Д. Фоем и Бенджамином Голдбергом)
Вы можете использовать Number::Format для разделения разрядов в числе. Он обрабатывает информацию о локали для тех из вас, кто хочет вставить точки вместо них (или что-то еще, что они хотят использовать).
Эта подпрограмма добавит запятые к вашему числу:
sub commify {
local $_ = shift;
1 while s/^([-+]?\d+)(\d{3})/$1,$2/;
return $_;
} Этот регулярное выражение от Бенджамина Голдберга добавит запятые к числам:
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) предоставляет более переносимую функциональность глоббинга.
Внутри 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 v5.6.0 или более поздней версии, сделайте глоббинг самостоятельно с помощью readdir() и шаблонов, или используйте модуль, например, File::Glob, который не использует оболочку для глоббинга.
Как открыть файл, имя которого начинается с ">" или содержит пробелы в конце?
(предоставлено Брайаном Макколли)
Специальная форма функции 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(), которое сохраняет метаданные, такие как разрешения, метки времени, информацию об индексном узле и т.д.
Как заблокировать файл?
Встроенная функция Perl flock() (см. perlfunc для подробностей) вызовет flock(2), если она существует, fcntl(2), если нет (в Perl версии 5.004 и выше), и lockf(3), если ни одна из двух предыдущих системных вызовов не существует. В некоторых системах она может даже использовать другой метод блокировки. Вот некоторые нюансы с flock() Perl:
-
Вызывает ошибку fatal, если ни один из трёх системных вызовов (или их близкий эквивалент) не существует.
-
lockf(3) не предоставляет совместную блокировку и требует, чтобы дескриптор файла был открыт для записи (или добавления, или чтения/записи).
-
В некоторых версиях flock() нельзя блокировать файлы через сеть (например, на файловых системах NFS), поэтому при компиляции Perl нужно принудительно использовать fcntl(2). Но и это сомнительно. См. запись flock в perlfunc и файл INSTALL в дистрибутиве исходного кода для получения информации о компиляции Perl для этого.
Два потенциально неочевидных, но традиционных аспекта семантики flock — это то, что она ожидает неопределённо долго, пока блокировка не будет предоставлена, и что её блокировки являются только рекомендательными. Такие условные блокировки более гибкие, но обеспечивают меньшие гарантии. Это означает, что файлы, заблокированные с помощью flock(), могут быть изменены программами, которые не используют flock(). Автомобили, которые останавливаются на красный свет, хорошо взаимодействуют друг с другом, но не с автомобилями, которые не останавливаются на красный свет. См. страницу руководства perlport, конкретную документацию вашего порта или локальные страницы руководства вашей системы для получения подробностей. Лучше всего предположить традиционное поведение, если вы пишете переносимые программы. (Если вы этого не делаете, вы, как всегда, можете свободно писать для особенностей вашей собственной системы (иногда называемых «функциями»). Слепое следование требованиям переносимости не должно мешать вам выполнять свою работу.)
Для получения дополнительной информации о блокировке файлов, см. также "Блокировка файлов" в perlopentut, если она у вас есть (новая функция для версии 5.6).
Почему я не могу просто открыть(FH, ">file.lock")?
Частая ошибка НЕ ДОЛЖНА ИСПОЛЬЗОВАТЬСЯ:
sleep(3) while -e 'file.lock'; # PLEASE DO NOT USE
open my $lock, '>', 'file.lock'; # THIS BROKEN CODE Это классическая проблема гонки: вы выполняете два шага, чтобы сделать то, что должно быть выполнено одним. Вот почему аппаратное обеспечение компьютера предоставляет атомарную инструкцию test-and-set. Теоретически это «должно» работать:
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; Как обычно, проверка ошибок оставлена читателю в качестве упражнения.
Документация по 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-ing может быть очень быстрым и, возможно, не увеличит ваш объём занимаемой памяти.
Возможно, вам будет интереснее использовать стандартный модуль 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);
} Если вы хотите узнать, сколько символов ожидает обработки, можно также посмотреть на системный вызов FIONREAD. Инструмент h2ph, который поставляется с Perl, пытается преобразовать файлы заголовков C в Perl-код, что может быть required. 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" в 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:(tab)emp(formfeed)oo" или "c:(tab)emp(formfeed)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 @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.
АВТОР И АВТОРСКИЕ ПРАВА
Copyright (c) 1997-2010 Tom Christiansen, Nathan Torkington и другие авторы, как указано. Все права защищены.
Данная документация бесплатна; вы можете распространять и/или изменять ее на тех же условиях, что и Perl сам по себе.
Независимо от его распространения, все примеры кода здесь находятся в общественном достоянии. Вам разрешено и рекомендуется использовать этот код и любые производные от него в своих собственных программах для развлечения или с выгодой, как вам заблагорассудится. Простая заметка в коде, приписывающая авторство FAQ, была бы вежливой, но не обязательной.
© 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.32.0/perlfaq5