Spec-Zone.ru › Perl 5.36

perlfaq5

СОДЕРЖАНИЕ

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

НАЗВАНИЕ

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

ВЕРСИЯ

версия 5.20210520

ОПИСАНИЕ

Этот раздел посвящён вводу-выводу и проблемам с файловыми дескрипторами: файловыми дескрипторами, сбросом буфера, форматами и подвалами.

Как выполнить сброс/отключение буферизации для выходного файлового дескриптора? Почему это необходимо?

(внёс вклад 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 для добавления слоёв буферизации к файловому дескриптору, которые не буферизируются:

binmode( STDOUT, ":unix" );

while( 1 ) {
    sleep 1;
    print ".";
    print "\n" unless ++$count % 50;
}

Для получения дополнительной информации о слоях вывода см. записи для binmode и open в perlfunc, а также документацию модуля PerlIO.

Если вы используете IO::Handle или один из его подклассов, можно вызвать метод autoflush для изменения настроек файлового дескриптора:

use IO::Handle;
open my( $io_fh ), ">", "output.txt";
$io_fh->autoflush(1);

Объекты IO::Handle также имеют метод flush. Можно сбрасывать буфер в любое время без автоматической буферизации.

$io_fh->flush;

Как изменить, удалить или вставить строку в файл, или добавить её в начало?

(внёс вклад brian d foy)

Основная идея вставки, изменения или удаления строки из текстового файла заключается в чтении и выводе файла до нужной точки изменения, внесении изменений, а затем чтении и выводе остальной части файла. Perl не предоставляет произвольный доступ к строкам (особенно, поскольку разделитель записей, $/, является изменяемым), хотя такие модули, как Tie::File, могут это имитировать.

Программа Perl для выполнения этих задач имеет базовый вид открытия файла, вывода его строк, а затем закрытия файла:

open my $in,  '<',  $file      or die "Can't read old file: $!";
open my $out, '>', "$file.new" or die "Can't write new file: $!";

while( <$in> ) {
        print $out $_;
}

close $out;

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

Чтобы добавить строки в начало, выведите эти строки перед входом в цикл, который выводит существующие строки.

open my $in,  '<',  $file      or die "Can't read old file: $!";
open my $out, '>', "$file.new" or die "Can't write new file: $!";

print $out "# Add this line to the top\n"; # <--- HERE'S THE MAGIC

while( <$in> ) {
        print $out $_;
}

close $out;

Чтобы изменить существующие строки, вставьте код для изменения строк внутри цикла while. В данном случае код находит все строчные версии "perl" и переводит их в верхний регистр. Это происходит для каждой строки, поэтому убедитесь, что это нужно для каждой строки!

open my $in,  '<',  $file      or die "Can't read old file: $!";
open my $out, '>', "$file.new" or die "Can't write new file: $!";

print $out "# Add this line to the top\n";

while( <$in> ) {
    s/\b(perl)\b/Perl/g;
    print $out $_;
}

close $out;

Чтобы изменить только определённую строку, номер строки ввода, $., полезен. Сначала прочитайте и выведите строки до той, которую нужно изменить. Затем прочитайте одиночную строку, которую нужно изменить, измените её и выведите. После этого прочитайте оставшиеся строки и выведите их:

while( <$in> ) { # print the lines before the change
    print $out $_;
    last if $. == 4; # line number before change
}

my $line = <$in>;
$line =~ s/\b(perl)\b/Perl/g;
print $out $line;

while( <$in> ) { # print the rest of the lines
    print $out $_;
}

Чтобы пропустить строки, используйте управляющие элементы цикла. next в данном примере пропускает комментарии, а last останавливает всю обработку при обнаружении __END__ или __DATA__.

while( <$in> ) {
    next if /^\s+#/;             # skip comment lines
    last if /^__(END|DATA)__$/;  # stop at end of code marker
    print $out $_;
}

Для удаления определённой строки сделайте то же самое, используя next для пропуска строк, которые не должны отображаться в выводе. В этом примере пропускается каждая пятая строка:

while( <$in> ) {
    next unless $. % 5;
    print $out $_;
}

Если по какой-то причине вы действительно хотите увидеть весь файл сразу, а не обрабатывать строку за строкой, вы можете его проглотить (если вы можете поместить его целиком в память!):

open my $in,  '<',  $file      or die "Can't read old file: $!"
open my $out, '>', "$file.new" or die "Can't write new file: $!";

my $content = do { local $/; <$in> }; # slurp!

    # do your magic here

print $out $content;

Модули, такие как Path::Tiny и Tie::File, тоже могут помочь. Однако, старайтесь избегать чтения всего файла сразу. Perl не вернёт эту память операционной системе до завершения процесса.

Вы также можете использовать Perl-однострочники для изменения файла на месте. Следующий пример меняет все вхождения «Fred» на «Barney» в файле inFile.txt, перезаписывая файл новым содержимым. С переключателем -p, Perl оборачивает цикл while вокруг указанного вами кода с -e, а -i включает редактирование на месте. Текущая строка находится в $_. С -p, Perl автоматически выводит значение $_ в конце цикла. Более подробную информацию см. в perlrun.

perl -pi -e 's/Fred/Barney/' inFile.txt

Чтобы сделать резервную копию inFile.txt, добавьте расширение файла с помощью -i:

perl -pi.bak -e 's/Fred/Barney/' inFile.txt

Чтобы изменить только пятую строку, вы можете добавить проверку $., номера строки ввода, а затем выполнить операцию только в случае успеха проверки:

perl -pi -e 's/Fred/Barney/ if $. == 5' inFile.txt

Чтобы добавить строки перед определённой строкой, добавьте строку (или строки!) перед тем, как Perl выведет $_:

perl -pi -e 'print "Put before third line\n" if $. == 3' inFile.txt

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

perl -pi -e 'print "Put before first line\n" if $. == 1' inFile.txt

Чтобы вставить строку после уже имеющейся в файле, используйте переключатель -n. Это аналогично -p, но не выводит $_ в конце цикла, поэтому вам придётся сделать это самостоятельно. В данном случае, выведите сначала $_, а затем строку, которую хотите добавить.

perl -ni -e 'print; print "Put after fifth line\n" if $. == 5' inFile.txt

Чтобы удалить строки, выводите только нужные.

perl -ni -e 'print if /d/' inFile.txt

Как посчитать количество строк в файле?

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

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

my $count = 0;
while( <$fh> ) { $count++; }

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

1 while( <$fh> );
my $count = $.;

Если вы хотите использовать $., вы можете свести это к простому однострочнику, например, такому:

% perl -lne '} print $.; {'    file

% perl -lne 'END { print $. }' file

Однако, они могут быть довольно неэффективными. Если они недостаточно быстры, вы можете читать куски данных и считать количество символов новой строки:

my $lines = 0;
open my($fh), '<:raw', $filename or die "Can't open $filename: $!";
while( sysread $fh, $buffer, 4096 ) {
    $lines += ( $buffer =~ tr/\n// );
}
close $fh;

Однако, это не работает, если разделитель строк не является символом новой строки. Вы можете изменить tr/// на s///, чтобы подсчитать количество раз, когда появляется разделитель ввода, $/,:

my $lines = 0;
open my($fh), '<:raw', $filename or die "Can't open $filename: $!";
while( sysread $fh, $buffer, 4096 ) {
    $lines += ( $buffer =~ s|$/||g; );
}
close $fh;

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

#!perl -T

$ENV{PATH} = undef;

my $lines;
if( $filename =~ /^([0-9a-z_.]+)\z/ ) {
    $lines = `/usr/bin/wc -l $1`
    chomp $lines;
}

Как удалить последние N строк из файла?

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

Самый простой концептуальный подход — посчитать строки в файле, затем начать с начала и вывести в новый файл количество строк (минус последние N).

Чаще всего, вопрос заключается в том, как удалить последние N строк, сделав не более одного прохода по файлу, или как сделать это без большого копирования. Простая концепция — сложная реальность, когда в файле могут быть миллионы строк.

Один из способов — использовать File::ReadBackwards, который начинает с конца файла. Этот модуль предоставляет объект, который оборачивает реальный дескриптор файла, чтобы легко перемещаться по файлу. Дойдя до нужной точки, вы можете получить фактический дескриптор файла и работать с ним как обычно. В данном случае вы получаете позицию файла в конце последней строки, которую нужно сохранить, и обрезаете файл до этой точки:

use File::ReadBackwards;

my $filename = 'test.txt';
my $Lines_to_truncate = 2;

my $bw = File::ReadBackwards->new( $filename )
    or die "Could not read backwards in [$filename]: $!";

my $lines_from_end = 0;
until( $bw->eof or $lines_from_end == $Lines_to_truncate ) {
    print "Got: ", $bw->readline;
    $lines_from_end++;
}

truncate( $filename, $bw->tell );

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

Вы также можете использовать модуль Tie::File, который позволяет обращаться к строкам через связанный массив. Вы можете использовать обычные операции с массивами для изменения файла, включая установку последнего индекса и использование splice.

Как использовать опцию -i Perl внутри программы?

-i устанавливает значение переменной Perl $^I, которая, в свою очередь, влияет на поведение <>; см. perlrun для получения дополнительной информации. Изменяя соответствующие переменные напрямую, вы можете получить такое же поведение внутри большей программы. Например:

# ...
{
    local($^I, @ARGV) = ('.orig', glob("*.c"));
    while (<>) {
        if ($. == 1) {
            print "This line should appear at the top of each file\n";
        }
        s/\b(p)earl\b/${1}erl/i;        # Correct typos, preserving case
        print;
        close ARGV if eof;              # Reset $.
    }
}
# $^I and @ARGV return to their old values here

Этот блок изменяет все файлы .c в текущей директории, оставляя резервную копию исходных данных каждого файла в новом файле с расширением .c.orig.

Как скопировать файл?

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

Используйте модуль File::Copy. Он входит в состав Perl и может выполнить истинное копирование между файловыми системами, а также выполняет свои действия портативно.

use File::Copy;

copy( $original, $new_copy ) or die "Copy failed: $!";

Если вы не можете использовать File::Copy, вам придётся сделать это самостоятельно: открыть исходный файл, открыть целевой файл, а затем выводить в целевой файл, как вы читаете исходный. Также необходимо скопировать разрешения, владельца и группу в новый файл.

Как создать временное имя файла?

Если вам не нужно знать имя файла, вы можете использовать open() с undef вместо имени файла. В Perl 5.8 или более поздних версиях функция open() создаёт анонимный временный файл:

open my $tmp, '+>', undef or die $!;

В противном случае, вы можете использовать модуль File::Temp.

use File::Temp qw/ tempfile tempdir /;

my $dir = tempdir( CLEANUP => 1 );
($fh, $filename) = tempfile( DIR => $dir );

# or if you don't need to know the filename

my $fh = tempfile( DIR => $dir );

Модуль File::Temp является стандартным модулем с Perl 5.6.1. Если у вас установлена не очень современная версия Perl, используйте метод new_tmpfile класса из модуля IO::File, чтобы получить дескриптор файла, открытый для чтения и записи. Используйте его, если вам не нужно знать имя файла:

use IO::File;
my $fh = IO::File->new_tmpfile()
    or die "Unable to make new temporary file: $!";

Если вы хотите создать временный файл вручную, используйте идентификатор процесса и/или текущее значение времени. Если вам нужно много временных файлов в одном процессе, используйте счётчик:

BEGIN {
    use Fcntl;
    use File::Spec;
    my $temp_dir  = File::Spec->tmpdir();
    my $file_base = sprintf "%d-%d-0000", $$, time;
    my $base_name = File::Spec->catfile($temp_dir, $file_base);

    sub temp_file {
        my $fh;
        my $count = 0;
        until( defined(fileno($fh)) || $count++ > 100 ) {
            $base_name =~ s/-(\d+)$/"-" . (1 + $1)/e;
            # O_EXCL is required for security reasons.
            sysopen $fh, $base_name, O_WRONLY|O_EXCL|O_CREAT;
        }

        if( defined fileno($fh) ) {
            return ($fh, $base_name);
        }
        else {
            return ();
        }
    }
}

Как манипулировать файлами с фиксированной длиной записи?

Самый эффективный способ — использовать pack() и unpack(). Это быстрее, чем использование substr() при работе с множеством строк. Это медленнее для небольшого числа строк.

Вот пример кода для разбиения и повторного объединения строк ввода с фиксированным форматом, в данном случае из вывода обычной команды ps в стиле Berkeley:

# sample input line:
#   15158 p5  T      0:00 perl /home/tchrist/scripts/now-what
my $PS_T = 'A6 A4 A7 A5 A*';
open my $ps, '-|', 'ps';
print scalar <$ps>;
my @fields = qw( pid tt stat time command );
while (<$ps>) {
    my %process;
    @process{@fields} = unpack($PS_T, $_);
    for my $field ( @fields ) {
        print "$field: <$process{$field}>\n";
    }
    print 'line=', pack($PS_T, @process{@fields} ), "\n";
}

Мы использовали срез хеша, чтобы легко обработать поля каждой строки. Хранение ключей в массиве позволяет легко работать с ними как группой или перебирать их с помощью for. Это также предотвращает загрязнение программы глобальными переменными и использование символических ссылок.

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

Начиная с perl5.6, open() автоматически создаёт ссылки на дескрипторы файлов и каталогов, если вы передаёте неинициализированную скалярную переменную. Затем вы можете передавать эти ссылки, как и любые другие скаляры, и использовать их вместо именованных дескрипторов.

open my    $fh, $file_name;

open local $fh, $file_name;

print $fh "Hello World!\n";

process_file( $fh );

По желанию, вы можете хранить эти дескрипторы файлов в массиве или хеше. Если вы обращаетесь к ним напрямую, они не являются простыми скалярами, и вам нужно помочь print, поместив ссылку на дескриптор файла в фигурные скобки. Perl может определить это только в случае ссылки на дескриптор файла в виде простого скаляра.

my @fhs = ( $fh1, $fh2, $fh3 );

for( $i = 0; $i <= $#fhs; $i++ ) {
    print {$fhs[$i]} "just another Perl answer, \n";
}

До perl5.6 вам приходилось иметь дело с различными идиомами typeglob, которые вы можете увидеть в более старом коде.

open FILE, "> $filename";
process_typeglob(   *FILE );
process_reference( \*FILE );

sub process_typeglob  { local *FH = shift; print FH  "Typeglob!" }
sub process_reference { local $fh = shift; print $fh "Reference!" }

Если вы хотите создать множество анонимных дескрипторов, обратите внимание на модули Symbol или IO::Handle.

Как использовать дескриптор файла косвенно?

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

$fh =   SOME_FH;       # bareword is strict-subs hostile
$fh =  "SOME_FH";      # strict-refs hostile; same package only
$fh =  *SOME_FH;       # typeglob
$fh = \*SOME_FH;       # ref to typeglob (bless-able)
$fh =  *SOME_FH{IO};   # blessed IO::Handle from *SOME_FH typeglob

Или вы можете использовать метод new из одного из модулей IO::* для создания анонимного дескриптора файла и хранения его в скалярной переменной.

use IO::Handle;                     # 5.004 or higher
my $fh = IO::Handle->new();

Затем используйте любой из них так же, как и обычный дескриптор файла. В любом месте, где Perl ожидает дескриптор файла, вместо него можно использовать косвенный дескриптор файла. Косвенный дескриптор файла — это просто скалярная переменная, которая содержит дескриптор файла. Функции, такие как print, open, seek, или оператор <FH> «алмаз», принимают либо именованный дескриптор файла, либо скалярную переменную, содержащую его:

($ifh, $ofh, $efh) = (*STDIN, *STDOUT, *STDERR);
print $ofh "Type it: ";
my $got = <$ifh>
print $efh "What was that: $got";

Если вы передаёте дескриптор файла в функцию, вы можете написать функцию двумя способами:

sub accept_fh {
    my $fh = shift;
    print $fh "Sending to indirect filehandle\n";
}

Или можно локализовать typeglob и использовать дескриптор файла напрямую:

sub accept_fh {
    local *FH = shift;
    print  FH "Sending to localized filehandle\n";
}

Оба стиля работают как с объектами, так и с typeglob реальных дескрипторов файлов. (Они могут работать и со строками в некоторых случаях, но это рискованно.)

accept_fh(*STDOUT);
accept_fh($handle);

В приведённых выше примерах мы присваивали дескриптор файла скалярной переменной перед его использованием. Это связано с тем, что только простые скалярные переменные, а не выражения или индексы хешей или массивов, могут использоваться с встроенными функциями, такими как print, printf, или оператором «алмаз». Использование чего-либо, кроме простой скалярной переменной, в качестве дескриптора файла является недопустимым и даже не будет компилироваться:

my @fd = (*STDIN, *STDOUT, *STDERR);
print $fd[1] "Type it: ";                           # WRONG
my $got = <$fd[0]>                                  # WRONG
print $fd[2] "What was that: $got";                 # WRONG

С print и printf вы обходите эту проблему, используя блок и выражение там, где вы бы поместили дескриптор файла:

print  { $fd[1] } "funny stuff\n";
printf { $fd[1] } "Pity the poor %x.\n", 3_735_928_559;
# Pity the poor deadbeef.

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

my $ok = -x "/bin/cat";
print { $ok ? $fd[1] : $fd[2] } "cat stat $ok\n";
print { $fd[ 1+ ($ok || 0) ]  } "cat stat $ok\n";

Этот подход к обработке print и printf как вызовов методов объекта не работает для оператора «алмаз». Это связано с тем, что это настоящий оператор, а не просто функция с аргументом без запятой. Предполагая, что вы храните typeglob в своей структуре, как мы сделали выше, вы можете использовать встроенную функцию readline, чтобы прочитать запись, точно так же, как <> делает. Учитывая инициализацию @fd, показанную выше, это сработает, но только потому, что readline() требует typeglob. Это не работает с объектами или строками, что может быть ошибкой, которую мы ещё не исправили.

$got = readline($fd[0]);

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

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

(предложено Peter J. Holzer, hjp-usenet2@hjp.at)

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

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

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

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

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

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

Как записать в строку с помощью write()?

(представлено brian d foy)

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

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

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

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

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

format ANIMAL_TOP =
 ID  Type    Name
.

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

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

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

print $string;

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

Как добавить запятые к моим числам при выводе?

(представлено brian d foy и Benjamin Goldberg)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

use Fcntl;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

См. также perlopentut.

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

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

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

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

(представлено Brian McCauley)

Специальная двухаргументная форма функции open() Perl игнорирует хвостовые пробелы в именах файлов и определяет режим по определенным ведущим символам (или заключительной "|"). В более старых версиях Perl это была единственная версия open() и поэтому она широко распространена в старом коде и книгах.

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

open my $fh, "<", "  file  ";  # filename is "   file   "
open my $fh, ">", ">file";     # filename is ">file"

Как надёжно переименовать файл?

Если ваша операционная система поддерживает утилиту mv(1) или её функциональный эквивалент, это работает:

rename($old, $new) or system("mv", $old, $new);

Для большей переносимости можно использовать модуль File::Copy вместо этого. Просто скопируйте файл в новое имя (проверяя возвращаемые значения), а затем удалите старый. Это не совсем то же самое, что и rename(), которая сохраняет метаинформацию, например, разрешения, метки времени, информацию об индексном узле и т. д.

Как заблокировать файл?

Встроенная функция Perl flock() (см. perlfunc для деталей) вызовет flock(2), если она существует, fcntl(2), если нет (в Perl версии 5.004 и более поздних), и lockf(3), если ни одна из предыдущих системных вызовов не существует. В некоторых системах она может использовать другой вид системной блокировки. Вот некоторые тонкости с flock() в Perl:

  1. Вызывает фатальную ошибку, если ни одна из трёх системных вызовов (или их аналоги) не существует.

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

  3. В некоторых версиях flock() нельзя блокировать файлы через сеть (например, на файловых системах NFS), поэтому вам нужно принудительно использовать fcntl(2) при компиляции Perl. Но даже это сомнительно. См. запись flock в perlfunc и файл INSTALL в исходном дистрибутиве для получения информации о компиляции Perl для этого.

    Два потенциально неявных, но традиционных аспекта семантики flock() заключаются в том, что она ожидает неопределённо долго, пока блокировка не будет получена, и что её блокировки только консультативные. Такие дискреционные блокировки более гибкие, но гарантируют меньше. Это означает, что файлы, заблокированные с помощью flock(), могут быть изменены программами, которые также не используют flock(). Машины, которые останавливаются на красный свет, хорошо взаимодействуют друг с другом, но не с машинами, которые не останавливаются на красный свет. См. страницу man perlport, документацию по вашей платформе или локальные страницы man вашего конкретного системного порта для деталей. Лучше всего предполагать традиционное поведение при написании переносимых программ. (Если вы этого не делаете, то вы, как всегда, совершенно свободны писать для особенностей своей системы (иногда называемых "функциями"). Рабская приверженность проблемам переносимости не должна мешать вам выполнять свою работу.)

    Для получения дополнительной информации о блокировании файлов, также см. "Блокировка файлов" в perlopentut, если у вас есть (новая функция в 5.6).

Почему я не могу просто открыть(FH, ">file.lock")?

Общая ошибка, которую НЕ НУЖНО использовать, выглядит так:

sleep(3) while -e 'file.lock';    # PLEASE DO NOT USE
open my $lock, '>', 'file.lock'; # THIS BROKEN CODE

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

sysopen my $fh, "file.lock", O_WRONLY|O_EXCL|O_CREAT
    or die "can't open  file.lock: $!";

кроме того, что создание (и удаление) файлов не является атомарной операцией через NFS, поэтому это не будет работать (по крайней мере, не всегда) в сети. Были предложены различные схемы, использующие link(), но они обычно включают ожидание, что также нежелательно.

Я всё ещё не понимаю блокировку. Я просто хочу увеличить число в файле. Как это сделать?

Разве вам никто не говорил, что счётчики посещений веб-страниц бесполезны? Они не учитывают количество посещений, это пустая трата времени, и они служат только для удовлетворения чьего-то тщеславия. Лучше выбрать случайное число; они более реалистичны.

В любом случае, вот что вы можете сделать, если не можете себя контролировать.

use Fcntl qw(:DEFAULT :flock);
sysopen my $fh, "numfile", O_RDWR|O_CREAT or die "can't open numfile: $!";
flock $fh, LOCK_EX                        or die "can't flock numfile: $!";
my $num = <$fh> || 0;
seek $fh, 0, 0                            or die "can't rewind numfile: $!";
truncate $fh, 0                           or die "can't truncate numfile: $!";
(print $fh $num+1, "\n")                  or die "can't write numfile: $!";
close $fh                                 or die "can't close numfile: $!";

Вот гораздо лучший счётчик посещений веб-страниц:

$hits = int( (time() - 850_000_000) / rand(1_000) );

Если счёт не впечатляет ваших друзей, то код может. :-)

Я хочу просто добавить небольшое количество текста в конец файла. Нужно ли мне всё ещё использовать блокировку?

Если вы работаете на системе, которая правильно реализует flock, и используете пример кода добавления из "perldoc -f flock", всё будет в порядке, даже если операционная система, на которой вы работаете, не реализует режим добавления корректно (если такая система существует). Таким образом, если вы хотите ограничиться системами, которые правильно реализуют flock (и это не такое уж и ограничение), то это то, что вам следует сделать.

Если вы знаете, что будете использовать только систему, которая правильно реализует добавление (например, локальную файловую систему на современной Unix), и вы храните файл в режиме с буферизацией по блокам, и записываете меньше одного буфера между каждым ручным сбросом буфера, то каждый фрагмент буфера почти гарантированно будет записан в конец файла одним куском без смешивания с выходными данными других процессов. Вы также можете использовать функцию syswrite, которая является просто оболочкой вокруг системного вызова write(2).

Всё ещё существует небольшая теоретическая вероятность того, что сигнал прервёт системную операцию write() до завершения. Также возможно, что некоторые реализации STDIO могут вызывать несколько системных вызовов write() даже если буфер был пустым в начале. Может быть, на некоторых системах эта вероятность сводится к нулю, и это не является проблемой при использовании :perlio вместо вашей STDIO.

Как произвольно обновить двоичный файл?

Если вы просто пытаетесь исправить двоичный файл, во многих случаях достаточно простого:

perl -i -pe 's{window manager}{window mangler}g' /usr/bin/emacs

Однако, если у вас есть записи фиксированного размера, то вы можете сделать что-то более похожее на это:

my $RECSIZE = 220; # size of record, in bytes
my $recno   = 37;  # which record to update
open my $fh, '+<', 'somewhere' or die "can't update somewhere: $!";
seek $fh, $recno * $RECSIZE, 0;
read $fh, $record, $RECSIZE == $RECSIZE or die "can't read record $recno: $!";
# munge the record
seek $fh, -$RECSIZE, 1;
print $fh $record;
close $fh;

Блокировка и проверка ошибок оставлены в качестве упражнения для читателя. Не забывайте об этом, иначе вам будет очень жаль.

Как получить метку времени файла в Perl?

Если вы хотите получить время последнего чтения, записи или изменения метаданных (владелец и т. д.) файла, используйте операции проверки файлов -A, -M или -C, как документировано в perlfunc. Они возвращают возраст файла (измеренный относительно времени запуска вашей программы) в днях в виде числа с плавающей запятой. Некоторые платформы могут не иметь всех этих временных меток. См. perlport для получения подробностей. Для получения «сырого» времени в секундах с начала эпохи вы бы вызвали функцию stat, а затем использовали localtime(), gmtime(), или POSIX::strftime(), чтобы преобразовать это в удобочитаемый вид.

Вот пример:

my $write_secs = (stat($file))[9];
printf "file %s updated at %s\n", $file,
    scalar localtime($write_secs);

Если вы предпочитаете что-то более удобочитаемое, используйте модуль File::stat (входит в стандартное распределение версии 5.004 и выше):

# error checking left as an exercise for reader.
use File::stat;
use Time::localtime;
my $date_string = ctime(stat($file)->mtime);
print "file $file updated at $date_string\n";

Подход POSIX::strftime() имеет преимущество, будучи, теоретически, независимым от текущего локали. См. perllocale для получения подробностей.

Как установить метку времени файла в Perl?

Используйте функцию utime(), документированную в "utime" в perlfunc. В качестве примера, вот небольшая программа, которая копирует время чтения и записи из первого аргумента во все остальные.

if (@ARGV < 2) {
    die "usage: cptimes timestamp_file other_files ...\n";
}
my $timestamp = shift;
my($atime, $mtime) = (stat($timestamp))[8,9];
utime $atime, $mtime, @ARGV;

Проверка ошибок, как обычно, оставлена в качестве упражнения для читателя.

В perldoc для utime также есть пример, который имеет тот же эффект, что и touch(1) на файлах, которые уже существуют.

Некоторые файловые системы имеют ограниченные возможности хранения временных меток файла с ожидаемой точностью. Например, файловые системы FAT и HPFS не могут создавать даты файлов с точностью выше двух секунд. Это ограничение файловой системы, а не utime().

Как печатать в более чем один файл одновременно?

Чтобы подключить один дескриптор файла к нескольким выходным дескрипторам файлов, вы можете использовать модули IO::Tee или Tie::FileHandle::Multiplex.

Если вам нужно сделать это только один раз, вы можете напечатать индивидуально в каждый дескриптор файла.

for my $fh ($fh1, $fh2, $fh3) { print $fh "whatever\n" }

Как прочитать весь файл сразу?

Обычный подход Perl для обработки всех строк в файле — делать это по одной строке за раз:

open my $input, '<', $file or die "can't open $file: $!";
while (<$input>) {
    chomp;
    # do something with $_
}
close $input or die "can't close $file: $!";

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

my @lines = <INPUT>;

Вы должны долго и серьезно подумать о том, почему вам нужно все загрузить сразу. Это просто не масштабируемое решение.

Если вы "mmap" файл с помощью модуля File::Map из CPAN, вы можете фактически загрузить весь файл в строку без его фактического хранения в памяти:

use File::Map qw(map_file);

map_file my $string, $filename;

После сопоставления вы можете обращаться к $string как к любой другой строке. Поскольку вам не обязательно загружать данные, mmap может быть очень быстрым и может не увеличить ваш объем памяти.

Вы также можете найти более интересным использование стандартного модуля Tie::File или модуля DB_File с его $DB_RECNO привязками, которые позволяют привязать массив к файлу, так что доступ к элементу массива фактически осуществляется к соответствующей строке в файле.

Если вы хотите загрузить весь файл, вы можете использовать модуль Path::Tiny, чтобы сделать это одним простым и эффективным шагом:

use Path::Tiny;

my $all_of_it = path($filename)->slurp; # entire file in scalar
my @all_lines = path($filename)->lines; # one line per element

Или вы можете прочитать содержимое всего файла в скаляр так:

my $var;
{
    local $/;
    open my $fh, '<', $file or die "can't open $file: $!";
    $var = <$fh>;
}

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

my $var = do { local $/; <$fh> };

Вы также можете использовать локальный @ARGV для устранения open.

my $var = do { local( @ARGV, $/ ) = $file; <> };

Как читать файл по абзацам?

Используйте переменную $/ (см. 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` не работает?

Ошибочка! Вы только что поместили табуляцию и символ перевода формы в имя файла! Помните, что в строках в двойных кавычках ("like\this") обратный слэш является символом экранирования. Полный список таких символов находится в "Quote and Quote-like Operators" в perlop. Неудивительно, что у вас нет файла под названием "c:(tab)emp(formfeed)oo" или "c:(tab)emp(formfeed)oo.exe" на вашей файловой системе DOS старого образца.

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

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

Потому что даже на непортативных системах функция Perl glob следует стандартной семантике подстановки имен 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.

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

Авторские права (c) 1997-2010 Тома Кристиана, Нейтана Торкингтона и других авторов, как указано. Все права защищены.

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

Независимо от его распространения, все примеры кода здесь находятся в общественном достоянии. Вы имеете право и вам рекомендуется использовать этот код и любые производные от него в своих программах для развлечения или с выгодой для себя, как вам заблагорассудится. Необязательный, но вежливый способ – добавить в код комментарий, благодарящий FAQ.

© 1993–2021 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.36.0/perlfaq5

Spec-Zone.ru

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