Dividir Arquivo em 10 arquivos com um único item em cada arquivo e o limite máximo de números de linha em cada arquivo

2

Eu tenho um arquivo enorme (mais de 2 milhões de registros nele). Aqui está minha exigência:

  • Inicialmente, divida o único arquivo grande em 10 arquivos menores.
  • Os arquivos devem ser formatados da seguinte forma: <File_name> - <timestamp> - xx
    • <timestamp> é o mesmo tempo em cada arquivo
    • xx representará qual arquivo é de 1 a 10
  • Os arquivos devem ter uma divisão limpa entre itens. Ou seja, não podemos ter o mesmo item em mais de um arquivo.

Por exemplo, se eu tiver o arquivo abaixo:

ITEM,PARENT_PARTNUMBER,STORE_NUMBER,QUANTITY,BUYABLE,AVAILABILITYCODE,STORENAME,PHONENUMBER
400000209333,400000209333P,ALL,1297,1,2,,
400000209333,400000209333P,A-80007838,1297,1,2,,
400009664058,400009664058P,ALL,499,1,1,,
400009664058,400009664058P,A-80007838,477,1,1,,
400009664058,400009664058P,13806529,104,0,0,WDW - FLOWER & GARDEN,8-224-6122/5866
400000276151,400000276151P,ALL,0,0,0,,
400000276151,400000276151P,A-80007823,0,0,0,,
400000209692,400000209692P,ALL,8,1,1,,

Então eu quero dividir os arquivos assim. Primeiro arquivo (suponha que o primeiro arquivo tenha atingido o limite de 20000 e que no 19999 haja uma alteração no número do item que não pode estar no mesmo arquivo devido ao limite máximo do arquivo como 20000 e precisamos manter o número do item exclusivo no arquivo:

400000209333,400000209333P,ALL,1297,1,2,,
400000209333,400000209333P,A-80007838,1297,1,2,,
400009664058,400009664058P,ALL,499,1,1,,
400009664058,400009664058P,A-80007838,477,1,1,,
400009664058,400009664058P,13806529,104,0,0,WDW - FLOWER & GARDEN,8-224-6122/5866

Segundo arquivo:

400000276151,400000276151P,ALL,0,0,0,,
400000276151,400000276151P,A-80007823,0,0,0,,
400000209692,400000209692P,ALL,8,1,1,,

e assim por diante até o arquivo 10.

    
por saurabh 16.03.2016 / 21:18

2 respostas

2

#!/bin/bash

file_name="huge.file"

#get file mask
my_mask="$(date +"$file_name-%F-")"

#collect lines with same item in one string separated by unexpected symbol
sed ':1;N;/^\([^,]\+,\).*\n/s/\n/×/;t1;P;D' "$file_name" > tmp.file

#divide tmp.file for 10 pieces without line splitting
split -dn l/10 "tmp.file" "$my_mask"

#split lines with same item back
sed -i 's/×/\n/g' "$my_mask"*

#remove tmp.file if need it
rm tmp.file
    
por 16.03.2016 / 22:22
0

Versão final, eu juro ...:)

Assume-se que:

  • a primeira linha contém o cabeçalho,
  • ITEM é sempre a primeira coluna,
  • todas as linhas com o mesmo número do ITEM são consecutivas,
  • o número de linhas com o mesmo número ITEM é menor que o número de linhas em cada arquivo menor menos 1.

This version guarantees that the smaller files will contain a number of lines lower than or equal to the upper limit specified (i.e. LINES).

#!/usr/bin/awk -f
BEGIN {
    numberOfLinesInCurrentFile=0;
    numberOfLinesInBuffer=0;
    filenumber=0;
    header="";
    previousITEM="";
    FS=",";
    timestamp=ENVIRON["TIMESTAMP"];
    numberOfLinesPerFile=ENVIRON["LINES"];
    currentFilename=FILENAME "-" timestamp "-00";
}
{
  if (NR == 1) {
    header=$0;
    print header >> currentFilename;
    numberOfLinesInCurrentFile=1;
  } else {
    currentITEM=$1;
    if (previousITEM != currentITEM) {
      for (i=0; i<numberOfLinesInBuffer; i++) {
        print bufferOfLines[i] >> currentFilename;
      }
      numberOfLinesInCurrentFile+=numberOfLinesInBuffer;
      numberOfLinesInBuffer=0;
      bufferOfLines[1]=$0
    }
    if ((numberOfLinesInCurrentFile+numberOfLinesInBuffer) >= numberOfLinesPerFile) {
        filenumber++;
        currentFilename=sprintf("%s-%s-%02d", FILENAME, timestamp, filenumber);
        print header >> currentFilename;
        numberOfLinesInCurrentFile=1;
    }
    bufferOfLines[numberOfLinesInBuffer++]=$0
    previousITEM=$1;
  }
}

