2009-12-28 4 views
3
  • Как проверить строку (значение $ _) - это пустая строка в Perl? Или еще хороший способ проверить это вместо использования $ _?Как я могу обнаружить пустую строку в Perl?

    Я хочу, чтобы закодировать как этот

    если ($ _ эк '') # Проверка текущей строки является пустая строка (без каких-либо символов) { $ х = 0; }


Я обновил код с раствором вопрос ниже.

Мой test.txt для синтаксического анализа:

constant fixup private GemAlarmFileName = <A "C:\\TMP\\ALARM.LOG"> 
     vid = 0 
     name = "" 
     units = "" 

    constant fixup private GemConfigAlarms = <U1 0>   /* my Comment */ 
     vid = 1 
     name = "CONFIGALARMS" 
    units = "" 
    min = <U1 0> 
    max = <U1 2> 
    default = <U1 0> 

Мой код находится ниже.

Вот почему мне нужно сначала установить $ x = 0. Я не уверен, что это нормальное решение или нет.

sub ConstantParseAndPrint 
    { 
     if (/^$/) // SOLUTION! 
     { 
      $x = 0; 
     } 

     if ($x == 0) 
     { 
      if (/^\s*(constant)\s*(fixup|\/\*fixup\*\/|)\s*(private|)\s*(\w+)\s+=\s+<([a-zA-Z0-9]+)\s+(["']?)([a-zA-Z0-9.:\\]+)\6>\s*(\/\*\s*(.*?)\s*\*\/|)(\r|\n|\s)/) 
      { 
       $name1 = $1; # Constant 
       $name2 = $2; # Fixup 
       $name3 = $3; # Private 
       $name4 = $4; 
       $name5 = $5; 
       $name6 = $7; 
       $name7 = $8; 
       # start print 
       if (!$name7 eq '') 
       { 
        print DEST_XML_FILE "<!-- $name7-->\n"; 
       } 
       print DEST_XML_FILE " <ECID"; 
       print DEST_XML_FILE " logicalName=\"$name4\""; 
       print DEST_XML_FILE " valueType=\"$name5\""; 
       print DEST_XML_FILE " value=\"$name6\""; 
       $x = 1; 
      } 
     } 
     elsif ($x == 1) 
     { 
      if(/\s*vid\s*=\s*(.*?)(\s|\n|\r)/) 
      { 
       $nID = $1; 
       print DEST_XML_FILE " vid=\"$nID\""; 
       $x = 2; 
      } 
     } 
      elsif ($x == 2) 
     { 
      if(/\s*name\s*=\s*(.*?)(\s|\n|\r)/) 
      { 
       $nName = $1; 
       print DEST_XML_FILE " name=$nName"; 
       $x = 3; 
      } 
     } 
      elsif ($x == 3) 
     { 
      if (/\s*units\s*=\s*(.*?)(\s|\n|\r)/) 
      { 
       $nUnits = $1; 
       print DEST_XML_FILE " units=$nUnits"; 
       $x = 4; 
      } 
     } 
     elsif ($x == 4) 
     { 
      # \s+<([a-zA-Z0-9]+)\s+([a-zA-Z0-9]+)>\ 
      if (/\s*min\s*=\s+<([a-zA-Z0-9]+)\s+([a-zA-Z0-9]+)>(\s|\n|\r)/) 
      { 
       #$nMinName1 = $1; 
       $nMinName2 = $2; # Find the nMin Value 
       #$nMinName3 = $3; 
       #$nMinName4 = $4; 
       print DEST_XML_FILE " min=\"$nMinName2\""; 
       $x = 5; 
      } 
      else 
      { 
       print DEST_XML_FILE "></ECID>\n"; 
       $x = 0; # There is no line 4 and line 5 
      } 
     } 
     elsif ($x == 5) 
     { 
      if (/\s*max\s*=\s+<([a-zA-Z0-9]+)\s+([a-zA-Z0-9]+)>(\s|\n|\r)/) 
      { 
       #$nMaxName1 = $1; 
       $nMaxName2 = $2; # Find the nMax Value 
       #$nMaxName3 = $3; 
       #$nMaxName4 = $4; 
       print DEST_XML_FILE " max=\"$nMaxName2\""; 
       $x = 6; 
      } 
     } 
     elsif ($x == 6) 
     { 
      if (/\s*default\s*=\s+<([a-zA-Z0-9]+)\s+([a-zA-Z0-9]+)>(\s|\n|\r)/) 
      { 
       #$nDefault1 = $1; 
       $nDefault2 = $2; # Find the default Value 
       #$nDefault3 = $3; 
       #$nDefault4 = $4; 
       print DEST_XML_FILE " default=\"$nDefault2\">"; 
       print DEST_XML_FILE "</ECID>\n"; 
       $x = 0; 
      } 
     } 
    } 
+6

Почему вы все еще делаете все неправильные вещи, которые я, Шверн и другие указывали в ваших предыдущих вопросах? http://stackoverflow.com/questions/1945538/how-do-i-extract-and-parse-quoted-strings-in-perl http://stackoverflow.com/questions/1919232/how-can-i-write -xml-data-to-a-file-with-perl Вы даже не заменили неправильное использование 'printf'' 'print'! Мне жаль бедняков, которые должны поддерживать этот код. Я плохо себя чувствую для тех, кто платит за это тоже. Разве вы не поняли суть этих предложений? – daotoad

+3

Действительно, в этом коде есть так много WTF, что трудно пробраться через них, чтобы определить * актуальный * вопрос, встроенный в него. Использование глобальных переменных вместо передачи аргументов функции; неправильное использование 'printf'; нумерованные переменные, а не разумные имена (или массив); огромные горные предложения 'if', которые должны обрабатываться переключателем или отдельными функциями; это продолжается и продолжается. – Ether

+0

@daotoad, СПАСИБО за ваш комментарий. На самом деле, я никогда не учился perl раньше. Это мой первый проект (я не знал различий между «printf» и «print»). Я нашел еще один скрипт perl, который написал мой стиль. Вот почему я публиковал снова и снова. Я уже признал преимущество предложения Шверна, и ваш учебник по деталям помог мне много актуален. Я бы сменил свой дизайн с вашим гидом. Очень сложно двигаться на основе моего огромного и сложного текстового файла, кстати, я мог понять, почему я ДОЛЖЕН изменить свой подход к вашему методу предложения. , :-) –

ответ

7

против моего лучшего суждения я постараюсь помочь вам снова.

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

В этом случае проблема заключается в «Как разобрать этот формат?»

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

ВНИМАНИЕ: анализатор не подвергается тщательной проверке на всех случаях. У него недостаточно встроенной обработки ошибок. Для этих функций вы можете запросить тарифную карту или написать ее самостоятельно.

Вот выборка данных вы предоставили (я не уверен, какой из нескольких вопросов, которые я вытащил это из):

constant fixup GemEstabCommDelay = <U2 20> 
    vid = 6 
    name = "ESTABLISHCOMMUNICATIONSTIMEOUT" 
    units = "s" 
    min = <U2 0> 
    max = <U2 1800> 
    default = <U2 20> 


constant fixup private GemConstantFileName = <A "C:\\TMP\\CONST.LOG"> 
    vid = 4 
    name = "" units = "" 


constant fixup private GemAlarmFileName = <A "C:\\TMP\\ALARM.LOG"> 
    vid = 0 
    name = "" 
    units = "" 

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

Итак, на основе выборочных данных, мы можем видеть, что:

  1. данные разбиваются на блоки.
  2. каждый блок начинается со слова constant в графе 0.
  3. каждый блок заканчивается пустой линией.
  4. блок состоит из стартовой линии и нулевых или более дополнительных строк.
  5. Стартовая строка состоит из ключевого слова constant, за которым следует одно или несколько слов с разделителями пробелов, знак '=' и значение данных, приведенное в качестве значения <>.
    • Последнее ключевое слово представляет собой имя константы.Назовите его constant_name
    • Данные, полученные с помощью <>, представляются комбинированным спецификатором типа/значения.
    • Предыдущие ключевые слова указывают, что указаны дополнительные метаданные о константе. Назовем их options.
  6. Дополнительные строки указывают дополнительные пары значений ключа. Назовем их атрибутами. Атрибуты могут иметь одно значение или у них может быть спецификатор типа/значения.
  7. Один или несколько атрибутов могут отображаться в одной строке.

Хорошо, теперь у нас есть приблизительная спецификация. Что нам с этим делать?

Как структурирован формат? Рассмотрим логические единицы организации от самых больших до самых маленьких. Они будут определять структуру и поток нашего кода.

  • ФАЙЛ сделан из БЛОКОВ.
  • БЛОКИ ИЗ ЛИНИЙ.

Поэтому наш парсер должен разложить файл на блоки, а затем обработать блоки.

Теперь мы шероховатый из парсер в комментариях:

# Parse a constant spec file. 

# Until file is done: 
    # Read in a whole block 
    # Parse the block and return key/value pairs for a hash. 

    # Store a ref to the hash in a big hash of all blocks, keyed by constant_name. 

# Return ref to big hash with all block data 

Теперь мы начинаем заполнять некоторый код:

# Parse a constant spec file. 
sub parse_constant_spec { 
    my $fh = shift; 

    my %spec; 

    # Until file is done: 
     # Read in a whole block 
    while(my $block = read_block($fh)) { 

     # Parse the and return key/value pairs for a hash. 
     my %constant = parse_block($block); 

     # Store a ref to the hash in a big hash of all blocks, keyed by constant_name. 
     $spec{ $constant{name} } = \%constant; 

    } 

    # Return ref to big hash with all block data 
    return \%spec; 
} 

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

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

#!/usr/bin/perl 
use strict; 
use warnings; 

use Data::Dumper; 

my $fh = \*DATA; 

print Dumper parse_constant_spec($fh); 


# Parse a constant spec file. 
# Pass in a handle to process. 
# As long as it acts like a file handle, it will work. 
sub parse_constant_spec { 
    my $fh = shift; 

    my %spec; 

    # Until file is done: 
     # Read in a whole block 
    while(my $block = read_block($fh)) { 

     # Parse the and return key/value pairs for a hash. 
     my %constant = parse_block($block); 

     # Store a ref to the hash in a big hash of all blocks, keyed by constant_name. 
     $spec{ $constant{const_name} } = \%constant; 

    } 

    # Return ref to big hash with all block data 
    return \%spec; 
} 

# Read a constant definition block from a file handle. 
# void return when there is no data left in the file. 
# Otherwise return an array ref containing lines to in the block. 
sub read_block { 
    my $fh = shift; 

    my @lines; 
    my $block_started = 0; 

    while(my $line = <$fh>) { 

     $block_started++ if $line =~ /^constant/; 

     if($block_started) { 

      last if $line =~ /^\s*$/; 

      push @lines, $line; 
     } 
    } 

    return \@lines if @lines; 

    return; 
} 


sub parse_block { 
    my $block = shift; 
    my ($start_line, @attribs) = @$block; 

    my %constant; 

    # Break down first line: 
    # First separate assignment from option list. 
    my ($start_head, $start_tail) = split /=/, $start_line; 

    # work on option list 
    my @options = split /\s+/, $start_head; 

    # Recover constant_name from options: 
    $constant{const_name} = pop @options; 
    $constant{options} = \@options; 

    # Now we parse the value/type specifier 
    @constant{'type', 'value' } = parse_type_value_specifier($start_tail); 

    # Parse attribute lines. 
    # since we've already got multiple per line, get them all at once. 
    chomp @attribs; 
    my $attribs = join ' ', @attribs; 

    # we have one long line of mixed key = "value" or key = <TYPE VALUE> 

    @attribs = $attribs =~ /\s*(\w+\s+=\s+".*?"|\w+\s+=\s+<.*?>)\s*/g; 

    for my $attrib (@attribs) { 
     warn "$attrib\n"; 
     my ($name, $value) = split /\s*=\s*/, $attrib; 

     if($value =~ /^"/) { 
      $value =~ s/^"|"\s*$//g; 
     } 
     elsif($value =~ /^</) { 
      $value = [ parse_type_value_specifier($start_tail) ]; 
     } 
     else { 
      warn "Bad line"; 
     } 

     $constant{ $name } = $value; 
    } 

    return %constant; 
} 

sub parse_type_value_specifier { 
    my $tvs = shift; 

    my ($type, $value) = $tvs =~ /<(\w+)\s+(.*?)>/; 

    return $type, $value; 
} 

__DATA__ 
constant fixup GemEstabCommDelay = <U2 20> 
    vid = 6 
    name = "ESTABLISHCOMMUNICATIONSTIMEOUT" 
    units = "s" 
    min = <U2 0> 
    max = <U2 1800> 
    default = <U2 20> 


constant fixup private GemConstantFileName = <A "C:\\TMP\\CONST.LOG"> 
    vid = 4 
    name = "" units = "" 


constant fixup private GemAlarmFileName = <A "C:\\TMP\\ALARM.LOG"> 
    vid = 0 
    name = "" 
    units = "" 

Приведенный выше код далек от совершенства. IMO, parse_block слишком длинный и должен быть разбит на более мелкие субмарины. Кроме того, почти недостаточно проверки и обеспечения корректного ввода. Имена переменных и описания могут быть более четкими, но я не понимаю семантики вашего формата данных. Более лучшие имена будут более точно соответствовать семантике формата данных.

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

Если вы используете этот формат во многих местах, я рекомендую поместить код синтаксического анализа в модуль. См. perldoc perlmod для получения дополнительной информации.

Теперь прекратите использовать глобальные переменные и игнорируйте полезные советы. Пожалуйста, начните читать perldoc, прочитайте «Изучение Perl и Perl Best Practices», используйте строгие предупреждения об использовании. Пока я читаю списки чтения, пройдите Global Variables are Bad, а затем побродите по вики, чтобы читать и учиться. Я узнал больше о написании программного обеспечения, прочитав c2, чем в школе.

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

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

+0

@daotoad, вы отправили отличный учебник для меня. Я читаю и практикую. Кстати, мне нравится программировать обучение и английский язык одновременно. БОЛЬШОЕ СПАСИБО. –

+0

Привет, я получил некоторый вопрос, чтобы двигаться дальше. '@attribs = $ attribs = ~ /\s*(\w+\s+=\s+".*?"|\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ например, * # у нас есть одна длинная строка смешанного ключа = «значение» или ключ = *, я должен действовать в соответствии с другим условием ** key = значение **, без знаков двойного счета («Значение»), например, vid = 6 выше , я попытался изменить код на своем локальном ноутбуке, но не смог его улучшить. –

+0

Кстати, я попытался вставить print ** Type, Value, Attributes Itmes ** в ** sub parse_block ** успешно.Есть ли хороший способ распечатать сложный хэш ** \% spec **? Сформировал результат печати в формате XML: * * –

10
if ($_ =~ /^\s*$/) { 
    # blank 
} 

проверяет 0 или более пробелов (\s*), связанных с началом (^)/концом ($) от линии. Это проверка на пустая строка (то есть может иметь пробелы). Если вы хотите получить пустую строку, просто удалите \s*.

Проверка на $_ может быть неявной, поэтому вы можете уменьшить приведенное выше значение до if (/^\s*$/) для краткости.

+0

Привет, Брайан, я заменил свой код на "if ($ _ ~ =/^ \ s * $ /)" - показать синтаксическую ошибку ? –

+0

Моя ошибка. Переверните значение ~ = to = ~. –

+0

Теперь он работает хорошо. БОЛЬШОЕ СПАСИБО. –

5

Вы можете использовать:

if ($_ =~ /^$/) 

или даже просто

if (/^$/) 

поскольку Perl предполагает проверку на $_

+0

обе стили работают хорошо. Спасибо. –

+0

Вы не хотите использовать $ там, так как он допускает завершающую новую строку. Используйте \ z для обозначения абсолютного конца строки. Используйте более точный инструмент, когда сможете. –

+0

Брайан, спасибо, что указал. –

0

так, как вы показали - if ($_ eq '') абсолютно нормален. Возможно, вам следует описать, в чем проблема с этим?

+1

Это проверяет только * пустую строку * без символов. Строка * * * может содержать пробелы. Я бы использовал вашу версию, но потом снова использовал trim(). – Nifle

+0

Привет, Depesz, я обновил свой код выше. Вот почему я хочу инициализировать $ x = 0 при чтении новой пустой строки. Спасибо за заботу. –

2

Если вы просто хотите, чтобы проверить текущее значение $_ или $var является пустым (или, по крайней мере, все пробельные) линии, а затем что-то вроде

if (/^\s*$/) { ... } 
if ($var =~ /^\s*$/){ ... } 

, как некоторые другие уже упоминали ,

Однако я считаю, что чаще всего я хочу игнорировать пустые строки при обработке ввода в цикле.Я делаю это так:

while (<>) { 
    next if /^\s*$/; 
    ... 
} 

Если я хочу, чтобы традиционные комментарии оболочки стиля, я обычно

s/\s*#.*$//; 

непосредственно перед проверкой на пустую строку.

+0

Спасибо за подробную информацию. –

6

Ответ зависит от того, что вы имеете в виду пустой строкой (не содержит ли он никаких символов кроме символа новой строки или содержит ли он только пробелы). Идиоматических способ справиться с этим, чтобы использовать отрицательный матч против \S, совпадающий в обоих случаях:

if (! /\S/) { 
    ... 
} 

Если вы ищете только для первого, чем ваш собственный ответ хорошо.

Вы часто видите эту технику, используемую в качестве фильтра:

while (<>) { 
    next unless /\S/; # Ignore blank lines. 
    ... 
} 
+0

Благодарим вас за подробности. Это очень простой стиль, чтобы покрыть мои потребности. –

+0

@jmcnamara: проголосовали. ваш «следующий, если/\ S /» намного лучше, чем «next if/^ \ s * $ /», к которому я привык! – 2009-12-29 06:49:14

Смежные вопросы