Como dividir um arquivo contando números de dígitos dentro de uma linha?

3

Eu tenho um arquivo com 45000 caracteres em cada linha e quero dividir o arquivo original com base em números específicos de caracteres em uma linha. como um pequeno exemplo meu arquivo de entrada se parece com:

input.txt:

123394531112334455938383726644600000111234499922281133
234442221117273747474747474729292921111098887777772235
231112233647474838389292121037549284753930837475111013

tem números de 54 dígitos em cada linha. Eu quero que os primeiros 10 dígitos sejam um arquivo separado e 11-24 seja outro arquivo. e de 25-32 dígitos outro arquivo e 33-50 o último arquivo como:

out1.txt (1-10)

1233945311
2344422211
2311122336

out2.txt (11-24)

 12334455938383
 17273747474747
 47474838389292

out3.txt (25-32)

72664460
47472929
12103754

out4.txt (33-54)

0000111234499922281133
2921111098887777772235
9284753930837475111013

alguma sugestão por favor?

    
por zara 05.10.2015 / 15:41

2 respostas

4

Você pode fazer isso com o Bash usando a leitura / substituição de parâmetro / expansão / divisão. O formulário é $ {PARAMETER: OFFSET: LENGTH}, em que OFFSET é baseado em zero. Salve o seguinte arquivo como 'split', por exemplo, depois leia cada linha por:

#!/usr/bin/env bash

# Usage: ./split "data.txt"

while IFS= read -r line
do
    printf '%s\n' "${line:0:10}"  >&3  #  1-10
    printf '%s\n' "${line:10:14}" >&4  # 11-24
    printf '%s\n' "${line:24:8}"  >&5  # 25-32
    printf '%s\n' "${line:32:22}" >&6  # 33-54
done < "$1" 3> output01.txt 4> output02.txt 5> output03.txt 6> output04.txt

# end file

É claro que você pode precisar ajustar um pouco as posições acima, mas você pode usar este modelo para vários tipos diferentes de processamento de arquivos. As posições acima produzirão a saída desejada. Uma boa referência (sobre expansão de parâmetros) pode ser encontrada em bash-hackers.org

Como um pós-escrito, depois de incorporar as melhorias práticas recomendadas (veja os comentários), tenha em mente que, para arquivos grandes, a abordagem do Bash não será eficiente em termos de tempo da CPU e recursos da CPU. Para quantificar essa declaração, preparei uma breve comparação abaixo. Primeiro crie um arquivo de teste (bigsplit.txt) dos dados do post de abertura com 300.000 linhas de comprimento (16500000 bytes). Em seguida, compare split , corte e awk , onde as implementações cut e awk são idênticas as versões do StéphaneChazelas . O tempo de CPU, em segundos, é a soma dos tempos de CPU do sistema e do usuário, e a RAM é o máximo usado.

$ stat -c %s bigsplit.txt && wc -l bigsplit.txt 
16500000
300000 bigsplit.txt

$ ./benchmark ./split bigsplit.txt 

CPU TIME AND RESOURCE USAGE OF './split bigsplit.txt'
VALUES ARE THE AVERAGE OF ( 10 ) TRIALS

CPU, sec :   88.41
CPU, pct :   99.00
RAM, kb  : 1494.40

$ ./benchmark ./cut bigsplit.txt 

CPU TIME AND RESOURCE USAGE OF './cut bigsplit.txt'
VALUES ARE THE AVERAGE OF ( 10 ) TRIALS

CPU, sec :    0.86
CPU, pct :   99.00
RAM, kb  :  683.60

$ ./benchmark ./awk bigsplit.txt

CPU TIME AND RESOURCE USAGE OF './awk bigsplit.txt'
VALUES ARE THE AVERAGE OF ( 10 ) TRIALS

CPU, sec :    1.19
CPU, pct :   99.00
RAM, kb  : 1215.60

A comparação segue onde o melhor desempenho, cut , é atribuído a um valor de 1:

                             RELATIVE PERFORMANCE 

                                    CPU Secs     RAM kb
                                    --------     ------
                    cut                    1          1
                    awk                  1.4        1.8
                    split (Bash)       102.8        2.2

Não há dúvida de que, nesse caso, cut é a ferramenta a ser usada em arquivos maiores. A partir de testes preliminares de Bash split acima, o loop lido do arquivo é responsável por cerca de 5 segundos do tempo de CPU, a expansão do parâmetro é responsável por cerca de 8 segundos e o restante pode ser relacionado a operações printf para arquivo .

    
por 05.10.2015 / 23:26
6

Você pode chamar cut várias vezes:

cut -c 1-10  < file > out1.txt
cut -c 11-24 < file > out2.txt
cut -c 25-32 < file > out3.txt
cut -c 33-54 < file > out4.txt

(tenha cuidado, porém, que as versões atuais do GNU cut não suportam caracteres multi-byte (o que não deve ser uma preocupação para dígitos decimais ASCII como em sua entrada))

Ou com awk de uma só vez:

awk '{
  print substr($0, 1,  10) > "out1.txt"
  print substr($0, 11, 14) > "out2.txt"
  print substr($0, 25, 8 ) > "out3.txt"
  print substr($0, 33, 22) > "out4.txt"}' < file

(lembre-se de que algumas implementações de awk , como as versões atuais de mawk , não suportam caracteres de múltiplos bytes (o que não deve ser uma preocupação para dígitos decimais ASCII))

Com o GNU awk , você também pode fazer:

awk -v FIELDWIDTHS='10 14 8 22' '
  {for (i = 1; i <= NF; i++) print $i > "out" i ".txt"}' < file
    
por 05.10.2015 / 15:56