Замените что-либо между круглыми скобками, даже если они охватывают несколько строк

Я хотел бы использовать bash или shell-скрипт и заменить что-нибудь между двумя скобками пустым пространством. Текст между двумя круглыми скобками может быть в нескольких строках, например:

myFunction (line0 line1 line2 line3 line4) 

что я хотел бы конвертировать в:

 myFunction ( ) 

Я ценю любую помощь. Я надеюсь использовать sed для этой цели.

AWK

AWK позволяет выполнять код-блок {} по диапазону условий. В этом случае мы хотим выполнить gsub() в каждой строке в диапазоне от той, которая содержит ( к той, которая содержит ) .

 $ awk '$0~/[(]/,$0~/[)]/{gsub(/line/,"newline")};1' input.txt another line something else myFunction (newline0 newline1 whatever newline2 newline3 newline4) some other line 

Python (оригинальный ответ)

Вот быстрый скрипт python, который выполняет эту работу:

 #!/usr/bin/env python3 from __future__ import print_function import sys with open(sys.argv[1]) as fp: flag = None for line in fp: clean_line = line.strip() if "(" in clean_line: flag = True if flag: clean_line = clean_line.replace("line","newline") print(clean_line) if ")" in clean_line: flag = False 

Тестовый забег:

 $ cat input.txt another line something else myFunction (line0 line1 lilne2 line3 line4) some other line $ ./edit_function_args.py input.txt another line something else myFunction (newline0 newline1 newline2 newline3 line4) some other line 

Версия BASH

Тот же сценарий, за исключением переписанного в bash с sed

 #!/bin/bash flag=false while IFS= read -r line do if grep -q '(' <<< "$line" then flag=true fi if $flag then line=$(sed 's/line/newline/' <<< "$line") fi printf "%s\n" "$line" if grep -q ')' <<< "$line" then flag=false fi done < "$1" 

Принимая ответ bash @Serg и преобразовывая его в использование встроенных встроенных балов, вместо 2 или 3 процессов на строку. Процессы дешевы, но не бесплатны!

 #!/bin/bash # Use shell builtins, read, true, false, printf flag=false while IFS= read -r line do case "$line" in (*"("*) flag=true ;; esac if $flag then line=${line//line/newline} fi printf "%s\n" "$line" case "$line" in (*")"*) flag=false ;; esac done < "$1" 

Если решение perl в порядке, и файл достаточно мал, чтобы обрабатываться в целом:

 $ perl -0777 -pe 's/\([^)]+\)/$&=~s|line|newline|gr/ge' ip.txt myFunction (newline0 newline1 newline2 newline3 newline4) 
  • -0777 полный файл ввода slurp
  • \([^)]+\) для сопоставления – ( за которым следует не ) символов и заканчивающихся )
  • $&=~s|line|newline|gr сопоставленный шаблон ссылается здесь, используя $& и выполняется требуемая замена (строка для новой строки). Обратите внимание на флаг r чтобы вернуть результат в качестве строки замены
  • e flag позволяет использовать выражение вместо строки
  • использовать perl -i -0777 -pe для редактирования perl -i -0777 -pe

Для вопроса и данных, которые были изначально представлены, работает sed 1-liner

  sed '/(/,/)/s/line/newline/g' 

который говорит для каждого региона, который начинается с строки, содержащей ( и заканчивается линией, содержащей «)», заменяет глобальную line для newline . Удалите g если вы хотите изменить первую line на строке ввода.

Для измененного вопроса,

  sed -e '/(/{' -e ':loop;s/(.*)/()/;t;N;b loop' -e '}' 

работает. Он перебирает вход, печатает его до тех пор, пока не найдет ( … В этот момент он пытается изменить все внутри пары ( ) включая разделители, на just () . Если это удастся, он выходит из цикла, распечатывает результат и продолжается Если это не удалось сделать, как правило, потому что он еще не видел ) , он добавляет следующую строку ввода и продолжает цикл. Если вы не хотите, чтобы это было на одной строке,

 sed -e '/(/{ :loop s/(.*)/()/ t N b loop }' 

облегчает переход.

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

 awk ' function mysub(str) { if (str) gsub(/line/, "newline", str); return str } BEGIN { OFS=FS="(" } NF>1 { if (FS=="(") { print $1,mysub($2); OFS=FS=")" } else { print mysub($1),$2; OFS=FS="(" } next } { print FS=="(" ? $0 : mysub($0) }' /path/to/input 

Пользовательская функция mysub – это место, где вы делаете замены между круглыми скобками. Предполагается, что скобки не вложены.

Как это работает:

