Como posso dividir um arquivo de texto em vários arquivos de texto?

16

Eu tenho um arquivo de texto chamado entry.txt que contém o seguinte:

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631
[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631
[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

Gostaria de dividi-lo em três arquivos de texto: entry1.txt , entry2.txt , entry3.txt . Seu conteúdo é o seguinte.

entry1.txt :

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631

entry2.txt :

[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631

entry3.txt :

[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

Em outras palavras, o caractere [ indica que um novo arquivo deve começar. As entradas ( [ entry*] , onde * é um número inteiro) estão sempre em ordem numérica e são inteiros consecutivos começando de 1 a N (no meu arquivo de entrada real, N = 200001).

Existe alguma maneira de realizar a divisão automática de arquivos de texto no bash? Minha entrada real entry.txt contém, na verdade, 200.001 entradas.

    
por Andrew 25.08.2012 / 23:41

11 respostas

11

E aqui está uma frase simples e simples:

$ gawk '/^\[/{match($0, /^\[ (.+?) \]/, k)} {print >k[1]".txt" }' entry.txt

Isso funcionará para qualquer tamanho de arquivo, independentemente do número de linhas em cada entrada, desde que cada cabeçalho de entrada se pareça com [ blahblah blah blah ] . Observe o espaço logo após a abertura [ e pouco antes do fechamento ] .

EXPLICAÇÃO:

awk e gawk leem um arquivo de entrada linha a linha. Conforme cada linha é lida, seu conteúdo é salvo na variável $0 . Aqui, estamos dizendo gawk para corresponder a qualquer coisa entre colchetes e salvar sua correspondência na matriz k .

Assim, toda vez que a expressão regular for correspondida, ou seja, para cada cabeçalho no seu arquivo, k [1] terá a região correspondente da linha. Ou seja, "entry1", "entry2" ou "entry3" ou "entryN".

Finalmente, imprimimos cada linha em um arquivo chamado <whatever value k currently has>.txt , ie entry1.txt, entry2.txt ... entryN.txt.

Este método será muito mais rápido que o perl para arquivos maiores.

    
por 26.08.2012 / 03:31
17

Com csplit do GNU coreutils (Linux não integrado, Cygwin) :

csplit -f entry -b '%d.txt' entry.txt '/^\[ .* \]$/' '{*}'

Você vai acabar com um arquivo extra vazio entry0.txt (contendo a parte antes do primeiro cabeçalho).

Padrão csplit não tem a repetidora {*} indefinida e a opção -b para especificar o formato do sufixo, assim em outros sistemas você terá que contar o número de seções primeiro e renomear os arquivos de saída depois.

csplit -f entry -n 9 entry.txt '/^\[ .* \]$/' "{$(egrep -c '^'\[ .* \]$' <entry.txt)}"
for x in entry?????????; do
  y=$((1$x - 1000000000))
  mv "entry$x" "entry$y.txt"
done
    
por 26.08.2012 / 01:26
10

No perl isso pode ser feito de forma muito mais simples:

perl -ne 'open(F, ">", ($1).".txt") if /\[ (entry\d+) \]/; print F;' file
    
por 26.08.2012 / 07:54
9

Aqui está um pequeno one-liner do awk:

awk '/^\[/ {ofn=$2 ".txt"} ofn {print > ofn}' input.txt

Como isso funciona?

  • /^\[/ corresponde a linhas que começam com um colchete esquerdo e
  • {ofn=$2 ".txt"} define uma variável para a segunda palavra delimitada por espaços em branco como nosso nome de arquivo de saída. Então,
  • ofn é uma condição que é avaliada como verdadeira se a variável for definida (causando assim linhas antes que o primeiro cabeçalho seja ignorado)
  • {print > ofn} redireciona a linha atual para o arquivo especificado.

Note que todos dos espaços neste script awk podem ser removidos, se o tamanho compacto te faz feliz.

Note também que o script acima realmente precisa dos cabeçalhos de seção para ter espaços ao redor e não dentro deles. Se você quisesse lidar com cabeçalhos de seção como [foo] e [ this that ] , precisaria de um pouco mais de código:

awk '/^\[/ {sub(/^\[ */,""); sub(/ *\] *$/,""); ofn=$0 ".txt"} ofn {print > ofn}' input.txt

Isso usa a função sub() do awk para remover os colchetes e os espaços em branco iniciais e finais. Observe que, por comportamento padrão do awk, isso reduz o espaço em branco (o separador de campo) em um único espaço (ou seja, [ this that ] é salvo em "this that.txt" ). Se a manutenção do espaço em branco original em seus nomes de arquivos de saída for importante, experimente configurar FS.

    
por 27.08.2012 / 19:31
2

Isso pode ser feito a partir da linha de comando em python como:

paddy$ python3 -c 'out=0
> with open("entry.txt") as f: 
>   for line in f:
>     if line[0] == "[":
>       if out: out.close()
>       out = open(line.split()[1] + ".txt", "w")
>     else: out.write(line)'
    
por 27.08.2012 / 08:17
2

Esta é uma maneira um pouco rude, mas fácil de entender: use grep -l '[ entry ]' FILENAME para obter os números de linha para dividir em [entrada]. Use uma combinação na cabeça e na cauda para obter as peças certas.

Como eu disse; não é bonito, mas é fácil de compreender.

    
por 26.08.2012 / 02:39
2

Que tal usar o awk com [ como separador de registro e espaço como o separador de campo. Isso nos dá facilmente os dados a serem colocados no arquivo como $0 , em que ele deve colocar de volta o primeiro [ removido e o nome do arquivo como $1 . Nós então só temos que lidar com o caso especial do primeiro registro que está vazio. Isso nos dá:

awk -v "RS=[" -F " " 'NF != 0 {print "[" $0 > $1}' entry.txt
    
por 27.09.2012 / 13:59
2
A resposta de

terdon funciona para mim, mas eu precisava usar o gawk, não o awk. O manual do gawk (procure por 'match (') explica que o argumento do array em match () é uma extensão gawk.Talvez isso dependa da sua instalação do Linux e de suas versões awk / nawk / gawk, mas na minha máquina Ubuntu, apenas o gawk executou a excelente resposta de terdon:

$ gawk '{if(match($0, /^\[ (.+?) \]/, k)){name=k[1]}} {print >name".txt" }' entry.txt
    
por 29.01.2013 / 11:10
1

Aqui está uma solução perl. Esse script detecta as [ entryN ] lines e altera o arquivo de saída de acordo, mas não valida, analisa ou processa os dados em cada seção, apenas imprime a linha de entrada no arquivo de saída.

#! /usr/bin/perl 

# default output file is /dev/null - i.e. dump any input before
# the first [ entryN ] line.

$outfile='/dev/null';
open(OUTFILE,">",$outfile) || die "couldn't open $outfile: $!";

while(<>) {
  # uncomment next two lines to optionally remove comments (starting with
  # '#') and skip blank lines.  Also removes leading and trailing
  # whitespace from each line.
  # s/#.*|^\s*|\s*$//g;
  # next if (/^$/)

  # if line begins with '[', extract the filename
  if (m/^\[/) {
    (undef,$outfile,undef) = split ;
    close(OUTFILE);
    open(OUTFILE,">","$outfile.txt") || die "couldn't open $outfile.txt: $!";
  } else {
    print OUTFILE;
  }
}
close(OUTFILE);
    
por 26.08.2012 / 03:03
1

Oi eu escrevi este script simples usando ruby para resolver seu problema

#!ruby
# File Name: split.rb

fout = nil

while STDIN.gets
  line = $_
  if line.start_with? '['
    fout.close if fout
    fname = line.split(' ')[1] + '.txt'
    fout = File.new fname,'w'
  end
  fout.write line if fout
end

fout.close if fout

você pode usar da seguinte maneira:

ruby split.rb < entry.txt

Eu testei e funciona bem ..

    
por 29.03.2013 / 05:49
1

Eu prefiro a opção csplit , mas como alternativa, aqui está uma solução do GNU para o awk:

parse.awk

BEGIN { 
  RS="\[ entry[0-9]+ \]\n"  # Record separator
  ORS=""                      # Reduce whitespace on output
}
NR == 1 { f=RT }              # Entries are of-by-one relative to matched RS
NR  > 1 {
  split(f, a, " ")            # Assuming entries do not have spaces 
  print f  > a[2] ".txt"      # a[2] now holds the bare entry name
  print   >> a[2] ".txt"
  f = RT                      # Remember next entry name
}

Execute assim:

gawk -f parse.awk entry.txt
    
por 26.08.2012 / 03:36