Фільтруйте або передайте певні розділи файлу


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

Наступною моєю спробою було використовувати фіфо:

#!/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, наприклад, розділяє вхід на логічні сторінки, як -dусунуто роздільником двох розділів . Три події в рядку самі по собі вказують на початок заголовка , два тіла і один колонтитул . Він замінює будь-який з цих даних, що знаходяться на вході, порожнім рядком на виході - що це єдині порожні рядки, які він коли-небудь друкує

Я змінив ваш приклад, щоб включити ще один розділ і вкласти його ./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це не ваша цільова програма, то GNU sedможе eвиконувати довільну команду оболонки для вас залежно від відповідності.

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

Вище sedзбирає вхід у простір шаблону, поки його не вистачить для успішного проходження заміщення Tі перестаньте bрухатися назад до :lабеля. Коли він це робить, він eвиконує nlз введенням, представленим як <<тут-документ, для всіх решти його простору шаблонів.

Робочий процес такий:

  1. /^@@.*start$/!b
    • якщо ^вся лінія $ніяк !НЕ /відповідає /зазначеній вище моделі, то вона bрозводять з сценарію і autoprinted - так що з цього моменту ми працюємо тільки з серією ліній , яка почалася з малюнком.
  2. s//nl <<\\@@/
    • порожнє s//поле /стоїть за останньою адресою, яка sedнамагалася відповідати, тому ця команда заміщає весь @@.*startрядок nl <<\\@@замість цього.
  3. :l;N
    • :Команда визначає мітку філії - тут я поставив один з ім'ям :lАвеля. Команда Next додає наступний рядок вводу до простору візерунка з подальшим \nсимволом ewline. Це один з небагатьох способів отримати \nеуліну в sedпросторі візерунка - \nперсонаж ewline - це справжній роздільник для sedдер, який це робив вже деякий час.
  4. s/\(\n@@\)[^\n]*end$/\1/
    • це s///встановлення може бути успішним лише після початку запуску і лише при першому наступному появі кінцевої лінії. Він буде діяти лише на просторі шаблону, в якому за кінцевою \nлініями одразу слідує @@.*endмаркування самого кінця $простору візерунка. Коли він дійде, він замінює весь збігається рядок \1першою \(групою \), або \n@@.
  5. Tl
    • команда Test відгалужується до мітки (якщо вона надана), якщо успішної заміни не відбулося з моменту останнього введення рядка вводу в простір шаблону (як я це роблю w / N) . Це означає, що щоразу, коли \newline додається до простору шаблону, який не відповідає вашому кінцевому розмежувачу, команда Test не вдається і відхиляється назад до :lабелі, що призводить до 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оскільки він може буфер , а жадібність інакше, але - в відповідності зі специфікацією - інший POSIX seds повинен працювати без будь - якого спеціального розгляду - до тих пір , як <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

Це лише приклади - тут можна зробити все, що завгодно, або в тесті, або в блоках, але перша утиліта не повинна використовувати занадто багато вкладних даних.

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


Я перевірив другий sedприклад, який ви подали, і він спрацьовує, але у мене ВЗАЄМО проблеми з виходом синтаксису. (мій sed є досить слабким і, як правило, обмежується s / findthis / substituthis / g. Мені доведеться докласти зусиль, щоб сісти і по-справжньому зрозуміти sed.)
Джеймс Скрівен,

@JamesScriven - я просто редагував, щоб пояснити це краще. Повідомте мене, якщо це не допоможе. Я також сильно змінив команду - це вже в менших, більш розумних частинах.
mikeserv

4

Одна з можливостей - це зробити за допомогою редактора тексту vim. Він може передавати довільні секції через команди оболонки.

Один із способів зробити це - за номерами рядків, використовуючи :4,6!nl. Ця колишня команда виконуватиме nl у рядках 4-6 включно, досягаючи того, що потрібно на прикладі введення.

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

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

