Substitua qualquer coisa entre parênteses, mesmo que abrangendo várias linhas

6

Eu gostaria de usar o bash ou shell script e substituir qualquer coisa entre os dois parênteses com um espaço vazio. O texto entre os dois parênteses pode estar em várias linhas, como:

myFunction (line0

line1

line2

line3

line4) 

que eu gostaria de converter para:

myFunction ( )
    
por Armin 29.01.2017 / 00:15

8 respostas

5

AWK

O AWK permite executar o bloco de códigos {} no intervalo de condições. Nesse caso, queremos executar gsub() em cada linha no intervalo da que contém ( para a que contém ) .

$ awk '$0~/[(]/,$0~/[)]/{gsub(/line/,"newline")};1' input.txt                                                     
another line
something else

myFunction (newline0

newline1

whatever

newline2

newline3

newline4)

some other line

Python (resposta original)

Aqui está um rápido script python que faz o trabalho:

#!/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

Execução de teste:

$ 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

versão BASH

O mesmo script, exceto reescrito em bash com 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"
    
por 29.01.2017 / 00:27
6

Tomando a resposta bash de @Serg e convertendo-a para usar bash builtins, em vez de 2 ou 3 processos por linha. Os processos são baratos, mas não são gratuitos!

#!/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"
    
por 29.01.2017 / 01:49
5

Se perl solution estiver bem e o arquivo for pequeno o suficiente para ser processado como um todo:

$ perl -0777 -pe 's/\([^)]+\)/$&=~s|line|newline|gr/ge' ip.txt    
myFunction (newline0

newline1

newline2

newline3

newline4) 
  • -0777 slurp arquivo de entrada inteiro
  • \([^)]+\) padrão a combinar - ( seguido por não ) caracteres e terminando com )
  • $&=~s|line|newline|gr o padrão correspondente é referenciado aqui usando $& e a substituição desejada (linha para nova linha) é feita. Observe o sinal r para retornar o resultado como a string de substituição
  • e flag permite usar expressão em vez de string
  • use perl -i -0777 -pe para edição no local
por 29.01.2017 / 06:07
4

Para a pergunta e os dados apresentados originalmente, um sed 1-liner funciona

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

que diz para cada região que começa com uma linha contendo um ( e termina com uma linha contendo um ')', substitua globalmente line por newline . Remova o g se você quiser alterar apenas o primeiro line em uma linha de entrada.

Para a pergunta modificada,

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

funciona. Faz um loop sobre a entrada, imprimindo até encontrar um ( . Neste ponto, ele tenta alterar tudo dentro de um par ( ) , incluindo os delimitadores para apenas () . Se isso for bem sucedido, interrompe o loop, imprime o resultado e continua. Se não conseguiu, geralmente porque ainda não viu o ) , ele anexa a próxima linha de entrada e continua o loop. Se você não quer em uma linha, então escreva como

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

facilita o acompanhamento.

    
por 29.01.2017 / 01:55
3
Usando 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

A função personalizada mysub é onde você faz as substituições que deseja fazer entre os parênteses. A suposição é que os parênteses não estão aninhados.

Como funciona:

Existem dois estados, dentro e fora dos parênteses.

  • Fora (o estado inicial), o separador de entrada e saída é definido para o parêntese de abertura ( OFS=FS="(" ).
  • Quando se deparar com uma linha com mais de um campo separado pelo separador de entrada ( NF>1 ) e…
    • … você está atualmente no modo externo ( FS=="(" ), tudo antes e depois do separador de campo ser enviado (com o separador de saída intermediário), mas com o último passando pela função de substituição ( mysub($2) ) e o modo é invertido alterando os separadores de entrada e saída ( OFS=FS=")" ),
    • … caso contrário ( else ), você está no modo interno, e tudo antes e depois do separador de campo é gerado, mas desta vez com o anterior passando pela função de substituição ( mysub($1) ) e o modo também aqui.
  • Em todas as outras linhas, a linha inteira é produzida inalterada se estiver fora ( FS=="(" ) ou se for como um todo através da função de substituição ( mysub($0) ).
Comprimido em uma única linha:
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
Meus dados de teste mais complicados (com os quais algumas soluções one-liner aqui falharão):
line96
line97 myFunction (line0

line1

line2

line3

line4) line98
line99
Saída para isso:
line96
line97 myFunction (newline0

newline1

newline2

newline3

newline4) line98
line99
Variação em que tudo entre os parênteses é excluído (exceto pela primeira e última quebra de linha):
awk '
  BEGIN {
    OFS=FS="("
  }
  NF>1 {
    if (FS=="(") {
      print $1,""; OFS=FS=")"
    } else {
      print "",$2; OFS=FS="("
    }
    next
  }
  FS=="("' /path/to/input
Saída para este:
line96
line97 myFunction (
) line98
line99
    
por 29.01.2017 / 00:46
2

Combinando todos os esclarecimentos, comentários e dados revisados, minha oferta seria como abaixo.

Primeiro, vamos considerar um arquivo de origem d.txt contendo sua função myFunction (...) e mais uma função para ser mais realista.
Para estar no lado difícil, vamos supor que essas duas funções neste arquivo d.txt tenham conteúdos quase idênticos, assim:

$ 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)

