Я пытаюсь заменить шаблон между строками файла.
В частности, я хотел бы заменить ,\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
Заранее спасибо!





Использование 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, они хотят.
@RenaudPacalet OP хочет переместить & с начала букв вверх на одну строку, но исключить те, которые начинаются с комментария. Я думаю, что ожидаемый результат был немного неправильным, поскольку & не может перейти к F, если он не вставлен искусственно.
Ну, может быть, но это не тот результат, который они показывают. Если вы правы, они должны отредактировать свой вопрос, иначе вы должны отредактировать свой ответ.
@RenaudPacalet Я не могу гарантировать, что прав, это всего лишь предположение.
Если вы используете 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Выход
A +, &
B -, &
C ), &
D +, &
E (, &
F *, &
# & G -,
H +, &
I (, &
J +,
K ?,%
См. баш демо.
Строка
#имеет один пробел до и после#во входных данных и два пробела после#в выходных данных. Я предполагаю, что это опечатка.