Como posso escapar de caracteres especiais em um “subcomando” sed?

0

Estou usando xmllint --shell em um arquivo XML grande e usando o comando write para gravar um snippet XML a ser usado no teste. O snippet que é gravado precisa de algumas linhas do arquivo XML original (a declaração, o namespace e o nó raiz). Eu quero poder adicionar essas linhas ao arquivo, sem ter que copiar manualmente as linhas. Em vez disso, eu gostaria de usar sed para anexar essas linhas para que eu possa escrever uma função para automatizar essa tarefa tediosa. Para ilustrar, esta é uma amostra do que estou tentando realizar.

XML de origem (source.xml):

<?xml version="1.0" encoding="UTF-8"?>
<foo:root xmlns:foo="TheFooNameSpaceIsImportant">
    <foo:Entry>
        <foo:SomeNode>Foo1</foo:SomeNode>
        <foo:AnotherNode>Bar1</foo:AnotherNode>
    </foo:Entry>
    <foo:Entry>
        <foo:SomeNode>Foo2</foo:SomeNode>
        <foo:AnotherNode>Bar2</foo:AnotherNode>
    </foo:Entry>
    <foo:Entry>
        <foo:SomeNode>Foo3</foo:SomeNode>
        <foo:AnotherNode>Bar3</foo:AnotherNode>
    </foo:Entry>
    <!-- tens of thousands of others -->
    <foo:Entry>
        <foo:SomeNode>Foo20432</foo:SomeNode>
        <foo:AnotherNode>Bar20432</foo:AnotherNode>
    </foo:Entry>

</foo:root>

Fragmento XML salvo (amostra.xml):

<foo:Entry>
    <foo:SomeNode>Foo</foo:SomeNode>
    <foo:AnotherNode>Bar</foo:AnotherNode>
</foo:Entry>

Então, preciso incluir isso nas duas primeiras linhas e na linha inferior do source.xml. Mas o seguinte falha devido ao caractere < :

$ sed -i 1i"'head -n 2 source.xml'" sample.xml
sed: -e expression #1, char 43: unknown command: '<'

Existe uma maneira de escapar desse caractere quando ele está sendo alimentado por um subcomando assim?

    
por jktravis 11.03.2016 / 16:19

3 respostas

4

O comando sed "i" espera \ seguido por texto, conforme explicado na saída de BSD sed quando recebe o comando fornecido acima.

No entanto, espera apenas uma linha de texto. Para inserir mais do que isso, você precisa incluir uma barra invertida no final da primeira linha:

sed "1i\
$(head -n 2 source.xml | sed 's/$/\/')
" sample.xml

Isso (aninhadas sed chamadas) fica um pouco ridículo. Como escrevi sobre em outro lugar , a ferramenta escolhida para edições de arquivos com scripts in-loco não é sed , mas ex :

ex -sc '1,2ya | n! | 0pu | x' source.xml sample.xml

O sinalizador -s inicia ex no modo silencioso, para processamento em lote. -c especifica o comando a ser executado.

1,2ya yanks (ou seja, cópias) as duas primeiras linhas do primeiro arquivo, source.xml .

| é o separador de comandos.

n! vai para o próximo arquivo, descartando as alterações feitas no arquivo atual. (Nós não fizemos nenhum neste caso, então n funcionaria tão bem).

0pu "puts" (ou seja, pastas) as linhas que copiamos anteriormente, colocando-as logo após a linha "0" (ou seja, colando-as acima da primeira linha).

x exits, salvando as alterações feitas no arquivo atual.

Ao contrário de sed -i , que não é especificado em POSIX (e que não funciona no BSD sed , que requer que uma extensão de arquivo de backup receba a opção -i , mesmo se vazia), o comando ex acima é totalmente compatível com POSIX .

    
por 11.03.2016 / 16:40
1

Quando você insere / anexa várias linhas, precisa sair do fim da linha para que sed saiba quando parar de inserir / anexar. No seu caso você poderia correr

head -n 2 source.xml | sed '1i\
1i\
s/\/&&/g
$!s/$/\/' | sed -f - sample.xml

