Como executar substituições definidas em um arquivo em outro arquivo

2

Prefácio: A cada dois dias chega uma questão desse tipo, que é fácil de resolver com sed , mas leva tempo para ser explicada. Estou escrevendo esta pergunta e resposta, para que mais tarde eu possa me referir a esta solução genérica e apenas explicar a adaptação para o caso específico. Sinta-se à vontade para contribuir.

Eu tenho arquivos com definições de variáveis. As variáveis consistem em letras maiúsculas ou sublinhados _ e seus valores seguem após := . Os valores podem conter outras variáveis. Isso é Gnom.def :

NAME:=Gnom
FULL_NAME:=$FIRST_NAME $NAME
FIRST_NAME:=Sman
STREET:=Mainstreet 42
TOWN:=Nowhere
BIRTHDAY:=May 1st, 1999

Depois, há outro arquivo form.txt com um formulário de modelo:

$NAME
Full name: $FULL_NAME
Address: $STREET in $TOWN
Birthday: $BIRTHDAY
Don't be confused by $NAMES

Agora eu quero um script que substitua as variáveis (marcadas com $ e o identificador) no formulário pelas definições no outro arquivo, de forma recursiva, se necessário, para obter o seguinte texto:

Gnom
Full name: Sman Gnom
Address: Mainstreet 42 in Nowhere
Birthday: May 1st, 1999
Don't be confused by $NAMES

A última linha é para garantir que nenhuma substrings de variáveis sejam substituídas acidentalmente.

    
por Philippos 01.05.2017 / 16:39

1 resposta

4

A ideia básica para resolver problemas como este é passar os dois arquivos para sed . Primeiro as definições, que estão armazenadas no espaço de espera de sed . Em seguida, cada linha do outro arquivo recebe o espaço de suspensão anexado e cada ocorrência de uma variável que pode ser encontrada repetida nas definições anexadas é substituída.

Aqui está o script:

sed '/^[A-Z_]*:=.*/{H;d;}
  G
 :b
 s/$\([A-Z_]*\)\([^A-Z_].*\n:=\)\([^[:cntrl:]]*\)//
 tb
 P
 d' Gnom.def form.txt

E agora a explicação detalhada:

/^[A-Z_]*:=.*/{H;d;}

Isso coleta as definições para o espaço de espera. /^[A-Z_]*:=.*/ seleciona todas as linhas que começam com um nome de variável e a seqüência := . Nessas linhas, os comandos em {} são executados: O H os anexa ao espaço de espera, o d os exclui e recomeça, para que não sejam impressos.

Se você não puder assegurar que todas as linhas no arquivo de definição seguem esse padrão, ou se as linhas no outro arquivo puderem corresponder ao padrão fornecido, essa parte precisará ser adaptada, como explicado mais tarde.

G

Neste ponto do script, apenas linhas do segundo arquivo são processadas. O G anexa o espaço de espera ao espaço de padrão, portanto, temos a linha a ser processada com todas as definições no espaço de padrão, separadas por novas linhas.

:b

Isso inicia um loop.

 s/$\([A-Z_]*\)\([^A-Z_].*\n:=\)\([^[:cntrl:]]*\)//

Esta é a parte fundamental, a substituição. Agora temos algo como

At the $FOO<newline><newline>FOO:=bar<newline>BAR:=baz
       ----==================---  ###

no espaço do padrão. (Detalhe: há duas novas linhas antes da primeira definição, uma produzida anexando ao espaço de espera, outra anexando ao espaço de buffer.)

A parte sublinhada com ---- corresponde a $\([A-Z_]*\) . O \(\) torna possível retroceder a referência para essa sequência mais adiante.

\([^A-Z_].*\n\) corresponde à parte sublinhada com === , que é tudo até a referência anterior . Começando com um não O caractere n-variable garante que não combinemos substrings de uma variável. Envolvendo a referência anterior com uma nova linha e := certifica-se de que uma subseqüência de uma definição não corresponderá.

Por fim, \([^[:cntrl:]]*\) corresponde à parte ### , que é a definição. Note que assumimos que a definição não possui caracteres de controle. Se isso for possível, você pode usar [^\n] com GNU sed ou fazer uma solução alternativa para POSIX sed .

Agora, o $ e o nome da variável são substituídos pelo valor da variável , a parte do meio e a definição são deixadas como estavam: .

 tb

Se uma substituição foi feita, o comando t faz um loop para marcar b e tenta outra substituição.

 P

Se nenhuma outra substituição for possível, o P em maiúsculas imprime tudo até a primeira nova linha (assim, a seção de definição não será impressa) e

 d

irá apagar o espaço padrão e iniciar o próximo ciclo. Feito.

Limitações

  • Você pode fazer uma coisa desagradável, como incluir FOO:=$BAR e BAR:=$FOO no arquivo de definição e fazer o script girar para sempre. Você pode definir uma ordem de processamento para evitar isso, mas tornará o script mais difícil de entender. Deixe isso de lado, se o seu script não precisar ser uma prova idiota.

  • Se a definição puder conter caracteres de controle, após o G , poderemos trocar a nova linha com outro caractere como y/\n#/#\n e repetir isso antes de imprimir. Não conheço uma solução melhor.

  • Se o arquivo de definição puder conter linhas com formato diferente ou o outro arquivo puder conter linhas com formato de definição, será necessário um separador exclusivo entre os dois arquivos, como última linha do arquivo de definição ou como primeira linha do arquivo. outro arquivo ou como arquivo separado você passa para sed entre os outros arquivos. Em seguida, você tem um loop para coletar as definições até que a linha separadora seja atendida e, em seguida, faça um loop para as linhas do outro arquivo.

por 01.05.2017 / 16:39