Anexa algo a cada lista em um arquivo

4

Eu tenho um arquivo, lists.txt, que se parece com isso:

// stuff at beginning of file

var list1 = new Array();
i = 0;
list1[i++] = 'a';
list1[i++] = 'b';
...
list1[i++] = 'z';

var list2 = new Array();
i = 0;
list2[i++] = 'a';
list2[i++] = 'b';
...
list2[i++] = 'z';

// other stuff at end of file

Eu preciso acrescentar a cada uma dessas listas (há mais de duas delas) e terminar com algo assim:

var list1 = new Array();
i = 0;
list1[i++] = 'a';
list1[i++] = 'b';
...
list1[i++] = 'z';
list1[i++] = 'something new';

var list2 = new Array();
i = 0;
list2[i++] = 'a';
list2[i++] = 'b';
...
list2[i++] = 'z';
list2[i++] = 'another thing';

// other stuff at end of file

Eu tenho estragado meu cérebro sobre isso por um tempo. Eu sei como obter a última ocorrência de cada lista:

list1_last=$(grep "list1\[i++\]" lists.txt | tail -1)
list2_last=$(grep "list2\[i++\]" lists.txt | tail -1)

Eu sei como obter tudo entre o início da primeira lista e o início da segunda lista (inclusive):

list1=$(sed -n '/var list1/,/var list2/p' lists.txt)

Eu sei que posso obter list1 sem a primeira linha de list2 com esta linha de expressão Perl ou este script sed louco .

Mas estou tendo dificuldade em juntar todas as peças. Como devo fazer isso?

Editar

Os valores adicionais que desejo acrescentar estão em outro arquivo, additional-values.txt, que por exemplo contém:

list1[i++] = 'something new';
list2[i++] = 'another thing';

Eu acho que você poderia dizer que estou tentando mesclar os dois arquivos.

Editar 2

O arquivo atual se parece mais com isso:

// comment
// comment
// ...
var foo = "bar";

// comment
// comment
// ...
var i= 0;

// comment
// comment
// ...
var GoodDomains = new Array();
i=0;
GoodDomains[i++] = "anything.com";  // comment
GoodDomains[i++] = "something.com"; // comment
...
GoodDomains[i++] = "lastthing.com"; // comment
// THIS IS WHERE I WANT TO INSERT SOMETHING

// comment
// comment
// ...
var BadDomains = new Array();
i=0;
BadDomains[i++] = "anything.com";  // comment
BadDomains[i++] = "something.com"; // comment
...
BadDomains[i++] = "lastthing.com"; // comment
// THIS IS WHERE I WANT TO INSERT SOMETHING

// more lists, including GoodHosts, GoodURLs, etc.

// comment
// comment
// ...
for (i in GoodDomains) {
    ...
}

// loop through BadDomains, GoodHosts, GoodURLs, etc.

// comment
// comment
// ...
function IsNumIpAddr(host) {
    ...
}

Originalmente postei uma versão simplificada porque

  1. Não tenho certeza se o arquivo real sempre seguirá esse formato (comentários na parte superior, declarações de variáveis, mais comentários, definições de listas, funções, etc.)
  2. Gostaria de encontrar uma solução genérica para o problema (anexando coisas a listas no meio de um arquivo)

Desculpe se isso foi enganoso.

    
por David Kennedy 25.05.2015 / 19:09

6 respostas

3

Como você está tentando usar sed ranges, veja uma maneira possível de fazer isso. As linhas no seu additional-values.txt seguem o mesmo padrão:

KEY[i++] = 'VALUE'; //etc

e, tanto quanto eu posso dizer, cada linha deve ser inserida em um intervalo que é sempre delimitado por

var KEY = new Array();

e uma linha vazia


para poder processar additional-values.txt e transformá-lo em um script sed para cada linha:

/^var KEY = new Array();/,/^$/{
/^$/ i\
KEY[i++] = 'VALUE'; // etc
}

ou seja, em /^var KEY = new Array();/,/^$/ range, insira a linha KEY[i++] = 'VALUE'; // etc antes da linha vazia. Você então usa o script para processar lists.txt :

sed 's/\/&&/g' additional-values.txt | \
sed 's|^\([^[]*\).*|/^var  = new Array();/,/^$/{\
/^$/ i\\
&\
}|' | sed -f - lists.txt

O primeiro sed escapa de todas as barras invertidas, o segundo sed processa additional-values.txt transformando-o em um script que é usado pelo terceiro sed (via -f ) para processar lists.txt .
por exemplo. amostra additional-values.txt content:

