Spec-Zone.ru › Perl 5.30

perlfaq5

СОДЕРЖАНИЕ

  • НАЗВАНИЕ
  • ВЕРСИЯ
  • ОПИСАНИЕ
    • Как выполнить сброс/отключение буферизации для выходного файлового дескриптора? Почему это нужно делать?
    • Как изменить, удалить или вставить строку в файл или добавить её в начало?
    • Как посчитать количество строк в файле?
    • Как удалить последние N строк из файла?
    • Как использовать опцию -i Perl внутри программы?
    • Как скопировать файл?
    • Как создать временное имя файла?
    • Как манипулировать файлами с фиксированной длиной записей?
    • Как сделать файловый дескриптор локальным для подпрограммы? Как передавать файловые дескрипторы между подпрограммами? Как создать массив файловых дескрипторов?
    • Как использовать файловый дескриптор косвенно?
    • Как открыть файловый дескриптор для строки?
    • Как настроить формат подвала, используемый с write()?
    • Как использовать write() для записи в строку?
    • Как выводить числа с добавлением запятых?
    • Как перевести тильды (~) в имени файла?
    • Почему при открытии файла для чтения и записи он очищается?
    • Почему иногда появляется ошибка "Слишком длинный список аргументов" при использовании <*>?
    • Как открыть файл, имя которого начинается с ">" или содержит хвостовые пробелы?
    • Как надёжно переименовать файл?
    • Как заблокировать файл?
    • Почему нельзя просто использовать open(FH, ">file.lock")?
    • Я всё ещё не понимаю блокировку. Мне нужно просто увеличить число в файле. Как это сделать?
    • Мне нужно всего лишь добавить небольшое количество текста в конец файла. Нужно ли всё ещё использовать блокировку?
    • Как произвольно обновить двоичный файл?
    • Как получить метку времени файла в Perl?
    • Как установить метку времени файла в Perl?
    • Как печатать в более чем один файл одновременно?
    • Как прочитать весь файл сразу?
    • Как прочитать файл по абзацам?
    • Как прочитать один символ из файла? С клавиатуры?
    • Как узнать, есть ли ожидающий символ в файловом дескрипторе?
    • Как реализовать tail -f в Perl?
    • Как создать дубликат файлового дескриптора в Perl?
    • Как закрыть файловый дескриптор по номеру?
    • Почему нельзя использовать "C:\temp\foo" в путях DOS? Почему `C:\temp\foo.exe` не работает?
    • Почему glob("*.*") не получает все файлы?
    • Почему Perl позволяет удалять только что читаемые файлы? Почему -i перезаписывает защищённые файлы? Это ошибка в Perl?
    • Как выбрать случайную строку из файла?
    • Почему при выводе массива строк появляются странные пробелы?
    • Как перебрать дерево каталогов?
    • Как удалить дерево каталогов?
    • Как скопировать всё содержимое каталога?
  • АВТОР И АВТОРСКИЕ ПРАВА

НАЗВАНИЕ

perlfaq5 - Файлы и форматы

ВЕРСИЯ

версия 5.20190126

ОПИСАНИЕ

Этот раздел посвящен вводу-выводу и проблемам с "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]);

Обратите внимание, что непостоянство косвенных дескрипторов файлов не связано с тем, являются ли они строками, шаблонами типов, объектами или чем-либо еще. Это синтаксис основных операторов. Использование объектов здесь вам совершенно не поможет.

Как можно открыть дескриптор файла для строки?

(предложено Питером Дж. Холцером, hjp-usenet2@hjp.at)

С Perl 5.8.0 можно создать дескриптор файла, ссылающийся на строку, вызвав open с ссылкой на эту строку вместо имени файла. Этот дескриптор файла можно затем использовать для чтения из строки или записи в неё:

open(my $fh, '>', \$string) or die "Could not open string for writing";
print $fh "foo\n";
print $fh "bar\n";    # $string now contains "foo\nbar\n"

open(my $fh, '<', \$string) or die "Could not open string for reading";
my $x = <$fh>;    # $x now contains "foo\n"

В более старых версиях Perl модуль IO::String обеспечивает аналогичную функциональность.

Как можно настроить формат подвала для использования с write()?

Нет встроенного способа сделать это, но perlform имеет несколько техник, чтобы сделать это возможным для смелых хакеров.

Как можно выполнить write() в строку?

(предложено brian d foy)

Если вы хотите write в строку, вам нужно <open> дескриптор файла для строки, что Perl умеет делать с Perl 5.6:

open FH, '>', \my $string;
write( FH );

