разбить текстовый файл по скобкам или параграфам (только на верхнем уровне) в терминале - PullRequest
0 голосов
/ 18 декабря 2018

У меня есть несколько текстовых файлов (utf-8), которые я хочу обработать в сценарии оболочки.Они не совсем одного формата, но если бы я мог разбить их на съедобные куски, я бы справился с этим.Это может быть запрограммировано в C или Python, но я предпочитаю нет.

РЕДАКТИРОВАТЬ: я написал решение в C;увидеть мой собственный ответ.Я думаю, что это может быть самый простой подход в конце концов.Если вы считаете, что я ошибаюсь, проверьте свое решение на более сложном примере ввода из моего ответа ниже.

- jcxz100

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

Все типы входных файлов состоят из:

  1. нежелательные строки
  2. строки с нежелательным текстом, за которыми следуют начальные скобки или круглые скобки - то есть '[' '{' '<' или '(') и, возможно, за ними следует полезная нагрузка </li>
  3. строки полезной нагрузки
  4. строки с квадратными скобками или скобкамивложенный в пары верхнего уровня, также рассматривается как полезная нагрузка
  5. строки полезной нагрузки с конечными скобками или парантезами - то есть ']' '}' '>' или ')' - возможно, за которыми следует что-то (нежелательный текст и /или начало новой полезной нагрузки)

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

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

Вот пример (с использованием только {} пар):

junk text
"atomic junk"

some junk text followed by a start bracket { here is the actual payload
   more payload
   "atomic payload"
   nested start bracket { - all of this line is untouchable payload too
      here is more payload
      "yet more atomic payload; this one's got a smiley ;-)"
   end of nested bracket pair } - all of this line is untouchable payload too
   this is payload too
} trailing junk
intermittent junk
{
   payload that goes in second output file    }
end junk

... извините: некоторые из входных файлов действительно такие грязные.

Первый выходной файл должен быть:

{ here is the actual payload
   more payload
   "atomic payload"
   nested start bracket { - all of this line is untouchable payload too
      here is more payload
      "yet more atomic payload; this one's got a smiley ;-)"
   end of nested bracket pair } - all of this line is untouchable payload too
   this is payload too
}

... и второй выходной файл:

{
   payload that goes in second output file    }

Примечание:

  • Я не совсем решил, нужно ли сохранять пару начальных / конечных символов в выводе или они сами должны быть отброшены как ненужные.Я думаю, что решение, в котором они содержатся, является более общим.

  • В одном и том же входном файле могут быть разные типы пар скобок / паразитов верхнего уровня.

  • Осторожно: во входных файлах есть символы * и $, поэтому избегайте путаницы с bash; -)

  • Я предпочитаю удобочитаемость, а не краткость;но не по экспоненциальной стоимости скорости.

Приятно иметь:

  • Есть экранирование с обратной косой чертойдвойные кавычки внутри текста;желательно, чтобы они были обработаны (у меня есть хак, но это не красиво).

  • Скрипт не должен разбивать несоответствующие пары скобок / скобок в мусорной и / или полезной нагрузке (примечание: внутри атома они должны быть разрешенными!)

Более-далеко-приятно-иметь:

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

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

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

Основная проблема - правильное разбиение ввода - все остальное можно игнорировать или "решено "с помощью хаков, так что не стесняйтесь игнорировать приятных вещей и более-далеко-приятно-иметь-1103 *.

Ответы [ 2 ]

0 голосов
/ 19 декабря 2018

У меня есть решение на C. Казалось бы, слишком много сложностей, чтобы этого было легко достичь в сценарии оболочки.Программа не слишком сложна, но, тем не менее, содержит более 200 строк кода, которые включают проверку ошибок, некоторую оптимизацию скорости и другие тонкости.

Исходный файл split-brackets-to-chunks.c:

#include <stdio.h>

/* Example code by jcxz100 - your problem if you use it! */

#define BUFF_IN_MAX 255
#define BUFF_IN_SIZE (BUFF_IN_MAX+1)

#define OUT_NAME_MAX 31
#define OUT_NAME_SIZE (OUT_NAME_MAX+1)

#define NO_CHAR '\0'

