sed: substituição de várias linhas do bloco de configuração

5

Eu tenho alguns arquivos de configuração que basicamente parecem

(...content...)
# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY
(... more content ...)
# END DYNAMIC BLOCK
(... even more content ...)

Agora, no bash usando CONTENT='wget -O - http://$SERVER/get_config.php' , eu tenho o substituto para o bloco dinâmico.

Como faço a substituição agora e como faço o script inserir o bloco no final do arquivo se ele não está lá?

    
por user1933738 02.01.2013 / 10:41

3 respostas

6

Se você quiser usar sed, você pode ler de um pipe nomeado. Tenha em atenção que este código não tenta lidar com erros. O script será bloqueado se o cabeçalho do bloco dinâmico estiver presente mais de uma vez.

CONTENT_URL="http://$SERVER/get_config.php"
tmp=$(mktemp -d)
(
  cd "$tmp"
  mkfifo dynamic_seen dynamic_content
  : >dynamic_seen & seen_pid=$!
  wget -O dynamic_content "$CONTENT_URL" & wget_pid=$!
  sed -e '/^# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY$/ p' \
      -e '/^# END DYNAMIC BLOCK$/ {'
          -e p -e 'r dynamic_seen' -e 'r dynamic_content' -e '}' \
      -e '/^# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY$/, /^# END DYNAMIC BLOCK$/ d'
  if ! kill $dynamic_seen 2>/dev/null; then
    # The pipe hasn't been read, so there was no dynamic block. Add one.
    echo "# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY"
    cat dynamic_pipe
    echo "# END DYNAMIC BLOCK - DO NOT EDIT MANUALLY"
  fi
)
rm -rf "$tmp"

Mas eu prefiro o awk.

export CONTENT_URL="http://$SERVER/get_config.php"
awk '
    $0 == "# END DYNAMIC BLOCK - DO NOT EDIT MANUALLY" {skip=0; system("wget \"$CONTENT_URL\""); substituted=1}
    !skip {print}
    $0 == "# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY" {skip=1}
    END {
         if (!substituted) {
            print "# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY";
            system("wget \"$CONTENT_URL\"");
            print "# END DYNAMIC BLOCK - DO NOT EDIT MANUALLY";
        }
    }
'
    
por 03.01.2013 / 03:05
1

Eu iria com um sub-shell e dois comandos sed, algo assim:

beg_tag='# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY'
end_tag='# END DYNAMIC BLOCK'

(
  sed "/^$beg_tag"'$/,$d' oldconf
  echo "$beg_tag"
  wget -O - http://$SERVER/get_config.php
  echo "$end_tag"
  sed "1,/^$end_tag/d" oldconf
) > newconf

Tenha cuidado para não colocar nenhum caractere sed-significativo em beg_tag e end_tag .

Isso anexará a saída se nenhuma tag estiver presente. O primeiro comando sed nunca excluirá nenhuma linha da entrada e o segundo comando sed excluirá todas as linhas.

Teste

Se oldconf contiver:

(...content...)
# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY
(... more content ...)
# END DYNAMIC BLOCK
(... even more content ...)

E o comando wget é substituído por echo hello world , a saída é:

(...content...)
# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY
hello world
# END DYNAMIC BLOCK
(... even more content ...)

Agora, se o bloco for removido, isto é, a seguinte entrada é usada:

(...content...)
(... even more content ...)

A saída é:

(...content...)
(... even more content ...)
# BEGIN DYNAMIC BLOCK - DO NOT EDIT MANUALLY
hello world
# END DYNAMIC BLOCK
    
por 02.01.2013 / 11:11
0

Isso é bastante simples de fazer com sed realmente. Você só precisa equilibrar os intervalos de linhas um contra o outro e como ancorado ao EOF.

INPUT |
sed -e 's/\/&&/g;$!s/$/\/' |        #this sed escapes INPUT for scripting
sed -e '/^'"$START"'/,$!{$!b          #this sed applies concatenated scripts
             G;G;s/$/'"$END"'/;P;:n
};$!N;  /\n'"$END"'/,$!{G;$!bn
};      /\n\n/c\' -f - -e 'P;$d;D
' ./named_infile >outfile

Então, há algumas coisas acontecendo, mas as mais importantes são:

/^$START/,$!{ -- function --}
N; /\n$END/,$!{ -- function -- }

A ideia é que quando nós ancorar um intervalo de linha para qualquer uma das Linhas 1 ou $ EOF, basicamente tornamos gulosos . Geralmente, os intervalos de linha aplicam-se apenas ao menor subconjunto de linhas para o qual eles podem - começando de novo para cada correspondência de LHS e terminando para a primeira correspondência de RHS que ocorre em seguida na entrada. Se o RHS é EOF, bem, eles só podem ser aplicados uma vez - porque há apenas um deles.

Quando faço:

/^$START/,$!{ -- function -- }

Eu especifico que todo o código entre os curlies é executado para cada linha no arquivo até, mas não , incluindo $START . Nesse contexto de função, eu b ranch away para cada linha que é ! e não a $ last.

Dessa forma, todas as linhas até a primeira entrada $START in são impressas automaticamente e ignoradas, mas se a última linha $ estiver dentro dessa faixa - como seria se $START nunca ocorresse uma vez - então está preparado para ser c pendurado na sua string.

E se o seu intervalo não ocorrer na entrada, a ENTRADA será anexada ao final do arquivo.

Quando eu fizer o seguinte:

N; /\n$END/,$!{ -- function -- }

Estou novamente aplicando uma função contextualmente. Desta vez, ele é aplicado ao corpo do seu intervalo - e é a única ocorrência dele - porque o complemento de /\n$END/,$ é todas as linhas que não foram b removidas antes do primeiro $START , e somente acima para e não incluindo o próximo $END .

Nesse caso, a função aplicada é um loop de ramificação - contanto que a entrada caia dentro desse intervalo, ela continuará a b ranch de volta e puxará a linha N ext até encontrar a primeira coincidência $END . ponto no qual c trava todo o intervalo para o conteúdo de -f - do arquivo de script stdin - ou sua entrada com escape. Essa mesma regra é aplicada à última linha no caso de ocorrer antes da primeira coincidência $START .

E é isso. Note, porém, que isto não requer nenhum arquivo especial para funcionar - porque (com segurança) incorpora uma cópia de INPUT dentro do seu script, não é necessário r ead em qualquer momento para aplicar quando necessário.

    
por 13.06.2015 / 08:40