Отфильтровать или передать определенные разделы файла

14

У меня есть входной файл с некоторыми разделами, которые разграничены начальным и конечным тегами, например:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

Я хочу применить преобразование к этому файлу так, чтобы строки X, Y, Z фильтровались через какую-то команду ( nlнапример), но остальные строки проходили без изменений. Обратите внимание, что nl(число строк) накапливает состояние между строками, поэтому это не статическое преобразование, которое применяется к каждой из строк X, Y, Z. ( Изменить : было отмечено, что nlможет работать в режиме, который не требует накопленного состояния, но я просто использую nlв качестве примера, чтобы упростить вопрос. На самом деле команда представляет собой более сложный пользовательский сценарий. Что я действительно ищу for - это общее решение проблемы применения стандартного фильтра к подразделу входного файла )

Вывод должен выглядеть так:

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D

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

Обновление 2 Я изначально не указывал, что должно произойти, если есть еще один раздел, например:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
 @@inline-code-start
line L
line M
line N
@@inline-code-end

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

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D
     1 line L
     2 line M
     3 line N

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

Конец обновления 2

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

#!/usr/bin/bash
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
  echo $line | nl
  else
    # output
    echo $line
  fi
done

С которыми я бегу:

cat test-inline-codify | ./inline-codify

Это не работает, так как каждый вызов nlявляется независимым, поэтому номера строк не увеличиваются:

line A
line B
     1  line X
     1  line Y
     1  line Z
line C
line D

Моей следующей попыткой было использовать fifo:

#!/usr/bin/bash
mkfifo myfifo
nl < myfifo &
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
    echo $line > myfifo
  else
    # output
    echo $line
  fi
done
rm myfifo

Это дает правильный вывод, но в неправильном порядке:

line A
line B
line C
line D
     1  line 1
     2  line 2
     3  line 3

Вероятно, происходит некоторое кеширование.

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

Джеймс Скривен
источник
nlне должен накапливать государство . Посмотрите nl -dи проверьте man/ infoстраницы для получения информации о nl«S разделе разделителем .
mikeserv
NL это просто пример. В моем случае я использую собственный скрипт, а не nl.
Джеймс Скривен,
В этом случае, пожалуйста, уточните, что делает ваш скрипт.
Terdon
Я уточнил в вопросе, что я использую только nlв качестве примера фильтра. Я подумал, что это упростит вопрос, замалчивая детали того, что именно делает фильтр, но я, вероятно, просто вызвал еще большую путаницу. Фактически, я фильтрую подраздел с помощью подсветки кода для собственного статического генератора блогов. Прямо сейчас я использую gnu source-highlight, но это может измениться, и я мог бы добавить больше фильтров, таких как форматтер.
Джеймс Скривен,

Ответы:

7

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


nl

nl, Например, разделяет входной сигнал в логические страницы , как -delimited на два символа секции разделителя . Три вхождения в одной строке указывают начало заголовка , два тела и один нижний колонтитул . Он заменяет любой из них, найденных на входе, пустой строкой на выходе - это единственные пустые строки, которые он печатает

Я изменил ваш пример, включив в него другой раздел и вставив его ./infile. Так это выглядит так:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
@@start
line M
line N
line O
@@end

Затем я запустил следующее:

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end$/@@/'  <infile |
nl -d@@ -ha -bn -w1

nlможно сказать, чтобы накапливать состояние по логическим страницам, но это не по умолчанию. Вместо этого он будет нумеровать строки своего ввода в соответствии со стилями и секциями . Так -haозначает номер всех строк заголовка и -bnозначает отсутствие строк тела - как это начинается в теле .

До тех пор, пока я не узнал об этом, я использовал nlдля любого ввода, но после осознания того, что это nlможет исказить вывод в соответствии с его -dограничителем по умолчанию, \:я научился быть с ним более осторожным и grep -nF ''вместо этого начал использовать для непроверенного ввода. Но еще один урок, извлеченный в тот день, заключался в том, что он nlможет быть очень полезен в других отношениях - например, в этом - если вы просто немного измените его входные данные - как я делаю sedвыше.

ВЫХОД

  line A
  line B

1       line X
2       line Y
3       line Z

  line C
  line D

1       line M
2       line N
3       line O