int main()
{
    char pcBuff[BUFF_IN_SIZE];
    size_t iReadActual;
    FILE *pFileIn, *pFileOut;
    int iNumberOfOutputFiles;
    char pszOutName[OUT_NAME_SIZE];
    char cLiteralChar, cAtomicChar, cChunkStartChar, cChunkEndChar;
    int iChunkNesting;
    char *pcOutputStart;
    size_t iOutputLen;

    pcBuff[BUFF_IN_MAX] = '\0';  /* ... just to be sure. */
    iReadActual = 0;
    pFileIn = pFileOut = NULL;
    iNumberOfOutputFiles = 0;
    pszOutName[OUT_NAME_MAX] = '\0';  /* ... just to be sure. */
    cLiteralChar = cAtomicChar = cChunkStartChar = cChunkEndChar = NO_CHAR;
    iChunkNesting = 0;
    pcOutputStart = (char*)pcBuff;
    iOutputLen = 0;

    if ((pFileIn = fopen("input-utf-8.txt", "r")) == NULL)
    {
        printf("What? Where?\n");
        return 1;
    }

    while ((iReadActual = fread(pcBuff, sizeof(char), BUFF_IN_MAX, pFileIn)) > 0)
    {
        char *pcPivot, *pcStop;

        pcBuff[iReadActual] = '\0'; /* ... just to be sure. */
        pcPivot = (char*)pcBuff;
        pcStop = (char*)pcBuff + iReadActual;

        while (pcPivot < pcStop)
        {
            if (cLiteralChar != NO_CHAR) /* Ignore this char? */
            {
                /* Yes, ignore this char. */

                if (cChunkStartChar != NO_CHAR)
                {
                    /* ... just write it out: */
                    fprintf(pFileOut, "%c", *pcPivot);
                }
                pcPivot++;
                cLiteralChar = NO_CHAR;

                /* End of "Yes, ignore this char." */
            }
            else if (cAtomicChar != NO_CHAR) /* Are we inside an atomic string? */
            {
                /* Yup; we are inside an atomic string. */

                int bBreakInnerWhile;
                bBreakInnerWhile = 0;

                pcOutputStart = pcPivot;
                while (bBreakInnerWhile == 0)
                {
                    if (*pcPivot == '\\') /* Treat next char as literal? */
                    {
                        cLiteralChar = '\\'; /* Yes. */
                        bBreakInnerWhile = 1;
                    }
                    else if (*pcPivot == cAtomicChar) /* End of atomic? */
                    {
                        cAtomicChar = NO_CHAR; /* Yes. */
                        bBreakInnerWhile = 1;
                    }
                    if (++pcPivot == pcStop) bBreakInnerWhile = 1;
                }
                if (cChunkStartChar != NO_CHAR)
                {
                    /* The atomic string is part of a chunk. */
                    iOutputLen = (size_t)(pcPivot-pcOutputStart);
                    fprintf(pFileOut, "%.*s", iOutputLen, pcOutputStart);
                }

                /* End of "Yup; we are inside an atomic string." */
            }
            else if (cChunkStartChar == NO_CHAR) /* Are we inside a chunk? */
            {
                /* No, we are outside a chunk. */

                int bBreakInnerWhile;
                bBreakInnerWhile = 0;
                while (bBreakInnerWhile == 0)
                {
                    /* Detect start of anything interesting: */
                    switch (*pcPivot)
                    {
                        /* Start of atomic? */
                        case '"':
                        case '\'':
                            cAtomicChar = *pcPivot;
                            bBreakInnerWhile = 1;
                            break;

                        /* Start of chunk? */
                        case '{':
                            cChunkStartChar = *pcPivot;
                            cChunkEndChar = '}';
                            break;
                        case '[':
                            cChunkStartChar = *pcPivot;
                            cChunkEndChar = ']';
                            break;
                        case '(':
                            cChunkStartChar = *pcPivot;
                            cChunkEndChar = ')';
                            break;
                        case '<':
                            cChunkStartChar = *pcPivot;
                            cChunkEndChar = '>';
                            break;
                    }
                    if (cChunkStartChar != NO_CHAR)
                    {
                        iNumberOfOutputFiles++;
                        printf("Start '%c' '%c' chunk (file %04d.txt)\n", *pcPivot, cChunkEndChar, iNumberOfOutputFiles);
                        sprintf((char*)pszOutName, "output/%04d.txt", iNumberOfOutputFiles);
                        if ((pFileOut = fopen(pszOutName, "w")) == NULL)
                        {
                            printf("What? How?\n");
                            fclose(pFileIn);
                            return 2;
                        }
                        bBreakInnerWhile = 1;
                    }
                    else if (++pcPivot == pcStop)
                    {
                        bBreakInnerWhile = 1;
                    }
                }

                /* End of "No, we are outside a chunk." */
            }
            else
            {
                /* Yes, we are inside a chunk. */

                int bBreakInnerWhile;
                bBreakInnerWhile = 0;

                pcOutputStart = pcPivot;
                while (bBreakInnerWhile == 0)
                {
                    if (*pcPivot == cChunkStartChar)
                    {
                        /* Increase level of brackets/parantheses: */
                        iChunkNesting++;
                    }
                    else if (*pcPivot == cChunkEndChar)
                    {
                        /* Decrease level of brackets/parantheses: */
                        iChunkNesting--;
                        if (iChunkNesting == 0)
                        {
                            /* We are now outside chunk. */
                            bBreakInnerWhile = 1;
                        }
                    }
                    else
                    {
                        /* Detect atomic start: */
                        switch (*pcPivot)
                        {
                            case '"':
                            case '\'':
                                cAtomicChar = *pcPivot;
                                bBreakInnerWhile = 1;
                                break;
                        }
                    }
                    if (++pcPivot == pcStop) bBreakInnerWhile = 1;
                }
                iOutputLen = (size_t)(pcPivot-pcOutputStart);
                fprintf(pFileOut, "%.*s", iOutputLen, pcOutputStart);
                if (iChunkNesting == 0)
                {
                    printf("File done.\n");
                    cChunkStartChar = cChunkEndChar = NO_CHAR;
                    fclose(pFileOut);
                    pFileOut = NULL;
                }

                /* End of "Yes, we are inside a chunk." */
            }
        }
    }
    if (cChunkStartChar != NO_CHAR)
    {
        printf("Chunk exceeds end-of-file. Exiting gracefully.\n");
        fclose(pFileOut);
        pFileOut = NULL;
    }

    if (iNumberOfOutputFiles == 0) printf("Nothing to do...\n");
    else printf("All done.\n");
    fclose(pFileIn);
    return 0;
}

