Использование sed для генерации нескольких файлов, имена которых зависят от искомого шаблона. - PullRequest
0 голосов
/ 11 марта 2012

По сути, у меня есть неправильно упорядоченный файл, который я хочу восстановить. Общим является шаблон сечения P, который в терминах регулярных выражений равен ^\d\{1,2}\\.\d\{1,2}\\.\d\{1,2}\\., т.е.: 1.2.3., а следующий - некоторый текст.

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

Моя стратегия до сих пор (потому что я не могу (пока) не справиться с многострочным регулярным выражением) -

  1. Заменить эти шаблоны P тем же шаблоном, которому предшествуют 6 & символов (в качестве маркеров)
  2. Затем замените все новые строки каким-либо другим шаблоном маркера, скажем, ###
  3. Затем (и вот тут у меня проблемы), с помощью sed, найдите что-нибудь вида &&&P[any characters]&&& и выведите его в файл с добавленным числом, значение которого равно P (то есть file1.2.3)
  4. Объедините эти файлы обратно в один файл в правильном порядке (не совсем уверен, как это сделать, но это еще не то, что блокирует меня)
  5. Удалите маркеры & и замените ### новыми строками.

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

Что касается (3), я пробовал производные от: sed s/\\(\&\&\&\\)\\(^\d\{1,2}\\.\d\{1,2}\\.\d\{1,2}\\.\\)\\(.*\&\&\&/\\)/\1\2\3/ < somefile > file\2

Где я пытаюсь использовать шаблон регулярных выражений \2 как расширение моего нового файла; и, ну, это вообще не работает!

Примечание: Я использую 6 &, поэтому я не выбираю шаблоны вида &&

Любая помощь будет принята с благодарностью!

Ответы [ 3 ]

0 голосов
/ 11 марта 2012

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

Perl (или Python) - разумная альтернатива. Я лучше владею Perl, чем Python, поэтому я бы использовал это.

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

Я читаю между строк немного, но я думаю, что ваш формат ввода документа примерно такой:

2.1.9
...multiple lines of material for section 2.1.9...
1.3.6
...multiple lines of material for section 1.3.6...
9.1.3
...multiple lines of material for section 9.1.3...

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

В общих чертах код должен выглядеть следующим образом:

my $current_section = "0.0.0";
my %section_list = ();
my $section_material = "";

while (<>)
{
    if (m/^(\d+\.\d+\.\d+)/)
    {
        # Found a new section...stash the old one...
        if ($section_material ne "")
        {
            # If the same section number appears twice, simply concatenate
            # the new material over the old.  Or you can get more complex,
            # using an array of refs to section material...
            $section_list{$current_section} = ""
                if !defined $section_list{$current_section}; 
            $section_list{$current_section} .= $section_material;
            $current_section = $1;
            $section_material = "";
        }
    }
    $section_material .= $_;
}
if ($section_material ne "")
{
    $section_list{$current_section} = ""
        if !defined $section_list{$current_section}; 
    $section_list{$current_section} .= $section_material;
}

# Now the hash %section_list contains all the material.
# You need a section number comparison function that can be used with sort
sub section_cmp
{
    ...if $a comes before $b...return -1
    ...if $b comes before $a...return +1
    ...otherwise...............return 0
}

foreach my $section (sort section_cmp keys %section_list)
{
     print "[$section]\n";
     print "$section_list{$section}\n";
}

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

Код является контурным. Я не полностью его изучил; это, вероятно, не минимально. В частности, фьюзинг с гарантией $section_list{$current_section} является пустым, если его не использовать раньше, что может легко привести к параноидальному излишеству. Другие детали, которые я должен тщательно проверить, это вызов функции сравнения в sort и механика функции сравнения.


Код сравнения ниже работает так, как я ожидаю. Я не уверен, что нет более умного способа сделать сравнение более лаконичным, но работать лучше, чем работать. Это небольшая независимая программа с тестовым набором:

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

my @array = ( "3.1.6", "1.2.9", "7.4.5", "2.1.3",   "10.1.2",  "1.1.1",
              "1.1.3", "1.4.9", "1.4",   "1.4.9.1", "1.10.13", "1.1.13" );

# For use from sort - data 'passed' as $a and $b
sub paranum_cmp
{
    my(@v1) = split /\./, $a;
    my(@v2) = split /\./, $b;
    my($l1) = scalar @v1;
    my($l2) = scalar @v2;
    my($len) = ($l1 < $l2) ? $l1 : $l2;

    for (my $i = 0; $i < $len; $i++)
    {
        return -1 if ($v1[$i] < $v2[$i]);
        return +1 if ($v1[$i] > $v2[$i]);
    }
    return -1 if ($l1 < $l2);
    return +1 if ($l1 > $l2);
    return 0;
}

print "Before:\n";
foreach my $v (@array) { print "$v\n"; }
@array = sort paranum_cmp @array;
print "After:\n";
foreach my $v (@array) { print "$v\n"; }

Вы можете найти v-числа или найти модуль «сравнения версий», который бы выполнял работу быстрее.

0 голосов
/ 11 марта 2012

awk может сделать это довольно элегантно:

#!/usr/bin/awk

# Put anything before the first section somewhere so we don't lose it.
BEGIN { section = "pre" }

# When we hit a new section, change to that section. Print the section to a file, for sorting later.
/^([0-9]{1,2}\.){3}/ { print (section=$0) >> "sections" }

# Print the line into the current working file
{ print >> section }

Теперь после выполнения этого каждый раздел находится в своем собственном файле, названном в честь раздела. Давайте объединим их.

# print the preamble if there was any
[ -f pre ] && cat pre > full

# sort has a -V option to sort version numbers, which is what you want.
sort -V sections | while read file; do cat "$file" >> full; done

И это все. У вас есть полный файл, отсортированный по разделам, а вся преамбула все еще находится вверху.

0 голосов
/ 11 марта 2012

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

while ( $line = <> ) {
    if( $line =~ /\d{1,2}.\d{1,2}.\d{1,2}/ ) {
        $section = $1;
        open( $SECTION, ">>", "out.$section.txt");
        print $SECTION $line;
        close $SECTION;
    }
}

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

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

...