Вот еще немного о том nl, замечаете ли вы выше, как все строки, кроме пронумерованных, начинаются с пробелов? когдаnl номера строк, он вставляет определенное количество символов в голову каждого. Для этих строк он не -wнумеруется - даже пробелы - он всегда соответствует отступу, вставляя ( idth count + -separator len) * пробелы в начале ненумерованных строк. Это позволяет точно воспроизводить ненумерованный контент, сравнивая его с пронумерованным контентом - и при этом не прилагая особых усилий. Если вы считаете, что nlэто разделит его входные данные на логические разделы, и что вы можете вставить произвольные -sтрэны в начало каждой строки, которую он нумерует, тогда становится довольно легко обработать его вывод:

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end/@@/; t
     s/^\(@@\)\{1,3\}$/& /' <infile |
nl -d@@ -ha -bn -s' do something with the next line!
'

Вышеуказанные отпечатки ...

                                        line A
                                        line B

 1 do something with the next line!
line X
 2 do something with the next line!
line Y
 3 do something with the next line!
line Z

                                        line C
                                        line D

 1 do something with the next line!
line M
 2 do something with the next line!
line N
 3 do something with the next line!
line O

GNU sed

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

sed '/^@@.*start$/!b
     s//nl <<\\@@/;:l;N
     s/\(\n@@\)[^\n]*end$/\1/
Tl;e'  <infile

Выше sedвводятся входные данные в пространстве шаблонов, пока их не будет достаточно, чтобы успешно пройти замену Test и прекратить bранчо обратно к :lабелю. Когда это произойдет, этоe выполняет nlввод, представленный <<здесь как документ для всего остального пространства шаблонов.

Рабочий процесс выглядит так:

  1. /^@@.*start$/!b
    • если ^вся линия $никак !не /соответствует /указанной выше модели, то она bразводят из сценария и autoprinted - так что с этого момента мы работаем только с серией линий , которая началась с рисунком.
  2. s//nl <<\\@@/
    • пустое s//поле /соответствует последнему адресу, который sedпытался найти соответствие, поэтому вместо него эта команда заменяет всю @@.*startстроку nl <<\\@@.
  3. :l;N
    • Команда :определяет метку ветки - здесь я установил один с именем :label. Команда Next добавляет следующую строку ввода к \nпробелу шаблона, за которой следует символ ewline. Это один из немногих способов получить \newline в sedпространстве паттернов - \nперсонаж ewline является верным разделителем для sedдер, который делал это некоторое время.
  4. s/\(\n@@\)[^\n]*end$/\1/
    • эта s///операция может быть успешной только после того, как встречается начало, и только в первом последующем появлении конечной строки. Он будет действовать только на пространство паттернов, в котором \nсразу же за конечной ewline будет отмечен @@.*endсамый конец $паттерна. Когда он действует, он заменяет всю совпавшую строку \1первой \(группой \)или \n@@.
  5. Tl
    • то Tкоманда Текущей ветви к метке (если имеется) , если успешная замена не произошла с момента последнего ввод линия затащила шаблон (как и я ш / N) . Это означает, что каждый раз, когда \newline добавляется в пространство шаблонов, которое не соответствует вашему конечному разделителю, команда Test завершается неудачно и возвращается к :label, что приводит к sedвытягиванию Nстроки ext и повторению цикла до успешного завершения .
  6. e

    • Когда замена для конечного матча успешно и сценарий не филиальную назад для неисправного TЭСТА, sedбудет execute команды , которая looks , как это:

      nl <<\\@@\nline X\nline Y\nline Z\n@@$

Вы можете убедиться в этом сами, отредактировав последнюю строку там, чтобы она выглядела так Tl;l;e.

Это печатает:

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
     1  line M
     2  line N
     3  line O

while ... read

Последний способ сделать это, и, возможно, самый простой, - это использовать while readцикл, но не без причины. Оболочка - (особенно bashоболочка) - обычно ужасна при обработке ввода в больших количествах или в устойчивых потоках. Это также имеет смысл - работа оболочки заключается в том, чтобы обрабатывать ввод за символом и вызывать другие команды, которые могут обрабатывать больше.

Но важно то, что его роль заключается в том, что оболочка не должна read перегружать ввод - она ​​указана для того, чтобы не буферизовать ввод или вывод до такой степени, что она потребляет так много или недостаточно ретранслирует во времени, чтобы не вызывать недостающие команды, которые она вызывает - к байту. Так readчто это отличный тест ввода - для получения returnинформации о том, остался ли ввод, и вам нужно вызвать следующую команду, чтобы прочитать его, но в целом это не лучший способ.

