Разделить текстовый файл в скобки или скобки (только на верхнем уровне) в терминале

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

EDIT: I wrote a solution in C; see my own answer. I think this may be the simplest approach after all. If you think I'm wrong please test your solution against the more complicated example input from my answer below.

-- jcxz100

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

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

  1. мусорные строки
  2. строки с ненужным текстом, за которыми следуют начальные скобки или круглые скобки - например, '[' '{' '<' или '(' - и, возможно, за ними следует полезная нагрузка
  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 ;-)

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

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

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

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

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

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

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

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

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

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

jcxz100 18.12.2018 17:19

Я дал вам хороший старт в Perl. Может быть, поиграйте с этим постом в конкретный вопрос Perl, если вы его не понимаете.

dawg 18.12.2018 17:44

Спасибо @dawg. Как вы думаете, Perl - лучший путь вперед? Сейчас я все равно подумываю написать (и, конечно же, опубликовать) программу на c, так как это может быть для меня проще, чем изучение Perl :)

jcxz100 18.12.2018 18:24

Есть много способов снять шкуру. Если вам нужно решение быстрый, Python, Ruby или Perl будут самыми быстрыми решениями. Конечно, вы можете написать что-нибудь на C, но это потребует больше усилий.

dawg 18.12.2018 18:29

Правильно. Итак, я написал свою первую программу на языке C за 8 лет. Это решает мою проблему; включая все, кроме одного еще-далеко-далеко-хорошо-иметь. Теперь мне интересно, разместить его здесь или открыть и ответить на новый вопрос?

jcxz100 19.12.2018 01:37

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

dawg 19.12.2018 03:30

@dawg: у меня низкая репутация (13), чтобы написать или проголосовать за вас, но спасибо :)

jcxz100 19.12.2018 12:51
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
7
442
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Данный:

$ 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 регулярное выражение:

$ 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 ...

Ответ принят как подходящий

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

Исходный файл разделить скобки на блоки. 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

Результирующий файл вывод / 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"
}

... и получившийся файл вывод / 0002.txt:

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

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

Другие вопросы по теме