Vamos supor agora que em outro arquivo d2.txt temos uma função diferente.

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

Esquecendo os nomes de amostra de linha, nova linha, etc e considerando seus comentários, parece que o que você realmente quer é substituir em seu arquivo de código-fonte inicial d.txt o myFunction (..) existente com o BrandNewFunction (...) presente no arquivo d2.txt .

Isso pode ser feito facilmente usando o bash puro:

$ 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)

Ou até mesmo como uma linha:

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

O comando acima apenas imprime na tela os resultados da substituição (eco). Para salvar os resultados, envie echo para >d.txt para sobrescrever o arquivo existente ou até mesmo um novo arquivo, se desejar.

Sed parece não ser muito bom para substituir múltiplas linhas separadas por novas linhas, uma vez que é focado em operações de linha.

O AWK deve ser bom para o trabalho, mas eu não sou bom no AWK.

O Bash é a solução mais fácil, que pode substituir multilinhas com sucesso.

PS1: Se o arquivo d2.txt contiver mais funções e você quiser isolar o BrandNewFunction (..) , similarmente ao arquivo de origem d.txt você só precisa modificar a definição da variável $ b assim:

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

PS2: Se você quer apenas substituir myFunction (...) do arquivo fonte d.txt com uma função vazia com o mesmo nome, você pode simplesmente codificar a variável b assim (você já sabe qual função do arquivo fonte d .txt você deseja excluir, certo?)

$ b="myFunction ( )" 
    
por 30.01.2017 / 00:45
2

Se você também deseja processar parênteses aninhados corretamente, use uma ferramenta para um idioma livre de contexto em vez de um idioma regular, por exemplo sgrep :

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

Desta forma, por exemplo, o seguinte

myFunction (line0

line1

(line2)

line3

line4) 

anotherFun (x y)

torna-se

myFunction ( ) 

anotherFun ( )
    
por 30.01.2017 / 19:59
0

Uma nova versão do GNU sed suporta a opção -z.

Normally, sed reads a line by reading a string of characters up to the end-of-line character (new line or carriage return).
The GNU version of sed added a feature in version 4.2.2 to use the "NULL" character instead. This can be useful if you have files that use the NULL as a record separator. Some GNU utilities can genertae output that uses a NULL instead a new line, such as "find . -print0" or "grep -lZ".

Você pode usar essa opção quando quiser que o sed funcione em diferentes linhas.

sed -z 's/([^)]*)/( )/g' inputfile
    
por 20.03.2019 / 23:53