Однако вот пример того, как можно использовать read и другие команды для обработки ввода синхронно:

while   IFS= read -r line        &&
case    $line in (@@*start) :;;  (*)
        printf %s\\n "$line"
        sed -un "/^@@.*start$/q;p";;
esac;do sed -un "/^@@.*end$/q;=;p" |
        paste -d: - -
done    <infile

Первое, что происходит для каждой итерации, - это readвытягивание строки. Если он успешен, это означает, что цикл еще не достиг EOF, и, таким образом, caseон соответствует начальному разделителю, и doблок немедленно выполняется. Остальное printfпечатает $lineоноread и sedназывается.

sedбудет pнабирать каждую строку, пока не встретит начальный маркер - когда он qполностью использует ввод. Переключатель -unbuffered необходим для GNU, sedпотому что он может жадно буферизовать в противном случае, но - согласно спецификации - другой POSIXsed должны работать без какого-либо специального рассмотрения - при условии, что <infileэто обычный файл.

При первом sed qиспользовании оболочка выполняет doблок цикла, который вызывает другой, sedкоторый печатает каждую строку, пока не встретит маркер конца . Он передает свой вывод paste, потому что он печатает номера строк, каждый на своей строке. Как это:

1
line M
2
line N
3
line O

pasteзатем вставляет их вместе в :символы, и весь вывод выглядит так:

line A
line B
1:line X
2:line Y
3:line Z
line C
line D
1:line M
2:line N
3:line O

Это всего лишь примеры - все может быть сделано здесь либо в тесте, либо в блоках do, но первая утилита не должна потреблять слишком много входных данных.

Все задействованные утилиты читают один и тот же ввод - и печатают свои результаты - каждая по-своему. Такого рода вещи могут быть трудно получить навык - потому что различные утилиты будут помещать в буфер больше , чем другие , - но вы можете вообще полагаться на dd, headи sedделать правильные вещи (хотя, для GNU sed, вам нужно CLI-переключатель) и Вы всегда должны быть в состоянии положиться, readпотому что это, по своей природе, очень медленно . И именно поэтому вышеуказанный цикл вызывает его только один раз на входной блок.

mikeserv
источник
Я протестировал второй sedпример, который вы дали, и он работает, но у меня ДЕЙСТВИТЕЛЬНО возникают проблемы с синтаксисом. (мой sed довольно слабый и обычно ограничивается s / findthis / replacethis / g. Мне придется приложить усилия, чтобы сесть и по-настоящему понять sed.)
James Scriven
@JamesScriven - я просто отредактировал, чтобы объяснить это лучше. Дайте мне знать, если это не поможет. Я также сильно изменил команду - теперь она более мелкая, более разумная.
mikeserv
4

Одна возможность - сделать это с помощью текстового редактора vim. Он может передавать произвольные разделы через команды оболочки.

Один из способов сделать это - по номерам строк, используя :4,6!nl . Эта команда ex будет запускать nl в строках 4-6 включительно, достигая того, что вы хотите на вашем примере ввода.

Другой, более интерактивный способ - выбрать подходящие строки, используя режим выбора линий (shift-V) и клавиши со стрелками или поиск, а затем с помощью :!nl. Полная последовательность команд для вашего примера ввода может быть

/@@inline-code-start
jV/@@inline-code-end
k:!nl

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

Если вы не знакомы с vi (m), вы должны как минимум знать, что после этих изменений вы можете сохранить файл, используя :wq.

marcelm
источник
Да, Вим потрясающе! Но я в этом случае ищу сценарий решения.
Джеймс Скривен,
@JamesScriven, любой, кто говорит vim, не может быть написан на скрипте в недостаточно определенном. Сначала создайте каталог проекта и в этом каталоге скопируйте все файлы запуска vim из вашего домашнего каталога (ln -s работает нормально, за исключением .vimrc, который мы собираемся изменить, и .viminfo, который может быть заполнен шумом). Добавьте определение функции, которая будет выполнять эту работу, в новый файл .vimrc, а затем вызовите vim as HOME=$(pwd) vim -c 'call Mf()' f. Если вы используете xargs, вы можете использовать gvim на выделенном сервере xserver, чтобы не повредить ваш tty (vnc не зависит от видеокарты и может отслеживаться).
hildred
@hildred Хммм ... Разве я не могу просто использовать [XSendEvent] ( tronche.com/gui/x/xlib/event-handling/XSendEvent.html ) для имитации щелчков мышью по vim?
Джеймс Скривен
2