GoodDomains[i++] = '^stuff/here/'; \
BadDomains[i++] = '%XYZ+=?\<>';
GoodNetworks[i++] = '|*{};:\'; // Malware\
BadDomains[i++] = '\$.|&$@"#"!||';

o resultado de:

sed 's/\/&&/g' additional-values.txt | \
sed 's|^\([^[]*\).*|/^var  = new Array();/,/^$/{\
/^$/ i\\
&\
}|'

é

/^var GoodDomains = new Array();/,/^$/{
/^$/ i\
GoodDomains[i++] = '^stuff/here/'; \
}
/^var BadDomains = new Array();/,/^$/{
/^$/ i\
BadDomains[i++] = '%XYZ+=?\\<>';
}
/^var GoodNetworks = new Array();/,/^$/{
/^$/ i\
GoodNetworks[i++] = '|*{};:\'; // Malware\\
}
/^var BadDomains = new Array();/,/^$/{
/^$/ i\
BadDomains[i++] = '\$.|&$@"#"!||'; 
}

isto é então passado para sed -f - lists.txt assim com por ex. amostra lists.txt :

// Counter Variable to initalize the arrays.
var i= 0;

var GoodDomains = new Array();
i=0;
GoodDomains[i++] = 'aba.com'; // Phish - 2010-02-05

var GoodNetworks = new Array();
i=0;
GoodNetworks[i++] = '10.0.0.0, 255.0.0.0';  // NRIP
// GoodNetworks[i++] = "63.140.35.160"; // DNSWCD 2o7

var BadDomains = new Array();
i=0;
BadDomains[i++] = '.0catch.com'; // AdServer - 2009-06-16

//var BadDomains = new Array();

em execução:

sed 's/\/&&/g' additional-values.txt | \
sed 's|^\([^[]*\).*|/^var  = new Array();/,/^$/{\
/^$/ i\\
&\
}|' | sed -f - lists.txt

saídas:

// Counter Variable to initalize the arrays.
var i= 0;

var GoodDomains = new Array();
i=0;
GoodDomains[i++] = 'aba.com'; // Phish - 2010-02-05
GoodDomains[i++] = '^stuff/here/'; \

var GoodNetworks = new Array();
i=0;
GoodNetworks[i++] = '10.0.0.0, 255.0.0.0';  // NRIP
// GoodNetworks[i++] = "63.140.35.160"; // DNSWCD 2o7
GoodNetworks[i++] = '|*{};:\'; // Malware\

var BadDomains = new Array();
i=0;
BadDomains[i++] = '.0catch.com'; // AdServer - 2009-06-16
BadDomains[i++] = '%XYZ+=?\<>';
BadDomains[i++] = '\$.|&$@"#"!||'; 

//var BadDomains = new Array();

Se preferir gnu sed e processar substituição:

sed -E 's|^([^[]*).*|/^var  = new Array();/,/^$/{/^$/ i\\n&\
}|' <(sed 's/\/&&/g' additional-values.txt) | sed -f - lists.txt
    
por 26.05.2015 / 02:54
4

Se você inverter o arquivo, você pode adicionar uma linha na primeira vez que você vir algo:

tac lists.txt |
awk -v l1="list1" -v val1="something new" \
    -v l2="list2" -v val2="another thing" '
          index($0, l1"[i++]") && !found1 {
              printf "%s[i++] = \"%s\";\n", l1, val1
              found1 = 1
          }
          index($0, l2"[i++]") && !found2 { 
              printf "%s[i++] = \"%s\";\n", l2, val2
              found2 = 1
          }
          {print}
' |
tac > lists.txt.new

É um pouco desidratado, mas serve.

Eu senti falta de que havia "additional-values.txt". É bem melhor assim:

tac lists.txt | 
awk '
    NR == FNR {additional[$1] = $0; next}
    $1 in additional && !found[$1] {print additional[$1]; found[$1] = 1}
    {print}
' additional-values.txt - | 
tac > newfile
    
por 26.05.2015 / 03:02
2

Se as listas em seu arquivo de entrada estiverem separadas por uma linha em branco, você poderá usar uma ferramenta que permita definir o separador de registros (o que define uma "linha") como novas linhas consecutivas. Por exemplo, em Perl (supondo que suas substituições estejam em um arquivo chamado additions ):

perl -ne 'BEGIN{## Open the additions file
                open($fh,"additions"); 
                while(<$fh>){ 
                  ## Get the name of the current list
                  /list./; 
                  ## save this replacement in the %f hash
                  $f{$&}=$_;
                }
                ## Set the record separator to consecutive newlines.
                $/="\n\n";
               }
          ## Now that the BEGIN{} block is finished, process the
          ## input file.

         ## Does this line match "list."? 
         if(/list./){
            chomp; ## remove trailing newlines. 
            ## Add the addition to this "line"
            $_.= "\n$f{$&}\n\n"; 
          } 
         ## print each input line
         print ' file 