Есть два состояния, внутри и снаружи круглых скобок.

  • Снаружи (начальное состояние) разделитель ввода и вывода устанавливается в открывающую скобку ( OFS=FS="(" ).
  • Когда он встречается с линией с несколькими полями, разделенными входным разделителем ( NF>1 ) и …
    • … вы находитесь во внешнем режиме ( FS=="(" ), все до и после вывода разделителя полей (с промежуточным разделителем вывода), но при этом последний выполняет функцию замены ( mysub($2) ) и затем режим переключается путем изменения входных и выходных разделителей ( OFS=FS=")" ),
    • … в противном случае ( else ) вы находитесь внутри режима, и все до и после mysub($1) разделителя полей, но на этот раз с прежним, проходящим через функцию замены ( mysub($1) ), и здесь также mysub($1) режим.
  • Во всех остальных строках вся строка выводится без изменений, если снаружи ( FS=="(" ) или иначе идет в целом через функцию замены ( mysub($0) ).

Сжатый в одну строку:

 awk 'function m(s){gsub(/line/,"newline",s);return s}BEGIN{OFS=FS="("}NF>1{if(FS=="("){print $1,m($2);OFS=FS=")"}else{print m($1),$2;OFS=FS="("}next}{print FS=="("?$0:m($0)}' /path/to/input 

Мои более сложные тестовые данные (которые некоторые из решений с одним слоем здесь потерпят неудачу):

 line96 line97 myFunction (line0 line1 line2 line3 line4) line98 line99 

Выход для него:

 line96 line97 myFunction (newline0 newline1 newline2 newline3 newline4) line98 line99 

Вариация, где все между скобками удаляется вместо (за исключением первого и последнего разрывов строк):

 awk ' BEGIN { OFS=FS="(" } NF>1 { if (FS=="(") { print $1,""; OFS=FS=")" } else { print "",$2; OFS=FS="(" } next } FS=="("' /path/to/input 

Выход для этого:

 line96 line97 myFunction ( ) line98 line99 

Объединив все пояснения, комментарии и пересмотренные данные, мое предложение будет похоже на рев.

Прежде всего, рассмотрим исходный файл d.txt содержащий вашу рекомендованную myFunction (...) плюс еще одну функцию, более реалистичную.
Чтобы быть на твердой стороне, предположим, что эти две функции в этом файле d.txt имеют почти одинаковое содержимое, например:

 $ cat d.txt myOtherFunction (x as boolean y as integer d as string e as whatever) myFunction (xx as boolean yy as integer dd as string ee as whatever) 

Предположим теперь, что в другом файле d2.txt у нас есть другая функция.

 $ cat d2.txt BrandNewFunction (xxx as integer yyy as boolean ddd as integer eee as whatever) 

Забывая образцы имен строки, новой строки и т. Д., И учитывая ваши комментарии, кажется, что вы действительно хотите заменить в своем исходном файле исходного кода d.txt существующий myFunction (..) с помощью BrandNewFunction (...) в файле d2.txt .

Это можно сделать легко, используя чистый bash:

 $ a="$(sed -n '/myFunction (/,/)/p' d.txt)" #isolates myFunction from the source file d.txt $ b="$(cat d2.txt)" #get contents of file d2.txt (BrandNewFunction) $ c="$(cat d.txt)" #get the whole source file d.txt $ echo "${c/$a/$b}" #in source file d.txt ($c) replace $a with $b (d2.txt) #Output: myOtherFunction (x as boolean y as integer d as string e as whatever) BrandNewFunction (xxx as integer yyy as boolean ddd as integer eee as whatever) 

Или даже как однострочный:

 $ a="$(sed -n '/myFunction (/,/)/p' d.txt)";b="$(cat d2.txt)";c="$(cat d.txt)";echo "${c/$a/$b}" 

Выше команда просто печатает на экране результаты замены (эхо). Чтобы сохранить результаты, просто отправьте echo на >d.txt для перезаписывания существующего файла или даже нового файла, если хотите.

Кажется, что Sed не очень хорошо заменяет несколько строк, разделенных символами новой строки, поскольку он ориентирован на операции с линиями.

AWK должен быть хорош для работы, но я не очень хорош в AWK.

Bash – это самое простое решение, которое может успешно заменить многострочные.

PS1: Если файл d2.txt содержит больше функций и вы хотите изолировать BrandNewFunction (..) , аналогично исходному файлу d.txt вам просто нужно изменить определение переменной $ b следующим образом:

 $ b="$(sed -n '/BrandNewFunction (/,/)/p' d2.txt)" 

PS2: Если вы просто хотите заменить myFunction (…) исходного файла d.txt на пустую функцию с тем же именем, вы можете просто переписать переменную b так, как это (вы уже знаете, какая функция из исходного файла d.txt вы хотите удалить, не так ли?)

 $ b="myFunction ( )" 

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

 sgrep -o '%r ' '(start .. end) extracting ("("__")")' < input_file 

Таким образом, например, следующие

 myFunction (line0 line1 (line2) line3 line4) anotherFun (xy) 

становится

 myFunction ( ) anotherFun ( )