Самое простое решение, которое я могу придумать, - это не использовать, nlа подсчитывать строки самостоятельно:

#!/usr/bin/env bash
while read line
do
    if [[ $line == @@inline-code-start* ]]
    then
        active=true
    elif [[ $line == @@inline-code-end* ]]
    then
        active=false
    elif [[ $active = true ]]
    then
        ## Count the line number
        let num++;
        printf "\t%s %s\n" "$num" "$line"
    else
        # output
        printf "%s\n" "$line"
    fi
done

Затем вы запускаете его в файле:

$ foo.sh < file
line A
line B
    1 line X
    2 line Y
    3 line Z
line C
line D
Тердон
источник
Спасибо, Тердон. Я обновил вопрос, чтобы уточнить, что я ищу общее решение для фильтрации подраздела ввода, а не конкретный пример нумерации строк. возможно, лучшим примером команды была бы «tac» (обратные линии)
Джеймс Скривен,
2

Если ваша цель состоит в том, чтобы отправить весь блок кода одному экземпляру процесса, вы можете накапливать строки и задерживать конвейер до достижения конца блока кода:

#!/bin/bash

acc=""

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    acc=""
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    # Act on entire block of code
    echo "${acc:1}" | nl  # Chops off first leading new-line character using ${VAR:1}
  elif [[ $active = true ]]
  then
    acc=$( printf "%s\n%s" "$acc" "$line" )
  else
    # output
    echo $line
  fi
done

Это приводит к следующему для входного файла, который повторяет контрольный пример три раза:

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D

Для того, чтобы сделать что - то еще с блоком кода, например , обратное , а затем номер, только трубу это через что - то другое: echo -E "${acc:1}" | tac | nl. Результат:

line A
line B
     1  line Z
     2  line Y
     3  line X
line C
line D

Или количество слов echo -E "${acc:1}" | wc:

line A
line B
      3       6      21
line C
line D
Supr
источник
2

Редактировать добавил опцию, чтобы определить пользовательский фильтр

#!/usr/bin/perl -s
use IPC::Open2;
our $p;
$p = "nl" unless $p;    ## default filter

$/ = "\@\@inline-code-end\n";
while(<>) { 
   chomp;
   s/\@\@inline-code-start\n(.*)/pipeit($1,$p)/se;
   print;
}

sub pipeit{my($text,$pipe)=@_;
  open2(my $R, my $W,$pipe) || die("can open2");
  local $/ = undef;
  print $W $text;
  close $W;
  return <$R>;
}

По умолчанию фильтр имеет значение «nl». Чтобы изменить фильтр, используйте опцию «-p» с некоторой командой, предоставленной пользователем:

codify -p="wc" file

или

codify -p="sed -e 's@^@ ║ @; 1s@^@ ╓─\n@; \$s@\$@\n ╙─@'" file

Этот последний фильтр выведет:

line A
line B
 ╓─
  line X
  line Y
  line Z
 ╙─
line C
line D

Обновление 1 Использование IPC :: Open2 имеет проблемы с масштабированием: если размер буфера превышен, он может блокироваться. (в моей машине размер буфера трубы, если 64K соответствует 10_000 x "линия Y").

Если нам нужны большие вещи (нам нужно больше 10000 «линия Y»):

(1) установить и использовать use Forks::Super 'open2';

(2) или замените функцию pipeit следующим образом:

sub pipeit{my($text,$pipe)=@_;
  open(F,">","/tmp/_$$");
  print F $text;
  close F;
  my $out = `$pipe < /tmp/_$$ `;
  unlink "/tmp/_$$";
  return $out;
}
JJoao
источник
Это действительно круто. Я предполагаю, что хитрости заключаются в том, что вы не обрабатываете построчно (путем повторного определения $/и sфлага), а используете eфлаг для фактического вызова внешней команды. Мне очень нравится второй (ascii art) пример!
Джеймс Скривен
Однако я заметил, что в этом подразделе это не выходит за рамки нескольких тысяч строк. Я подозреваю, что это связано с трактовкой подраздела как одного большого блока текста.
Джеймс Скривен
Благодарю. Да: `/ e` = eval; /s= ("." означает (.|\n)); $/переопределяет регистр разделителя.
Жоао
@JamesScriven, вы правы (канал блокируется). Позвольте мне проверить, что происходит ...
JJoao
@JamesScriven, пожалуйста, смотрите мое обновление ...
JJoao
1