Os itens acima podem ser condensados para:

perl -ne 'BEGIN{open($fh,"additions"); while(<$fh>){/list./;$f{$&}=$_;}$/="\n\n";}
         if(/list./){chomp;$_.= "\n$f{$&}\n\n"; }; print ' file 
    
por 25.05.2015 / 20:47
1

Eu finalmente encontrei algo que funciona:

# print from beginning of file to "var list1" (exclusive)                                                                                 
sed "/var list1/,\$d" lists.txt > merged.txt

# print from "var list1" to last member of array
lastlist1=$(grep -n "list1\[i++\]" lists.txt | tail -1 | cut -f1 -d:)
sed -n "/var list1/,$(echo $lastlist1)p" lists.txt >> merged.txt
grep "^list1" additional-values.txt >> merged.txt

# print from "var list2" to last member of array
lastlist2=$(grep -n "list2\[i++\]" lists.txt | tail -1 | cut -f1 -d:)
sed -n "/var list2/,$(echo $lastlist2)p" lists.txt >> merged.txt
grep "^list2" additional-values.txt >> merged.txt

# do this for list3, list4,... listn

# print from last member of listn (exclusive) to end of file
sed "1,$(echo $lastlistn)d" lists.txt >> merged.txt

Isso é muito chato e provavelmente pode ser melhorado, mas pelo menos eu entendo.

    
por 26.05.2015 / 02:55
1

Dado que as suas listas são separadas por novas linhas como esta

var list1 = new Array();
i = 0;
list1[i++] = 'a';
list1[i++] = 'b';
list1[i++] = 'z';

var list2 = new Array();
i = 0;
list2[i++] = 'a';
list2[i++] = 'b';
list2[i++] = 'z';\n

E se o adicional-lists.txt se parece com:

list1[i++] = 'something new';
list2[i++] = 'another thing';

Então este script bash / sed produzirá a saída desejada:

#! /bin/bash
a="lists.txt"
b="additional-values.txt"
while read line; do
    list=$(expr match "$line" '\(.*\[\)')   
    list=${list::-1}
    sed -i "/$list\[i++\]/{:loop; n; /^$/{s/^$/$line\n/; b}; b loop;}" $a
done < $b

Ele faz isso lendo cada linha de additional-values.txt e obtendo a subseqüência de caracteres da linha até [(estamos assumindo que additional-lists.txt é o nome do formato [i ++] ...), Por exemplo, "list1 [", então ele remove o último caractere para obter o nome da lista. Em seguida, ele inicia um script sed que corresponde ao nome da lista (observe o uso de aspas duplas para usar a variável bash) e, em seguida, inicia um loop que termina quando você alcança uma linha em branco. Finalmente, substitui a linha em branco pela linha de valores adicionais (e uma nova linha). A opção -i significa editar no lugar.

Saída:

 $ cat lists.txt
 var list1 = new Array();
 i = 0;
 list1[i++] = 'a';
 list1[i++] = 'b';
 list1[i++] = 'z';
 list1[i++] = 'something new';

 var list2 = new Array();
 i = 0;
 list2[i++] = 'a';
 list2[i++] = 'b';
 list2[i++] = 'z';
 list2[i++] = 'another thing';
    
por 25.05.2015 / 23:56
1

Usa o Separador de Registros do awk RS definido como: a última linha de uma lista, mais a seguinte linha em branco .

Como funciona :

Inicialmente, RS é \n (padrão) - para o arquivo de entrada denominado no primeiro argumento: additional-values.txt
Imediatamente após a leitura no primeiro arquivo, awk altera o valor de RS para o valor no segundo argumento.
O segundo arquivo lists.txt , nomeado em terceiro, arg tem o RS definido pelo segundo argumento

line 1 : criar matriz de valores adicionais
line 2 : primeiro campo de divisão é a chave da lista atual - via RT (o texto por RS )
line 3 : registro de impressão + separador de registro (menos um \n ) + valores adicionais

 awk 'RS == "\n" { addval[$1] = addval[$1] $0 "\n"; next }
   { split(RT,crskey) 
     print $0 gensub(/\n/,"","",RT) addval[ crskey[1] ] 
   }' additional-values.txt \
      RS='[^[\n]+[[]i[+][+][]] = [^;\n]+;\n\n' \
      lists.txt
    
por 26.05.2015 / 01:45