Должен ли я вручную установить Perl @ARGV, чтобы я мог использовать ‹› для открытия, сканирования и закрытия файлов?

Недавно я начал изучать Perl, и одно из моих последних заданий связано с поиском определенной строки в группе файлов. Пользователь указывает имя каталога в качестве аргумента, и программа ищет шаблон во всех файлах в этом каталоге. Используя readdir(), мне удалось создать массив со всеми доступными для поиска именами файлов, и теперь мне нужно искать шаблон в каждом файле, моя реализация выглядит примерно так:

sub searchDir($) {
    my $dirN = shift;
    my @dirList = glob("$dirN/*");
    for(@dirList) {
        push @fileList, $_ if -f $_;

    }
    @ARGV = @fileList;
    while(<>) {
        ## Search for pattern
    }
}

Мой вопрос: можно ли вручную загрузить массив @ARGV, как это было сделано выше, и использовать оператор ‹> для сканирования в отдельных строках, или мне следует открывать/сканировать/закрывать каждый файл по отдельности? Будет ли какая-то разница, если эта обработка существует в подпрограмме, а не в основной функции?


person aks    schedule 03.02.2009    source источник
comment
Я настоятельно рекомендую вам не использовать прототипы в ваших функциях. По причинам см. stackoverflow.com/questions/ 297034/   -  person Leon Timmermans    schedule 03.02.2009


Ответы (5)


Что касается манипулирования @ARGV - это определенно рабочий код, Perl, безусловно, позволяет вам это делать. Я не думаю, что это хорошая привычка кодирования. Большая часть кода, который я видел, который использует идиому «пока (‹>)», использует ее для чтения из стандартного ввода, и я изначально ожидаю, что ваш код будет делать это. Более читаемым шаблоном может быть открытие/закрытие каждого входного файла по отдельности:

foreach my $file (@files) {
    open FILE, "<$file" or die "Error opening file $file ($!)";
    my @lines = <FILE>;
    close FILE or die $!;

    foreach my $line (@file) {
        if ( $line =~ /$pattern/ ) {
            # do something here!
        }
    }
}

Это было бы легче читать для меня, хотя это еще несколько строк кода. Perl дает вам большую гибкость, но я думаю, что гораздо важнее разработать свой собственный стиль в Perl, который будет читабельным и понятным для вас (и ваших коллег, если это важно для вашего кода/карьеры).

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

Удачи! Перл — это весело. :)

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

while ( my $line = <FILE> ) {
    if ( $line =~ /$pattern/ ) {
        # do something here!
    }
}

Пункт, когда я написал «вы вряд ли столкнетесь с ситуациями, в которых такой сценарий перегружает ваше оборудование», предназначался для того, чтобы охватить это, извините, что не был более конкретным. Кроме того, у кого вообще есть жесткие диски емкостью 4 ГБ, не говоря уже о файлах объемом 4 ГБ? :П

Другое редактирование: просматривая Интернет по совету комментаторов, я понял, что есть жесткие диски, которые намного больше, чем 4 ГБ, доступные для покупки. Я благодарю комментаторов за указание на это и обещаю в будущем никогда-никогда-никогда не пытаться написать саркастический комментарий в Интернете.

person James Thompson    schedule 03.02.2009
comment
Нет необходимости сначала хранить весь файл в памяти и проходить его в отдельном цикле. Представьте, например, что у него есть файл размером 4 ГБ! - person Frank; 03.02.2009
comment
У кого вообще есть жесткие диски на 4 Гб? - в каком десятилетии вы находитесь? У меня жесткий диск 150 Гб. Возможно, вы имели в виду 4 ГБ памяти, что несколько более правдоподобно. Но у меня на компе 2гб памяти. Так о чем именно вы говорите? - person Chris Lutz; 03.02.2009

Я бы предпочел эту более явную и удобочитаемую версию:

#!/usr/bin/perl -w 