Поскольку вы хотите быть хорошим программистом, вы, вероятно, захотите использовать лексический дескриптор файла, даже если форматы предназначены для работы с обычными именами файлов, поскольку имена форматов по умолчанию принимают имя дескриптора файла. Однако вы можете управлять этим с помощью некоторых специальных переменных Perl для каждого дескриптора файла: $^, который называет формат начала страницы, и $~, который отображает формат строки. Для изменения дескриптора файла по умолчанию необходимо установить эти переменные:

open my($fh), '>', \my $string;

{ # set per-filehandle variables
    my $old_fh = select( $fh );
    $~ = 'ANIMAL';
    $^ = 'ANIMAL_TOP';
    select( $old_fh );
}

format ANIMAL_TOP =
 ID  Type    Name
.

format ANIMAL =
@##   @<<<    @<<<<<<<<<<<<<<
$id,  $type,  $name
.

Хотя write может работать с лексическими или пакетными переменными, любые используемые переменные должны быть в области видимости формата. Скорее всего, вам нужно будет локально определить некоторые переменные пакета:

{
    local( $id, $type, $name ) = qw( 12 cat Buster );
    write( $fh );
}

print $string;

Также есть несколько хитростей, которые можно применить с formline и переменной-аккумулятором $^A, но вы потеряете большую часть ценности форматов, так как formline не будет обрабатывать страничные переходы и т. д. В итоге вы будете повторно реализовывать форматы при их использовании.

Как можно вывести числа с запятыми?

(предложено brian d foy и Benjamin Goldberg)

Можно использовать Number::Format для разделения разрядов в числе. Он обрабатывает информацию о языке для тех из вас, кто хочет вставлять точки вместо запятых (или что-либо ещё, что они захотят использовать).

Эта подпрограмма добавит запятые к вашему числу:

sub commify {
    local $_  = shift;
    1 while s/^([-+]?\d+)(\d{3})/$1,$2/;
    return $_;
}

Этот регулярное выражение от Benjamin Goldberg добавит запятые к числам:

s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;

Его проще понять с комментариями:

s/(
    ^[-+]?             # beginning of number.
    \d+?               # first digits before first comma
    (?=                # followed by, (but not included in the match) :
        (?>(?:\d{3})+) # some positive multiple of three digits.
        (?!\d)         # an *exact* multiple, not x * 3 + 1 or whatever.
    )
    |                  # or:
    \G\d{3}            # after the last group, get three digits
    (?=\d)             # but they have to have more digits after them.
)/$1,/xg;

Как можно перевести тильды (~) в имени файла?

Используйте оператор <> (glob()), описанный в perlfunc. В версиях Perl, более старых чем 5.6, требуется оболочка, понимающая тильды. Более поздние версии Perl имеют эту функцию встроенной. Модуль File::KGlob (доступный с CPAN) предоставляет более переносимую функциональность glob.

Внутри Perl вы можете использовать это напрямую:

$filename =~ s{
  ^ ~             # find a leading tilde
  (               # save this in $1
      [^/]        # a non-slash character
            *     # repeated 0 or more times (0 means me)
  )
}{
  $1
      ? (getpwnam($1))[7]
      : ( $ENV{HOME} || $ENV{LOGDIR} )
}ex;

Почему при открытии файла на чтение-запись он стирается?

Потому что вы используете что-то вроде этого, что обрезает файл сначала, а затем даёт доступ для чтения-записи:

open my $fh, '+>', '/path/name'; # WRONG (almost always)

Ошибочка. Вместо этого используйте это, которое не сработает, если файл не существует:

open my $fh, '+<', '/path/name'; # open for update

Использование ">" всегда перезаписывает или создаёт. Использование "<" никогда не делает ни того, ни другого. "+" это не меняет.

Вот примеры различных открытий файлов. Те, которые используют sysopen, предполагают, что вы загрузили константы из Fcntl:

use Fcntl;

Чтобы открыть файл для чтения:

open my $fh, '<', $path                               or die $!;
sysopen my $fh, $path, O_RDONLY                       or die $!;

Чтобы открыть файл для записи, создать новый файл, если необходимо, или обрезать старый файл:

open my $fh, '>', $path                               or die $!;
sysopen my $fh, $path, O_WRONLY|O_TRUNC|O_CREAT       or die $!;
sysopen my $fh, $path, O_WRONLY|O_TRUNC|O_CREAT, 0666 or die $!;

Чтобы открыть файл для записи, создать новый файл, файл не должен существовать:

sysopen my $fh, $path, O_WRONLY|O_EXCL|O_CREAT        or die $!;
sysopen my $fh, $path, O_WRONLY|O_EXCL|O_CREAT, 0666  or die $!;

Чтобы открыть файл для добавления, создать, если необходимо:

open my $fh, '>>', $path                              or die $!;
sysopen my $fh, $path, O_WRONLY|O_APPEND|O_CREAT      or die $!;
sysopen my $fh, $path, O_WRONLY|O_APPEND|O_CREAT, 0666 or die $!;