Я решил приятных для обладания и один из более дальних приятных для обладания ,Чтобы показать это, ввод немного сложнее, чем пример в вопросе:

junk text
"atomic junk"

some junk text followed by a start bracket { here is the actual payload
   more payload
   'atomic payload { with start bracket that should be ignored'
   nested start bracket { - all of this line is untouchable payload too
      here is more payload
"this atomic has a literal double-quote \" inside"
      "yet more atomic payload; this one's got a smiley ;-) and a heart <3"
   end of nested bracket pair } - all of this line is untouchable payload too
   this is payload too
   "here's a totally unprovoked $ sign and an * asterisk"
} trailing junk
intermittent junk
<
   payload that goes in second output file } mismatched end bracket should be ignored     >
end junk

Результирующий файл output / 0001.txt :

{ here is the actual payload
   more payload
   'atomic payload { with start bracket that should be ignored'
   nested start bracket { - all of this line is untouchable payload too
      here is more payload
"this atomic has a literal double-quote \" inside"
      "yet more atomic payload; this one's got a smiley ;-) and a heart <3"
   end of nested bracket pair } - all of this line is untouchable payload too
   this is payload too
   "here's a totally unprovoked $ sign and an * asterisk"
}

... и полученный файл output / 0002.txt :

<
   payload that goes in second output file } mismatched end bracket should be ignored     >

Спасибо @dawg за помощь:)

0 голосов
/ 18 декабря 2018

Дано:

$ cat file
junk text
"atomic junk"

some junk text followed by a start bracket { here is the actual payload
   more payload
   "atomic payload"
   nested start bracket { - all of this line is untouchable payload too
      here is more payload
      "yet more atomic payload; this one's got a smiley ;-)"
   end of nested bracket pair } - all of this line is untouchable payload too
   this is payload too
} trailing junk
intermittent junk
{
   payload that goes in second output file    }
end junk

Этот perl-файл извлечет описанные вами блоки в файлы block_1, block_2 и т. Д .:

#!/usr/bin/perl
use v5.10;
use warnings;
use strict;

use Text::Balanced qw(extract_multiple extract_bracketed);

my $txt;

while (<>){$txt.=$_;}  # slurp the file

my @blocks = extract_multiple(
    $txt,
    [
        # Extract {...}
        sub { extract_bracketed($_[0], '{}') },
    ],
    # Return all the fields
    undef,
    # Throw out anything which does not match
    1
);
chdir "/tmp";
my $base="block_";
my $cnt=1;
for my $block (@blocks){ my $fn="$base$cnt";
                         say "writing $fn";
                         open (my $fh, '>', $fn) or die "Could not open file '$fn' $!";
                         print $fh "$block\n";
                         close $fh;
                         $cnt++;}

Теперь файлы:

$ cat block_1
{ here is the actual payload
   more payload
   "atomic payload"
   nested start bracket { - all of this line is untouchable payload too
      here is more payload
      "yet more atomic payload; this one's got a smiley ;-)"
   end of nested bracket pair } - all of this line is untouchable payload too
   this is payload too
}

$ cat block_2
{
   payload that goes in second output file    }

Использование Text::Balanced является надежным и, вероятно, лучшим решением.

Вы можете делать блоки с помощью одного Perl regex :

$ perl -0777 -nlE 'while (/(\{(?:(?1)|[^{}]*+)++\})|[^{}\s]++/g) {if ($1) {$cnt++; say "block $cnt:== start:\n$1\n== end";}}' file
block 1:== start:
{ here is the actual payload
   more payload
   "atomic payload"
   nested start bracket { - all of this line is untouchable payload too
      here is more payload
      "yet more atomic payload; this one's got a smiley ;-)"
   end of nested bracket pair } - all of this line is untouchable payload too
   this is payload too
}
== end
block 2:== start:
{
   payload that goes in second output file    }
== end

Но это немного более хрупко, чем использование правильного парсеракак Text::Balanced ...

...