Це не дуже підходить для автоматизації (відповіді за допомогою напр. Sed краще для цього), але для разових редагувань дуже корисно не вдаватися до 20-рядкових оболонок.

Якщо ви не знайомі з vi (m), вам слід принаймні знати, що після цих змін ви можете зберегти файл за допомогою :wq.


Так, vim - приголомшливий! Але я в цьому випадку шукаю сценарій для вирішення проблем.
Джеймс Скрівен

@JamesScriven, будь-хто, хто каже, що vim не може бути написаний недостатньо. Спочатку створіть каталог проектів і в цьому каталозі скопіюйте всі файли запуску vim із домашнього каталогу (ln -s працює нормально, за винятком .vimrc, який ми збираємося змінити, і .viminfo, який може бути наповнений шумом). Додайте визначення функції, яке буде виконувати роботу, у новий .vimrc файл, а потім викликайте vim як HOME=$(pwd) vim -c 'call Mf()' f. Якщо ви використовуєте xargs, можливо, ви хочете використовувати gvim на спеціальному xserver, щоб не пошкодити ваш tty (vnc не залежить від відеокарти та може бути відстежено).
hildred

@hildred Hmmm ... Чи не можу я просто використовувати [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

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 х "рядок Y").

Якщо нам потрібні більші речі (чи потрібно більше 10000 "рядків Y"):

(1) встановити та використовувати use Forks::Super 'open2';

(2) або замінити функціональну трубу на:

sub pipeit{my($text,$pipe)=@_;
  open(F,">","/tmp/_$$");
  print F $text;
  close F;
  my $out = `$pipe < /tmp/_$$ `;
  unlink "/tmp/_$$";
  return $out;
}

Це дійсно круто. Я думаю, що прийоми полягають у тому, що ви не обробляєте по черзі (шляхом повторного визначення $/та sпрапорця), а використовуєте eпрапор для здійснення фактичного виклику зовнішньої команди. Мені дуже подобається другий (ascii art) приклад!
Джеймс Скривен

Я все-таки помітив, що це, здається, не перевищує декількох тисяч рядків у підрозділі. Я підозрюю, що це стосується трактування підрозділу як одного великого блоку тексту.
Джеймс Скривен

Спасибі. Так: `/ e` = eval; /s= ("." означає (.|\n)); $/перевизначає роздільник реєстру.
JJoao

@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змінна вірна (ненульова), висновок передається в 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, чи може розбудувати рішення: "Ей, ти зараз достатньо проклав трубку. Я збираюся закрити цю трубу та відкрити нову". Ваше "конвеєрне" рішення теж дуже приємне. Я спочатку знизив підхід із вбудованими циклами, оскільки я вважав, що це може бути трохи заплутано, але я думаю, що у вас є чудово. Перед точкою відсутня крапка з комою 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виконується більша частина роботи - саме для цього і є її варіант усунення.
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 (але зі змінною оболонки замість файлу temp). Також мені дуже подобається ідея використання 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, яка працювала б як temp-файл, але автоматично зникає, коли процес закінчується.


Я думав , що ви хотіли , щоб бути в змозі «стан накопичення через лінію», так, наприклад, з використанням тестових даних Майки, лінією M, Nі Oбуду пронумерований 4, 5і 6. Це не робить цього. Моя відповідь справді (окрім того, що в теперішньому втіленні вона не працює nlяк фільтр). Якщо ця відповідь дає вам потрібний вихід, то що ви мали на увазі під «накопиченням стану по лініях»? Ви мали на увазі, що хочете зберегти стан лише через кожен розділ, але не між (через) розділи? (Чому ви не поставили приклад із кількох розділів у своєму питанні?)
Скотт

@Scott - використовувати nl -pдля отримання M,N,O==4,5,6.
mikeserv

Я оновив питання, щоб уточнити, що мене цікавить лише підтримка стану в підрозділі, хоча я думаю, що інше тлумачення не менш цікаве.
Джеймс Скривен
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.