Чтобы открыть файл для добавления, файл должен существовать:

sysopen my $fh, $path, O_WRONLY|O_APPEND              or die $!;

Чтобы открыть файл для обновления, файл должен существовать:

open my $fh, '+<', $path                              or die $!;
sysopen my $fh, $path, O_RDWR                         or die $!;

Чтобы открыть файл для обновления, создать файл, если необходимо:

sysopen my $fh, $path, O_RDWR|O_CREAT                 or die $!;
sysopen my $fh, $path, O_RDWR|O_CREAT, 0666           or die $!;

Чтобы открыть файл для обновления, файл не должен существовать:

sysopen my $fh, $path, O_RDWR|O_EXCL|O_CREAT          or die $!;
sysopen my $fh, $path, O_RDWR|O_EXCL|O_CREAT, 0666    or die $!;

Чтобы открыть файл без блокировки, создавая, если необходимо:

sysopen my $fh, '/foo/somefile', O_WRONLY|O_NDELAY|O_CREAT
    or die "can't open /foo/somefile: $!":

Обратите внимание, что ни создание, ни удаление файлов не гарантировано как атомарная операция через NFS. То есть два процесса могут оба успешно создать или удалить один и тот же файл! Поэтому O_EXCL не так эксклюзивен, как хотелось бы.

См. также perlopentut.

Почему иногда возникает ошибка "Список аргументов слишком длинный" при использовании <*>?

Оператор <> выполняет операцию glob (см. выше). В версиях Perl до v5.6.0 внутренний оператор glob() запускает csh(1) для фактического расширения glob, но csh не может обработать более 127 элементов, и поэтому выдаёт сообщение об ошибке Argument list too long. У людей, установивших tcsh как csh, этой проблемы не будет, но их пользователи могут быть удивлены этим.

Чтобы обойти эту проблему, либо обновитесь до Perl v5.6.0 или более поздней версии, либо сделайте glob самостоятельно с readdir() и шаблонами, или используйте модуль, такой как File::Glob, который не использует оболочку для выполнения glob.

Как можно открыть файл с лидирующим ">" или конечными пробелами в имени?

(предложено Брайаном МакКолли)

Специальная двухаргументная форма функции Perl open() игнорирует конечные пробелы в именах файлов и выводит режим из определённых ведущих символов (или конечного "|"). В более старых версиях 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), если ни одна из двух предыдущих системных вызовов не существует. На некоторых системах она может даже использовать другой вид системной блокировки. Вот некоторые подводные камни с Perl flock():

  1. Вызывает ошибку fatal, если ни одна из трёх системных вызовов (или их близкий эквивалент) не существует.

  2. lockf(3) не предоставляет совместную блокировку и требует, чтобы дескриптор файла был открыт для записи (или добавления, или чтения/записи).

  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

Это классическая проблема гонки: вы выполняете два шага, чтобы сделать то, что нужно сделать в одном. Именно поэтому аппаратное обеспечение компьютеров предоставляет атомарную инструкцию 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 может быть очень быстрым и может не увеличивать объём используемой памяти.

Вы также можете найти более интересным использование стандартного модуля 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.

Как дублировать дескриптор файла в 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" на вашей файловой системе legacy DOS.

Используйте одинарные кавычки для строк или (лучше) обратные слэши. Поскольку все версии DOS и Windows начиная с MS-DOS 2.0 и выше обрабатывают / и \ одинаково в пути, вы можете использовать тот, который не конфликтует с Perl — или с POSIX оболочкой, ANSI C и C++, awk, Tcl, Java или Python, чтобы упомянуть лишь некоторые. POSIX пути также более портативны.

Почему glob("*.*") не получает все файлы?

Потому что даже на не-Unix платформах функция glob в Perl следует стандартной семантике Unix globbing. Вам потребуется glob("*") для получения всех (не скрытых) файлов. Это делает glob() портативным даже для устаревших систем. Ваш порт может также включать проприетарные функции globbing. Обратитесь к документации для получения подробностей.

Почему Perl позволяет мне удалять файлы только для чтения? Почему -i перезаписывает защищенные файлы? Это ошибка в Perl?

Это подробно и тщательно описано в статье file-dir-perms в коллекции "Far More Than You Ever Wanted To Know" в http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz.

Краткий вывод: узнайте, как работает ваша файловая система. Разрешения на файл определяют, что может произойти с данными в этом файле. Разрешения на директорию определяют, что может произойти со списком файлов в этой директории. Если вы удаляете файл, вы удаляете его имя из каталога (следовательно, операция зависит от разрешений каталога, а не файла). Если вы пытаетесь записать в файл, разрешения файла определяют, разрешено ли вам это.