foreach my $file (<$ARGV[0]/*>){
    open(F, $file) or die "$!: $file";
    while(<F>){
      # search for pattern
    }
    close F;
}

Но также можно манипулировать @ARGV:

#!/usr/bin/perl -w 

@ARGV = <$ARGV[0]/*>;
while(<>){
    # search for pattern
}
person Frank    schedule 03.02.2009
comment
К оригинальному плакату: обратите внимание на использование подстановки (‹*›), а не readdir(). - person nimrodm; 03.02.2009

Да, можно настроить список аргументов перед запуском цикла 'while (<>)'; было бы почти безрассудно настраивать его внутри цикла. Например, если вы обрабатываете аргументы опций, вы обычно удаляете элементы из @ARGV; здесь вы добавляете элементы, но это все равно изменяет исходное значение @ARGV.

Неважно, находится ли код в подпрограмме или в «основной функции».

person Jonathan Leffler    schedule 03.02.2009

Предыдущие ответы довольно хорошо освещают ваш основной вопрос о программировании на Perl.

Итак, позвольте мне прокомментировать основной вопрос: как найти закономерность в куче файлов.

В зависимости от ОС может иметь смысл вызвать специализированную внешнюю программу, например

grep -l <pattern> <path>

на юникс.

В зависимости от того, что вам нужно делать с файлами, содержащими шаблон, и насколько велико соотношение попаданий/промахов, это может сэкономить довольно много времени (и повторно использовать проверенный код).

person lexu    schedule 03.02.2009
comment
Большое спасибо за предложение, на самом деле я пробовал базовый порт egrep на Perl, чтобы работать с моим файловым вводом-выводом и регулярными выражениями. - person aks; 03.02.2009
comment
Это всегда баланс хранения в одном месте (например, перенос egrep на Perl, таким образом, весь Perl, самообслуживаемое, возможно переносимое решение, которое содержит самодельные ошибки) и использование инструментов, предоставляемых ОС (быстрое решение, проверенное, оптимизированное, но потенциально не переносимый). зависит от ситуации! - person lexu; 03.02.2009
comment
Это правда. В любой момент я бы выбрал инструменты ОС, чтобы выполнить свою работу, но в данном случае я пытался использовать свои знания о Perl в файловом вводе, чтобы улучшить свои знания (приобрел книгу по Perl неделю назад или около того). - person aks; 04.02.2009

Большая проблема с настройкой @ARGV заключается в том, что это глобальная переменная. Кроме того, вы должны знать, что while (<>) имеет специальные магические атрибуты. (чтение каждого файла в @ARGV или обработка STDIN, если @ARGV пуста, проверка определенности, а не истинности). Чтобы уменьшить магию, которую необходимо понять, я бы избегал ее, за исключением халтурных заданий.

Вы можете получить имя текущего файла, проверив $ARGV.

Вы можете этого не осознавать, но на самом деле вы влияете на две глобальные переменные, а не только на @ARGV. Вы также нажимаете $_. Также очень хорошая идея локализовать $_.

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

Кстати, есть еще одна важная, тонкая магия с <>. Допустим, вы хотите вернуть номер строки совпадения в файле. Вы можете подумать: хорошо, проверьте perlvar и обнаружите, что $. дает номер строки в последнем доступном дескрипторе — отлично. Но здесь скрывается проблема — $. не сбрасывается между @ARGV файлами. Это удобно, если вы хотите узнать, сколько всего строк вы обработали, но не в том случае, если вам нужен номер строки для текущего файла. К счастью, есть простой трюк с eof, который решит эту проблему.

use strict;
use warnings;

...

searchDir( 'foo' );

sub searchDir {
    my $dirN    = shift;
    my $pattern = shift;

    local $_;

    my @fileList = grep { -f $_ } glob("$dirN/*");

    return unless @fileList;  # Don't want to process STDIN.

    local @ARGV;

    @ARGV = @fileList;
    while(<>) {
        my $found = 0;
        ## Search for pattern
        if ( $found ) {
            print "Match at $. in $ARGV\n";
        }
    }
    continue {
        # reset line numbering after each file.
        close ARGV  if eof;  # don't use eof().
    }
}

ВНИМАНИЕ: я только что изменил ваш код в своем браузере. Я не запускал его, поэтому он может иметь опечатки и, вероятно, не будет работать без небольшой настройки.

Обновление: причина использования local вместо my заключается в том, что они делают совершенно разные вещи. my создает новую лексическую переменную, которая видна только в содержащемся блоке и недоступна через таблицу символов. local сохраняет существующую переменную пакета и присваивает ей псевдоним новой переменной. Новая локализованная версия видна в любом последующем коде, пока мы не покинем объемлющий блок. См. perlsub: временные значения через local().

В общем случае создания новых переменных и их использования правильным выбором будет my. local подходит, когда вы работаете с глобальными переменными, но вы хотите убедиться, что ваши изменения не распространятся на остальную часть программы.

Этот короткий скрипт демонстрирует локальное:

$foo = 'foo';

print_foo();
print_bar();
print_foo();

sub print_bar {
    local $foo;
    $foo = 'bar';
    print_foo();
}

sub print_foo {
    print "Foo: $foo\n";
}
person daotoad    schedule 04.02.2009
comment
Любая причина, по которой вы должны использовать «local» для переменных $_ и @ARGV вместо лексической области видимости с «my». - person aks; 04.02.2009
comment
Спасибо, документ perlsub оказался наиболее полезным для объяснения необходимости использования local при обработке специальных глобальных переменных и переменных пунктуации. - person aks; 05.02.2009