У меня есть входной файл с некоторыми разделами, которые разграничены начальным и конечным тегами, например:
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 разделе разделителем .nl
в качестве примера фильтра. Я подумал, что это упростит вопрос, замалчивая детали того, что именно делает фильтр, но я, вероятно, просто вызвал еще большую путаницу. Фактически, я фильтрую подраздел с помощью подсветки кода для собственного статического генератора блогов. Прямо сейчас я использую gnusource-highlight
, но это может измениться, и я мог бы добавить больше фильтров, таких как форматтер.Ответы:
Я согласен с вами - это , вероятно , является общая проблема. У некоторых общих утилит есть некоторые средства для обработки этого, все же.
nl
nl
, Например, разделяет входной сигнал в логические страницы , как-d
elimited на два символа секции разделителя . Три вхождения в одной строке указывают начало заголовка , два тела и один нижний колонтитул . Он заменяет любой из них, найденных на входе, пустой строкой на выходе - это единственные пустые строки, которые он печатаетЯ изменил ваш пример, включив в него другой раздел и вставив его
./infile
. Так это выглядит так:Затем я запустил следующее:
nl
можно сказать, чтобы накапливать состояние по логическим страницам, но это не по умолчанию. Вместо этого он будет нумеровать строки своего ввода в соответствии со стилями и секциями . Так-ha
означает номер всех строк заголовка и-bn
означает отсутствие строк тела - как это начинается в теле .До тех пор, пока я не узнал об этом, я использовал
nl
для любого ввода, но после осознания того, что этоnl
может исказить вывод в соответствии с его-d
ограничителем по умолчанию,\:
я научился быть с ним более осторожным иgrep -nF ''
вместо этого начал использовать для непроверенного ввода. Но еще один урок, извлеченный в тот день, заключался в том, что онnl
может быть очень полезен в других отношениях - например, в этом - если вы просто немного измените его входные данные - как я делаюsed
выше.ВЫХОД
Вот еще немного о том
nl
, замечаете ли вы выше, как все строки, кроме пронумерованных, начинаются с пробелов? когдаnl
номера строк, он вставляет определенное количество символов в голову каждого. Для этих строк он не-w
нумеруется - даже пробелы - он всегда соответствует отступу, вставляя ( idth count +-s
eparator len) * пробелы в начале ненумерованных строк. Это позволяет точно воспроизводить ненумерованный контент, сравнивая его с пронумерованным контентом - и при этом не прилагая особых усилий. Если вы считаете, чтоnl
это разделит его входные данные на логические разделы, и что вы можете вставить произвольные-s
трэны в начало каждой строки, которую он нумерует, тогда становится довольно легко обработать его вывод:Вышеуказанные отпечатки ...
GNU
sed
Если
nl
это не ваше целевое приложение, тогда GNUsed
можетe
выполнить для вас произвольную команду оболочки в зависимости от соответствия.Выше
sed
вводятся входные данные в пространстве шаблонов, пока их не будет достаточно, чтобы успешно пройти заменуT
est и прекратитьb
ранчо обратно к:l
абелю. Когда это произойдет, этоe
выполняетnl
ввод, представленный<<
здесь как документ для всего остального пространства шаблонов.Рабочий процесс выглядит так:
/^@@.*start$/!b
^
вся линия$
никак!
не/
соответствует/
указанной выше модели, то онаb
разводят из сценария и autoprinted - так что с этого момента мы работаем только с серией линий , которая началась с рисунком.s//nl <<\\@@/
s//
поле/
соответствует последнему адресу, которыйsed
пытался найти соответствие, поэтому вместо него эта команда заменяет всю@@.*start
строкуnl <<\\@@
.:l;N
:
определяет метку ветки - здесь я установил один с именем:l
abel. КомандаN
ext добавляет следующую строку ввода к\n
пробелу шаблона, за которой следует символ ewline. Это один из немногих способов получить\n
ewline вsed
пространстве паттернов -\n
персонаж ewline является верным разделителем дляsed
дер, который делал это некоторое время.s/\(\n@@\)[^\n]*end$/\1/
s///
операция может быть успешной только после того, как встречается начало, и только в первом последующем появлении конечной строки. Он будет действовать только на пространство паттернов, в котором\n
сразу же за конечной ewline будет отмечен@@.*end
самый конец$
паттерна. Когда он действует, он заменяет всю совпавшую строку\1
первой\(
группой\)
или\n@@
.Tl
T
команда Текущей ветви к метке (если имеется) , если успешная замена не произошла с момента последнего ввод линия затащила шаблон (как и я ш /N
) . Это означает, что каждый раз, когда\n
ewline добавляется в пространство шаблонов, которое не соответствует вашему конечному разделителю, командаT
est завершается неудачно и возвращается к:l
abel, что приводит кsed
вытягиваниюN
строки ext и повторению цикла до успешного завершения .e
Когда замена для конечного матча успешно и сценарий не филиальную назад для неисправного
T
ЭСТА,sed
будетe
xecute команды , котораяl
ooks , как это:Вы можете убедиться в этом сами, отредактировав последнюю строку там, чтобы она выглядела так
Tl;l;e
.Это печатает:
while ... read
Последний способ сделать это, и, возможно, самый простой, - это использовать
while read
цикл, но не без причины. Оболочка - (особенноbash
оболочка) - обычно ужасна при обработке ввода в больших количествах или в устойчивых потоках. Это также имеет смысл - работа оболочки заключается в том, чтобы обрабатывать ввод за символом и вызывать другие команды, которые могут обрабатывать больше.Но важно то, что его роль заключается в том, что оболочка не должна
read
перегружать ввод - она указана для того, чтобы не буферизовать ввод или вывод до такой степени, что она потребляет так много или недостаточно ретранслирует во времени, чтобы не вызывать недостающие команды, которые она вызывает - к байту. Такread
что это отличный тест ввода - для полученияreturn
информации о том, остался ли ввод, и вам нужно вызвать следующую команду, чтобы прочитать его, но в целом это не лучший способ.Однако вот пример того, как можно использовать
read
и другие команды для обработки ввода синхронно:Первое, что происходит для каждой итерации, - это
read
вытягивание строки. Если он успешен, это означает, что цикл еще не достиг EOF, и, таким образом,case
он соответствует начальному разделителю, иdo
блок немедленно выполняется. Остальноеprintf
печатает$line
оноread
иsed
называется.sed
будетp
набирать каждую строку, пока не встретит начальный маркер - когда онq
полностью использует ввод. Переключатель-u
nbuffered необходим для GNU,sed
потому что он может жадно буферизовать в противном случае, но - согласно спецификации - другой POSIXsed
должны работать без какого-либо специального рассмотрения - при условии, что<infile
это обычный файл.При первом
sed
q
использовании оболочка выполняетdo
блок цикла, который вызывает другой,sed
который печатает каждую строку, пока не встретит маркер конца . Он передает свой выводpaste
, потому что он печатает номера строк, каждый на своей строке. Как это:paste
затем вставляет их вместе в:
символы, и весь вывод выглядит так:Это всего лишь примеры - все может быть сделано здесь либо в тесте, либо в блоках do, но первая утилита не должна потреблять слишком много входных данных.
Все задействованные утилиты читают один и тот же ввод - и печатают свои результаты - каждая по-своему. Такого рода вещи могут быть трудно получить навык - потому что различные утилиты будут помещать в буфер больше , чем другие , - но вы можете вообще полагаться на
dd
,head
иsed
делать правильные вещи (хотя, для GNUsed
, вам нужно CLI-переключатель) и Вы всегда должны быть в состоянии положиться,read
потому что это, по своей природе, очень медленно . И именно поэтому вышеуказанный цикл вызывает его только один раз на входной блок.источник
sed
пример, который вы дали, и он работает, но у меня ДЕЙСТВИТЕЛЬНО возникают проблемы с синтаксисом. (мой sed довольно слабый и обычно ограничивается s / findthis / replacethis / g. Мне придется приложить усилия, чтобы сесть и по-настоящему понять sed.)Одна возможность - сделать это с помощью текстового редактора vim. Он может передавать произвольные разделы через команды оболочки.
Один из способов сделать это - по номерам строк, используя
:4,6!nl
. Эта команда ex будет запускать nl в строках 4-6 включительно, достигая того, что вы хотите на вашем примере ввода.Другой, более интерактивный способ - выбрать подходящие строки, используя режим выбора линий (shift-V) и клавиши со стрелками или поиск, а затем с помощью
:!nl
. Полная последовательность команд для вашего примера ввода может бытьЭто не очень подходит для автоматизации (лучше использовать ответы с использованием, например, sed), но для одноразовых изменений очень полезно не прибегать к 20-строчным скриптам.
Если вы не знакомы с vi (m), вы должны как минимум знать, что после этих изменений вы можете сохранить файл, используя
:wq
.источник
HOME=$(pwd) vim -c 'call Mf()' f
. Если вы используете xargs, вы можете использовать gvim на выделенном сервере xserver, чтобы не повредить ваш tty (vnc не зависит от видеокарты и может отслеживаться).Самое простое решение, которое я могу придумать, - это не использовать,
nl
а подсчитывать строки самостоятельно:Затем вы запускаете его в файле:
источник
Если ваша цель состоит в том, чтобы отправить весь блок кода одному экземпляру процесса, вы можете накапливать строки и задерживать конвейер до достижения конца блока кода:
Это приводит к следующему для входного файла, который повторяет контрольный пример три раза:
Для того, чтобы сделать что - то еще с блоком кода, например , обратное , а затем номер, только трубу это через что - то другое:
echo -E "${acc:1}" | tac | nl
. Результат:Или количество слов
echo -E "${acc:1}" | wc
:источник
Редактировать добавил опцию, чтобы определить пользовательский фильтр
По умолчанию фильтр имеет значение «nl». Чтобы изменить фильтр, используйте опцию «-p» с некоторой командой, предоставленной пользователем:
или
Этот последний фильтр выведет:
Обновление 1 Использование IPC :: Open2 имеет проблемы с масштабированием: если размер буфера превышен, он может блокироваться. (в моей машине размер буфера трубы, если 64K соответствует 10_000 x "линия Y").
Если нам нужны большие вещи (нам нужно больше 10000 «линия Y»):
(1) установить и использовать
use Forks::Super 'open2';
(2) или замените функцию pipeit следующим образом:
источник
$/
иs
флага), а используетеe
флаг для фактического вызова внешней команды. Мне очень нравится второй (ascii art) пример!/s
= ("." означает(.|\n)
);$/
переопределяет регистр разделителя.Это работа для awk.
Когда скрипт видит маркер запуска, он отмечает, что должен начать пайпинг
nl
. Когдаpipe
переменная имеет значение true (отличное от нуля), выходные данные передаются вnl
команду; когда переменная ложна (не установлена или равна нулю), вывод печатается напрямую. Переданная по конвейеру команда разветвляется при первом обнаружении конструкции канала для каждой командной строки. Последующие оценки оператора трубы с той же строкой повторно используют существующую трубу; другое строковое значение создаст другой канал.close
Функция закрывает трубу для данной командной строки.По сути, это та же логика, что и в вашем сценарии оболочки с использованием именованного канала, но гораздо проще разобрать, и логика закрытия выполнена правильно. Вам нужно закрыть трубу в нужное время, чтобы сделать
nl
команду выхода, очистив ее буферы. Ваш скрипт на самом деле закрывает канал слишком рано: канал закрывается, как толькоecho $line >myfifo
завершается выполнение первого . Однакоnl
команда видит конец файла только в том случае, если она получает интервал времени до следующего выполнения скриптаecho $line >myfifo
. Если у вас большой объем данных или вы добавитеsleep 1
после записиmyfifo
, вы увидите, чтоnl
обрабатывает только первую строку или первую быструю связку строк, затем он завершается, потому что видит конец своего ввода.Используя вашу структуру, вам нужно будет держать трубу открытой, пока она вам больше не понадобится. Вам нужно иметь одно перенаправление вывода в канал.
(Я также воспользовался возможностью, чтобы добавить правильные кавычки и тому подобное - см. Почему мой сценарий оболочки задыхается от пробелов или других специальных символов? )
Если вы делаете это, вы можете использовать конвейер, а не именованный канал.
источник
do
. (У меня нет представителя, чтобы сделать небольшое редактирование.)Хорошо, первый раз; Я понимаю, что вы не ищете способ нумерации строк в разделах вашего файла. Поскольку вы не привели фактический пример того, каким может быть ваш фильтр (кроме
nl
), давайте предположим, что этот.е. преобразовать текст в верхний регистр; Итак, для ввода
Вы хотите вывод
Вот мое первое приближение решения:
где пробелы перед
@@
строками и рядом с концом последней строки являются символами табуляции. Обратите внимание, что я используюnl
в своих целях . (Конечно, я делаю это, чтобы решить вашу проблему, но не для того, чтобы получить вывод с номерами.)Это нумерует строки ввода, так что мы можем разбить его на маркеры разделов и узнать, как его собрать позже. Основная часть цикла основана на вашей первой попытке, принимая во внимание тот факт, что маркеры разделов имеют номера строк. Он разбивает входные данные на два файла:
file0
(неактивный; не в разделе) иfile1
(активный; в разделе). Вот как они выглядят для вышеуказанного ввода:Затем мы запускаем
file1
(что является объединением всех строк в сечении) через фильтр капитализации; объединить это с нефильтрованными линиями вне сечения; сортировать, чтобы вернуть их в исходный порядок; а затем уберите номера строк. Это производит вывод, показанный около вершины моего ответа.Это предполагает, что ваш фильтр оставляет номера строк в покое. Если это не так (например, если он вставляет или удаляет символы в начале строки), то, я полагаю, этот общий подход все еще можно использовать, но он потребует некоторого более сложного кодирования.
источник
nl
уже делает большую часть работы там - это то, для чего его-d
опция elimiter.Сценарий оболочки, который использует sed для вывода фрагментов не демаркированных строк и подачи выделенных фрагментов строк в программу фильтра:
Я написал этот скрипт в файл detagger.sh и использовал его так:
./detagger.sh infile.txt
. Я создал отдельный файл filter.sh для имитации функции фильтрации в вопросе:Но операция фильтрации может быть изменена в коде.
Я попытался следовать этой идее общего решения, чтобы такие операции, как нумерация строк, не требовали дополнительного / внутреннего подсчета. Скрипт выполняет некоторую элементарную проверку, чтобы увидеть, что теги демаркатора находятся в парах и вообще не обрабатывает вложенные теги изящно.
источник
Спасибо за все прекрасные идеи. Я придумала собственное решение, отслеживая подраздел в временном файле и передавая его сразу моей внешней команде. Это очень похоже на то, что предложил Supr (но с переменной оболочки вместо временного файла). Кроме того, мне очень нравится идея использования sed, но синтаксис для этого случая кажется мне немного чрезмерным.
Мое решение:
(Я использую
nl
только в качестве примера фильтра)Я бы предпочел не иметь дело с управлением временными файлами, но я понимаю, что переменные оболочки могут иметь довольно низкие ограничения по размеру, и я не знаю ни одной конструкции bash, которая бы работала как временный файл, но исчезала автоматически, когда процесс заканчивается
источник
M
,N
иO
буду пронумерован4
,5
и6
. Это не делает этого. Мой ответ так (кроме того факта, что в его нынешнем воплощении он не работаетnl
как фильтр). Если этот ответ дает желаемый результат, то что вы имели в виду под «накапливать состояние через строки»? Вы имели в виду, что хотите сохранить состояние только через каждый раздел, но не между (через) разделами? (Почему вы не включили пример с несколькими разделами в свой вопрос?)nl -p
чтобы получитьM,N,O==4,5,6
.