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

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

В частности, я хотел бы заменить ,\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

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

🤔 А знаете ли вы, что...
Bash поддерживает множество операторов для выполнения арифметических операций.


96
4

Ответы:

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

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

Решено

Если вы используете 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

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

 # &  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 ?,
,

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


Использование 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 ?,%

См. баш демо.