Это работа для awk.

#!/usr/bin/awk -f
$0 == "@@inline-code-start" {pipe = 1; next}
$0 == "@@inline-code-end" {pipe = 0; close("nl"); next}
pipe {print | "nl"}
!pipe {print}

Когда скрипт видит маркер запуска, он отмечает, что должен начать пайпинг nl. Когда pipeпеременная имеет значение true (отличное от нуля), выходные данные передаются в nlкоманду; когда переменная ложна (не установлена ​​или равна нулю), вывод печатается напрямую. Переданная по конвейеру команда разветвляется при первом обнаружении конструкции канала для каждой командной строки. Последующие оценки оператора трубы с той же строкой повторно используют существующую трубу; другое строковое значение создаст другой канал. closeФункция закрывает трубу для данной командной строки.


По сути, это та же логика, что и в вашем сценарии оболочки с использованием именованного канала, но гораздо проще разобрать, и логика закрытия выполнена правильно. Вам нужно закрыть трубу в нужное время, чтобы сделать nlкоманду выхода, очистив ее буферы. Ваш скрипт на самом деле закрывает канал слишком рано: канал закрывается, как только echo $line >myfifoзавершается выполнение первого . Однако nlкоманда видит конец файла только в том случае, если она получает интервал времени до следующего выполнения скрипта echo $line >myfifo. Если у вас большой объем данных или вы добавите sleep 1после записи myfifo, вы увидите, чтоnl обрабатывает только первую строку или первую быструю связку строк, затем он завершается, потому что видит конец своего ввода.

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

nl <myfifo &
exec 3>&1
while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    exec >myfifo
  elif [[ $line == @@inline-code-end* ]]
  then
    exec >&3
  else
    printf '%s\n' "$line"
  fi
done

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

Если вы делаете это, вы можете использовать конвейер, а не именованный канал.

while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    while IFS= read -r line && [[ $line != @@inline-code-end* ]] do
      printf '%s\n' "$line"
    done | nl
  else
    printf '%s\n' "$line"
  fi
done
Жиль "ТАК - перестань быть злым"
источник
Ваше решение awk действительно приятно! Я думаю, что это, безусловно, самое краткое (но очень удобочитаемое) решение. Гарантируется ли поведение awk повторного использования канала для nl, или awk может решить: «Эй, ты уже достаточно выполнил… Я собираюсь закрыть этот канал и открыть новый»? Ваше «конвейерное» решение тоже очень приятно. Я изначально не принимал во внимание подход со встроенными циклами while, так как думал, что это может немного сбивать с толку, но я думаю, что у вас получилось здорово. Перед точкой нет точки с запятойdo . (У меня нет представителя, чтобы сделать небольшое редактирование.)
Джеймс Скривен
1
... Я не смог заставить работать ваше именованное решение. Кажется, есть условие гонки, такое, что раздел, переданный по каналу nl, иногда теряется полностью. Кроме того, если есть второй раздел @@ inline-code-start / end, он всегда теряется.
Джеймс Скривен
0

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

tr "[[:lower:]]" "[[:upper:]]"

т.е. преобразовать текст в верхний регистр; Итак, для ввода

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

Вы хотите вывод

line A
line B
LINE X
LINE Y
LINE Z
line C
line D

Вот мое первое приближение решения:

#!/bin/sh
> file0
> file1
active=0
nl -ba "$@" | while IFS= read -r line
do
        case "$line" in
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-start")
                active=1
                ;;
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-end")
                active=0
                ;;
            (*)
                printf "%s\n" "$line" >> file$active
        esac
done
(cat file0; tr "[[:lower:]]" "[[:upper:]]" < file1) | sort | sed 's/^[ 0-9]\{6\}        //'

где пробелы перед @@ строками и рядом с концом последней строки являются символами табуляции. Обратите внимание, что я использую nl в своих целях . (Конечно, я делаю это, чтобы решить вашу проблему, но не для того, чтобы получить вывод с номерами.)