LINHAS é usado para especificar o número máximo de linhas por arquivos menores.

TIMESTAMP é usado para especificar o registro de data e hora.

bigfile é o arquivo a ser dividido.

O teste está abaixo:

LINES=4000 TIMESTAMP=20160320101538 ./scriptv2.awk bigfile

ls bigfile*
bigfile                    bigfile-20160320101538-02  bigfile-20160320101538-04  bigfile-20160320101538-06  bigfile-20160320101538-08
bigfile-20160320101538-01  bigfile-20160320101538-03  bigfile-20160320101538-05  bigfile-20160320101538-07

----

SEGUNDA VERSÃO PARA REFERÊNCIA: não garante que o número de linhas em cada arquivo menor estará abaixo do limite especificado.

Mais uma versão testada rapidamente. Assume-se que a primeira linha contém o cabeçalho e que o ITEM é sempre a primeira coluna. Todas as linhas com o mesmo número ITEM são consecutivas.

cat script.awk
#!/usr/bin/awk -f
BEGIN {
    filenumber=0;
    header="";
    previousITEM="";
    FS=",";
    timestamp=ENVIRON["TIMESTAMP"];
    numberOfLinesPerFile=ENVIRON["LINES"];
    currentFilename=FILENAME "-" timestamp "-00";
    changeFilenameWhenPossible=0;
}
{
  if (NR == 1) {
    header=$0;
  } else {
    currentITEM=$1;
    if (NR % numberOfLinesPerFile == 0) {
      if (previousITEM != currentITEM) {
        filenumber=filenumber+1;
        filenamberString=sprintf("%02d",filenumber);
        currentFilename=FILENAME "-" timestamp "-" filenamberString;
        changeFilenameWhenPossible=0;
        print header >> currentFilename;
      } else {
        changeFilenameWhenPossible=1;
      } 
    } else if (changeFilenameWhenPossible == 1 && previousITEM != currentITEM) {
      filenumber=filenumber+1;
      filenamberString=sprintf("%02d",filenumber);
      currentFilename=FILENAME "-" timestamp "-" filenamberString;
      changeFilenameWhenPossible=0;
      print header >> currentFilename;
    }
    previousITEM=$1;
  }
  print $0 >> currentFilename;
}

LINHAS devem ser configuradas para o número de linhas desejadas por arquivos menores.

TIMESTAMP deve ser definido como timestamp especificado.

bigfile é o arquivo de linhas de 2 milhões.

O teste está abaixo:

chmod +x script.awk
LINES=200000 TIMESTAMP=20160318101538 ./script.awk bigfile

ls -1 bigfile-*
bigfile-20160318101538-01
bigfile-20160318101538-02
bigfile-20160318101538-03
bigfile-20160318101538-04
bigfile-20160318101538-05
bigfile-20160318101538-06
bigfile-20160318101538-07
bigfile-20160318101538-08
bigfile-20160318101538-09
bigfile-20160318101538-10

Para primeira resposta de referência ...

Notei que você gostaria de se livrar da primeira linha com o cabeçalho, certo?

#!/bin/bash --
nblines=$(wc -l "${1}" | cut -d\  -f1)
nblines=$(((nblines - 1)/10))
tail -n +2 "${1}" | split -l $nblines -d -- - "${1}"-"${2}"-
touch -r "${1}" ./"${1}"?*

Certifique-se de testar com um bigfile que contenha 11 linhas ou mais.

O comando touch é usado para aplicar os tempos de bigfile a todos os menores criados. Remova touch se não for necessário.

nova edição ainda não testada:

ls

bigfile
script.sh

chmod +x ./script.sh
./script.sh bigfile 20160309144430

ls -l bigfile*
-rw-r--r-- 1 jay stackgrp 556 Mar 16 17:03 bigfile
-rw-r--r-- 1 jay stackgrp 92 Mar 16 17:03 bigfile-20160309144430-00
-rw-r--r-- 1 jay stackgrp 42 Mar 16 17:03 bigfile-20160309144430-01

etc.

Note que ls mostra o mesmo tempo para todos os bigfile * graças ao comando touch .

    
por 16.03.2016 / 22:13