O primeiro sed processa a entrada (adiciona o comando 1i\ antes dessas duas linhas, escapa as barras invertidas e também o fim das linhas, se não a última linha) e passa como um script sed para o segundo comando. Adicione -i ao segundo sed se você deseja editar no lugar.

    
por 11.03.2016 / 16:40
1

Não use sed com XML . XML é uma estrutura de dados contextual, e o regex simplesmente não suporta isso bem. link

Use um analisador. perl tem XML::Twig , o que funciona muito bem:

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
use XML::Twig;

my $xml_to_insert = XML::Twig -> parse ( '<foo:Entry>
    <foo:SomeNode>Foo</foo:SomeNode>
    <foo:AnotherNode>Bar</foo:AnotherNode>
</foo:Entry>') -> root -> copy;

my $xml = XML::Twig -> parse ( \*DATA ); 

$xml_to_insert -> paste ( 'first_child', $xml -> root );
$xml -> set_pretty_print ( 'indented_a');
$xml -> print;


__DATA__
<?xml version="1.0" encoding="UTF-8"?>
<foo:root xmlns:foo="TheFooNameSpaceIsImportant">
    <foo:Entry>
        <foo:SomeNode>Foo1</foo:SomeNode>
        <foo:AnotherNode>Bar1</foo:AnotherNode>
    </foo:Entry>
    <foo:Entry>
        <foo:SomeNode>Foo2</foo:SomeNode>
        <foo:AnotherNode>Bar2</foo:AnotherNode>
    </foo:Entry>
    <foo:Entry>
        <foo:SomeNode>Foo3</foo:SomeNode>
        <foo:AnotherNode>Bar3</foo:AnotherNode>
    </foo:Entry>
    <!-- tens of thousands of others -->
    <foo:Entry>
        <foo:SomeNode>Foo20432</foo:SomeNode>
        <foo:AnotherNode>Bar20432</foo:AnotherNode>
    </foo:Entry>

</foo:root>

Saída:

<?xml version="1.0" encoding="UTF-8"?>
<foo:root xmlns:foo="TheFooNameSpaceIsImportant">
  <foo:Entry>
    <foo:SomeNode>Foo</foo:SomeNode>
    <foo:AnotherNode>Bar</foo:AnotherNode>
  </foo:Entry>
  <foo:Entry>
    <foo:SomeNode>Foo1</foo:SomeNode>
    <foo:AnotherNode>Bar1</foo:AnotherNode>
  </foo:Entry>
  <foo:Entry>
    <foo:SomeNode>Foo2</foo:SomeNode>
    <foo:AnotherNode>Bar2</foo:AnotherNode>
  </foo:Entry>
  <foo:Entry>
    <foo:SomeNode>Foo3</foo:SomeNode>
    <foo:AnotherNode>Bar3</foo:AnotherNode>
  </foo:Entry>
  <!-- tens of thousands of others -->
  <foo:Entry>
    <foo:SomeNode>Foo20432</foo:SomeNode>
    <foo:AnotherNode>Bar20432</foo:AnotherNode>
  </foo:Entry>
</foo:root>

Isso é mais longo e mais detalhado para fins de ilustração - mas ele basicamente pega o seu trecho e o copia para a sua estrutura. Bom e simples.

XML::Twig também suporta "parsefile_inplace", que permite fazer o mesmo que sed -i . Então, seu exemplo seria um pouco mais parecido com:

my $xml_to_insert = XML::Twig -> parsefile ( 'source.xml' ) -> root -> copy;

XML::Twig -> new ( pretty_print => 'indented_a',
                   twig_handlers => { 
                       'foo:root' => sub {  
                            $xml_to_insert -> paste ( 'first_child', $_ ) 
                        } }) -> parsefile_inplace ('sample.xml'); 

Ou se isso parece um pouco complicado:

sub insert_source {
    my ( $twig, $branch ) = @_;  
    my $xml_to_insert = XML::Twig -> parsefile ( 'source.xml' ) -> root -> copy; 
    $xml_to_insert -> paste ( 'first_child', $branch ); 
}

my $xml = XML::Twig -> new ( twig_handlers => { 'foo:root' => \&insert_source } );
   $xml -> parsefile_inplace ( 'sample.xml'); 
    
por 11.03.2016 / 17:17

Tags