Замена шаблона между строками

Я пытаюсь заменить шаблон между строками файла.

В частности, я хотел бы заменить ,\n & на , &\n в больших и нескольких файлах. Это фактически перемещает символ & на предыдущую строку. Это очень просто с CTR+H, но мне было сложно с sed.

Итак, исходный файл имеет следующий вид:

      A +,
   &  B -,
   &  C ),
   &  D +,
   &  E (,
   &  F *,
 # &  G -,
   &  H +,
   &  I (,
   &  J +,
      K ?,

Желаемая форма вывода:

      A +, &
      B -, &
      C ), &
      D +, &
      E (, &
      F *, &
#  &  G -,
      H +, &
      I (, &
      J +,
      K ?,

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

sed ':a;N;$!ba;s/,\n &/&\n /g' file1.txt > file2.txt

sed -i -e '$!N;/&/b1' -e 'P;D' -e:1 -e 's/\n[[:space:]]*/ /' file2.txt

но они терпят неудачу, если в файле присутствует символ "#".

Есть ли способ заменить совпадающий шаблон проще, скажем: sed -i 's/,\n &/, &\n /g' file

Заранее спасибо!

Строка # имеет один пробел до и после # во входных данных и два пробела после # в выходных данных. Я предполагаю, что это опечатка.

Bodo 13.05.2022 16:24
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
96
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Использование sed

$ sed ':a;N;s/\n \+\(&\) \(.*\)/ \1\n     \2/;ba' input_file
      A +, &
      B -, &
      C ), &
      D +, &
      E (, &
      F *,
 # &  G -, &
      H +, &
      I (, &
      J +,

Это добавляет & в конец строки # & G -,, но не в конец строки F *,. Даже если это было принято, это нет то, что, по словам OP, они хотят.

Renaud Pacalet 13.05.2022 13:42

@RenaudPacalet OP хочет переместить & с начала букв вверх на одну строку, но исключить те, которые начинаются с комментария. Я думаю, что ожидаемый результат был немного неправильным, поскольку & не может перейти к F, если он не вставлен искусственно.

HatLess 13.05.2022 13:58

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

Renaud Pacalet 13.05.2022 14:02

@RenaudPacalet Я не могу гарантировать, что прав, это всего лишь предположение.

HatLess 13.05.2022 14:06
Ответ принят как подходящий

Если вы используете GNU sed и ваш файл не содержит символов NUL (код ASCII 0), вы можете использовать его опцию -z для обработки всего файла как одной строки и многострочный режим команды замены (флаг m). Флаг m не является абсолютно необходимым, но он немного упрощает (. и классы символов не соответствуют символам новой строки):

$ sed -Ez ':a;s/((\`|\n)[^#]*,)((\n.*#.*)*)(\n[[:blank:]]*)&/\1 \&\3\5 /gm;ta' file
      A +, &
      B -, &
      C ), &
      D +, &
      E (, &
      F *, &
 # &  G -,
      H +, &
      I (, &
      J +,
      K ?,

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

Мы ищем последовательность чанков в виде AB*C, где:

  • A — фрагмент (возможно, первый), не содержащий #. Он соответствует (\<backtick>|\n)[^#]*,, что означает начало файла или новую строку, за которым следует любое количество символов, кроме новой строки и #, за которыми следует запятая.
  • B* — любое количество (включая отсутствие) чанков, содержащих #. Ему соответствует \n.*#.*, что означает новую строку, за которой следует любое количество символов, кроме новой строки, за которой следует # и любое количество символов, кроме новой строки.
  • C — фрагмент, начинающийся с новой строки, за которым следуют пробелы и &. Он соответствует \n[[:blank:]]*&, что означает новую строку, за которой следует любое количество пробелов и &.

Если мы находим такую ​​AB*C последовательность, мы добавляем пробел и & в конце A, мы не меняем B* и заменяем первый & в C пробелом. И повторяем до тех пор, пока такой последовательности не будет найдено.

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

$ sed -Ez ':a;s/((\`|\n)[^#]*,[[:blank:]]*)((\n.*#.*)*)(\n[[:blank:]]*)&/\1 \&\3\5 /gm;ta' file

Еще:

$ sed -Ez ':a;s/((\`|\n)[^#]*,)[[:blank:]]*((\n.*#.*)*)(\n[[:blank:]]*)&/\1 \&\3\5 /gm;ta' file

Большое спасибо за ответ! Я вижу вашу точку зрения. Я должен отредактировать свой вопрос, потому что он оказался запутанным. Я не хочу добавлять & в конце каждой строки, кроме # и последней строки. Но вместо этого переместите символ & в конец предыдущей строки. Есть строки (здесь не показаны), которые не содержат символа &.

Petro 13.05.2022 14:58

Хорошо, смотрите мой обновленный ответ.

Renaud Pacalet 13.05.2022 15:02

Предполагая, что линия

 # &  G -,

является закомментированной строкой, которая может быть раскомментирована позже, может иметь смысл обрабатывать & и в этой строке. Не зная цели данных, это может быть полезно, а может и нет.

С ГНУ Awk команда

awk 'BEGIN { RS = ",";ORS = "" } { printf "%s%s", ORS, gensub(/(\n[ \t#]*)&/, " \\&\\1 ",1); ORS=RS }' inputfile

повернет вход

      A +,
   &  B -,
   &  C ),
   &  D +,
   &  E (,
   &  F *,
 # &  G -,
   &  H +,
   &  I (,
   &  J +,
      K ?,

в

      A +, &
      B -, &
      C ), &
      D +, &
      E (, &
      F *, &
 #    G -, &
      H +, &
      I (, &
      J +,
      K ?,

Этот скрипт будет работать правильно, только если последняя строка заканчивается новой строкой или любой другой символ следует за ,.

Объяснение:

  • RS = "," устанавливает запятую в качестве разделителя записи вместо новой строки для ввода.
  • ORS = "" устанавливает разделитель выходных записей на пустую строку перед первой записью.
  • fprintf "%s%s", ORS, gensub(...)до Добавляет разделитель записей вместо добавления.
  • gensub Специальная функция подстановки GNU, которая позволяет использовать обратные ссылки на соответствующие группы.
  • /(\n[ \t#]*)&/ шаблон поиска: круглые скобки определяют группу (1), которая состоит из новой строки \n, за которой следует любая последовательность пробелов, символов табуляции или комментариев [ \t#]*. За группой следует персонаж &.
  • " \\&\\1 " замена: пробел, за которым следует &, за которым следует захваченная группа (1) (\\1) и дополнительный пробел для замены удаленного &. (\\& необходим для получения буквального символа & вместо вставки всего совпадения.)
  • ORS=RS устанавливает разделитель выходных записей на , после первой строки. (фактически после каждого ros) ставить запятую перед 2-й и последующими записями. Это гарантирует, что последняя запись, которая должна быть новой строкой, не получит завершающую ,.

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

awk 'BEGIN { RS=ORS = "," } { print gensub(/(\n[ \t#]*)&/, " \\&\\1 ",1) }' inputfile

Если входной файл заканчивается новой строкой, вывод будет

...
      I (, &
      J +,
      K ?,
,

без новой строки после последнего ,.

Добавляет ли это дополнительную строку, содержащую запятую?

Petro 16.05.2022 11:40

@ Петро Да, ты прав. Когда последняя строка завершается символом новой строки, как это обычно бывает, скрипт добавляет дополнительный , после новой строки. По-видимому, я тестировал его с входным файлом, в котором отсутствует окончательный перевод строки. Попробую исправить скрипт.

Bodo 16.05.2022 13:32

Использование sed

sed -En 'H;${g;s/^\n//;s/((\n *#.*)*)\n +&(.*)/ \&\1\n    \3/gmp}' file

Объяснение

  • -E Включить расширенное регулярное выражение
  • -n Предотвратить печать sed по умолчанию
  • H Добавить для удержания места
  • ${ Когда в конце
  • g Перезаписать то, что находится в пространстве хранения, в пространство шаблона
  • s/^\n//; удалить начальную новую строку из пробела
  • s/ Начать замену
  • ((\n *#.*)*) Группа захвата 1, при необходимости повторите сопоставление новой строки и #, за которым следует остальная часть строки
  • \n +&(.*) Сопоставьте новую строку и 1+ пробелов, затем сопоставьте & и захватите оставшуюся часть строки в группе 3
  • / Замените после этого
  • \&\1\n \3 Шаблон замены с группами захвата и экранированными &
  • / Конец замены
  • gmpграммграмм>lobal для замены всех вхождений, мultiline, п для вывода строки, в которой есть замена

Выход

      A +, &
      B -, &
      C ), &
      D +, &
      E (, &
      F *, &
 # &  G -,
      H +, &
      I (, &
      J +,
      K ?,%

См. баш демо.

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