Как выбрать случайную строку из файла?

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

Вот алгоритм выбора с заменой из книги Camel:

srand;
rand($.) < 1 && ($line = $_) while <>;

Это имеет значительное преимущество в пространстве перед чтением всего файла. Доказательство этого метода можно найти в «Искусстве программирования вычислительных машин», том 2, раздел 3.4.2, Дональдом Кнутом.

Вы можете использовать модуль File::Random, который предоставляет функцию для этого алгоритма:

use File::Random qw/random_line/;
my $line = random_line($filename);

Другой способ — использовать модуль Tie::File, который обрабатывает весь файл как массив. Просто обратитесь к случайному элементу массива.

Почему при печати массива строк появляются странные пробелы?

(предложено brian d foy)

Если вы видите пробелы между элементами массива при печати массива, вы, вероятно, интерполируете массив в двойных кавычках:

my @animals = qw(camel llama alpaca vicuna);
print "animals are: @animals\n";

Это двойные кавычки, а не print, которые это делают. Всякий раз, когда вы интерполируете массив в контексте двойных кавычек, Perl соединяет элементы пробелами (или тем, что находится в $", что по умолчанию является пробелом):

animals are: camel llama alpaca vicuna

Это отличается от печати массива без интерполяции:

my @animals = qw(camel llama alpaca vicuna);
print "animals are: ", @animals, "\n";

Теперь в выводе нет пробелов между элементами, потому что элементы @animals просто становятся частью списка для print:

animals are: camelllamaalpacavicuna

Вы можете заметить это, когда каждый из элементов @array заканчивается символом новой строки. Вы ожидаете, что каждый элемент будет напечатан на новой строке, но обратите внимание, что каждая строка после первой отступает:

this is a line
 this is another line
 this is the third line

Этот дополнительный пробел происходит из интерполяции массива. Если вы не хотите ничего между элементами массива, не используйте массив в двойных кавычках. Вы можете отправить его на печать без них:

print @lines;

Как пройти по дереву каталогов?

(предложено brian d foy)

Модуль File::Find, который поставляется с Perl, выполняет всю тяжелую работу по обходу структуры каталогов. Он поставляется с Perl. Вы просто вызываете подпрограмму find с обратной подпрограммой и каталогами, которые вы хотите обойти:

use File::Find;

find( \&wanted, @directories );

sub wanted {
    # full path in $File::Find::name
    # just filename in $_
    ... do whatever you want to do ...
}

Модуль File::Find::Closures, который можно загрузить с CPAN, предоставляет множество готовых подпрограмм, которые можно использовать с File::Find.

Модуль File::Finder, который можно загрузить с CPAN, поможет вам создать обратную подпрограмму, используя синтаксис, более близкий к синтаксису утилиты командной строки find:

use File::Find;
use File::Finder;

my $deep_dirs = File::Finder->depth->type('d')->ls->exec('rmdir','{}');

find( $deep_dirs->as_options, @places );

Модуль File::Find::Rule, который можно загрузить с CPAN, имеет аналогичный интерфейс, но также выполняет за вас обход:

use File::Find::Rule;

my @files = File::Find::Rule->file()
                         ->name( '*.pm' )
                         ->in( @INC );

Как удалить дерево каталогов?

(предложено brian d foy)

Если у вас есть пустой каталог, вы можете использовать встроенную в Perl команду rmdir. Если каталог не пустой (то есть содержит файлы или подкаталоги), вам нужно либо очистить его самостоятельно (много работы), либо использовать модуль, который поможет вам.

Модуль File::Path, который поставляется с Perl, имеет функцию remove_tree, которая может взять на себя всю тяжелую работу за вас:

use File::Path qw(remove_tree);

remove_tree( @directories );

Модуль File::Path также имеет устаревший интерфейс к более старой функции rmtree.

Как скопировать весь каталог?

(предложено Shlomi Fish)

Для выполнения эквивалента cp -R (то есть рекурсивного копирования всего дерева каталогов) в портативном Perl вам нужно либо написать что-то самим, либо найти хороший модуль с CPAN, такой как File::Copy::Recursive.

АВТОР И АВТОРСКИЕ ПРАВА

Авторские права (c) 1997-2010 Tom Christiansen, Nathan Torkington и других авторов, как указано. Все права защищены.

Эта документация бесплатна; вы можете распространять и/или изменять её на тех же условиях, что и Perl сам по себе.

Независимо от его распространения, все примеры кода здесь являются общественным достоянием. Вам разрешено и рекомендуется использовать этот код и любые производные от него в ваших собственных программах для развлечения или с выгодой, как вам заблагорассудится. Простой комментарий в коде, дающий кредит 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.30.3/perlfaq5

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API