Должен ли я вручную установить @ARGV в Perl, чтобы использовать <> для открытия, сканирования и закрытия файлов? - PullRequest
5 голосов
/ 03 февраля 2009

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

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

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

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

Ответы [ 5 ]

9 голосов
/ 03 февраля 2009

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

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, который будет читабелен и понятен вам (и вашим коллегам, если это важно для вашего кода / карьеры).

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

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

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

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

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

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

3 голосов
/ 03 февраля 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
}
1 голос
/ 03 февраля 2009

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

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

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

grep -l <pattern> <path>

в Unix.

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

1 голос
/ 03 февраля 2009

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

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

0 голосов
/ 04 февраля 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: Временные значения через локальные () .

В общем случае создания новых переменных и их использования, 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";
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...