Это нумерует строки ввода, так что мы можем разбить его на маркеры разделов и узнать, как его собрать позже. Основная часть цикла основана на вашей первой попытке, принимая во внимание тот факт, что маркеры разделов имеют номера строк. Он разбивает входные данные на два файла: file0 (неактивный; не в разделе) и file1(активный; в разделе). Вот как они выглядят для вышеуказанного ввода:

file0:
     1  line A
     2  line B
     8  line C
     9  line D

file1:
     4  line X
     5  line Y
     6  line Z

Затем мы запускаем file1(что является объединением всех строк в сечении) через фильтр капитализации; объединить это с нефильтрованными линиями вне сечения; сортировать, чтобы вернуть их в исходный порядок; а затем уберите номера строк. Это производит вывод, показанный около вершины моего ответа.

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

Скотт
источник
nlуже делает большую часть работы там - это то, для чего его -dопция elimiter.
mikeserv
0

Сценарий оболочки, который использует sed для вывода фрагментов не демаркированных строк и подачи выделенных фрагментов строк в программу фильтра:

#!/bin/bash

usage(){
    echo "  usage: $0 <input file>"
}

# Check input file
if [ ! -f "$1" ]; then
    usage
    exit 1
fi

# Program to use for filtering
# e.g. FILTER='tr X -'
FILTER='./filter.sh'

# Generate arrays with starting/ending line numbers of demarcators
startposs=($(grep -n '^@@inline-code-start$' "$1" | cut -d: -f1))
endposs=($(grep -n '^@@inline-code-end$' "$1" | cut -d: -f1))

nums=${#startposs[*]}
nume=${#endposs[*]}

# Verify both line number arrays have the same number of elements
if (($nums != $nume)); then
    echo "Tag mismatch"
    exit 2
fi

lastline=1
i=0
while ((i < nums)); do
    # Exclude lines with code demarcators
    sprev=$((${startposs[$i]} - 1))
    snext=$((${startposs[$i]} + 1))
    eprev=$((${endposs[$i]} - 1))

    # Don't run this bit if the first demarcator is on the first line
    if ((sprev > 1)); then
        # Output lines leading up to start demarcator
        sed -n "${lastline},${sprev} p" "$1"
    fi

    # Filter lines between demarcators
    sed -n "${snext},${eprev} p" "$1" | $FILTER

    lastline=$((${endposs[$i]} + 1))
    let i++
done

# Output lines (if any) following last demarcator
sed -n "${lastline},$ p" "$1"

Я написал этот скрипт в файл detagger.sh и использовал его так: ./detagger.sh infile.txt . Я создал отдельный файл filter.sh для имитации функции фильтрации в вопросе:

#!/bin/bash
awk '{ print "\t" NR " " $0}'

Но операция фильтрации может быть изменена в коде.

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

изматывать
источник
-1

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

Мое решение:

(Я использую nlтолько в качестве примера фильтра)

#!/usr/bin/bash

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    tmpfile=$(mktemp)
    trap "rm -f $tmpfile" EXIT
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    <$tmpfile nl
    rm $tmpfile
  elif [[ $active = true ]]
  then
    echo $line >> $tmpfile
  else
    echo $line
  fi
done

Я бы предпочел не иметь дело с управлением временными файлами, но я понимаю, что переменные оболочки могут иметь довольно низкие ограничения по размеру, и я не знаю ни одной конструкции bash, которая бы работала как временный файл, но исчезала автоматически, когда процесс заканчивается

Джеймс Скривен
источник
Я думал , что вы хотели , чтобы быть в состоянии «состояние накопления через линию», так, например, с использованием тестовых данных Майки, линией M, Nи Oбуду пронумерован 4, 5и 6. Это не делает этого. Мой ответ так (кроме того факта, что в его нынешнем воплощении он не работает nlкак фильтр). Если этот ответ дает желаемый результат, то что вы имели в виду под «накапливать состояние через строки»? Вы имели в виду, что хотите сохранить состояние только через каждый раздел, но не между (через) разделами? (Почему вы не включили пример с несколькими разделами в свой вопрос?)
Скотт,
@ Скотт - использовать, nl -pчтобы получить M,N,O==4,5,6.
mikeserv
Я обновил вопрос, чтобы уточнить, что меня интересует только поддержание состояния в подразделе, хотя я думаю, что другая интерпретация одинаково интересна.
Джеймс Скривен