unix - divide un enorme file .gz per riga


16

Sono sicuro che qualcuno ha avuto il bisogno di seguito, qual è un modo rapido per dividere un enorme file .gz per linea? Il file di testo sottostante ha 120 milioni di righe. Non ho abbastanza spazio su disco per comprimere l'intero file in una sola volta, quindi mi chiedevo se qualcuno fosse a conoscenza di uno script o uno strumento bash / perl che potesse dividere il file (o .zz o .txt interno) in file di linea 3x 40mn . cioè chiamandolo come:

    bash splitter.sh hugefile.txt.gz 4000000 1
 would get lines 1 to 40 mn    
    bash splitter.sh hugefile.txt.gz 4000000 2
would get lines 40mn to 80 mn
    bash splitter.sh hugefile.txt.gz 4000000 3
would get lines 80mn to 120 mn

Forse sta facendo una serie di questi una soluzione o il gunzip -c richiederebbe abbastanza spazio per decomprimere l'intero file (cioè il problema originale): gunzip -c hugefile.txt.gz | testa 4000000

Nota: non riesco a ottenere un disco aggiuntivo.

Grazie!


1
Vuoi che i file risultanti vengano nuovamente compressi?

Puoi usare il gunzip in un ipe. Il resto può essere fatto con testa e coda
Ingo,

@Tichodroma - no, non ho più bisogno che vengano decompressi. Ma non sono riuscito a memorizzare tutti i file di testo divisi contemporaneamente. Quindi vorrei ottenere la prima divisione, fare cose con essa, quindi eliminare la prima divisione e quindi ottenere la seconda divisione. Ecc. Infine rimuovendo l'originale gz
toop

1
@toop: grazie per il chiarimento. Nota che è generalmente meglio modificare la tua domanda se vuoi chiarirla, piuttosto che inserirla in un commento; in questo modo tutti lo vedranno.
sleske,

La risposta accettata è buona se si desidera solo una parte dei blocchi e non li si conosce in anticipo. Se vuoi generare tutti i blocchi contemporaneamente, le soluzioni basate sulla divisione saranno molto più veloci, O (N) invece di O (N²).
nato il

Risposte:


11

Come farlo meglio dipende da cosa vuoi:

  • Vuoi estrarre una singola parte del file di grandi dimensioni?
  • O vuoi creare tutte le parti in una volta sola?

Se vuoi una singola parte del file , la tua idea da usare gunziped headè giusta. Puoi usare:

gunzip -c hugefile.txt.gz | head -n 4000000

Ciò produrrebbe le prime 4000000 linee in uscita standard - probabilmente vorrai aggiungere un'altra pipe per fare effettivamente qualcosa con i dati.

Per ottenere le altre parti, dovresti usare una combinazione di heade tail, come:

gunzip -c hugefile.txt.gz | head -n 8000000 |tail -n 4000000

per ottenere il secondo blocco.

Forse sta facendo una serie di questi una soluzione o il gunzip -c richiederebbe abbastanza spazio per decomprimere l'intero file

No, gunzip -cnon richiede spazio su disco: fa tutto in memoria, quindi viene trasmesso allo stdout.


Se si desidera creare tutte le parti in una volta sola , è più efficiente crearle tutte con un singolo comando, perché il file di input viene letto solo una volta. Una buona soluzione è usare split; vedi la risposta di jim mcnamara per i dettagli.


1
Dal punto di vista delle prestazioni: gzip decomprime effettivamente l'intero file? O è in grado di "magicamente" sapere che sono necessarie solo 4mn di linee?
Alois Mahdal

3
@AloisMahdal: In realtà, sarebbe una buona domanda separata :-). Versione breve: gzipnon conosce il limite (che proviene da un processo diverso). Se headusato, headuscirà quando ne avrà ricevuto abbastanza e questo si propagherà a gzip(tramite SIGPIPE, vedi Wikipedia). Per tailquesto non è possibile, quindi sì, gzipdecomprimerà tutto.
sleske,

Ma se sei interessato, dovresti davvero porlo come una domanda separata.
sleske,

20

pipe per dividere usa gunzip -c o zcat per aprire il file

gunzip -c bigfile.gz | split -l 400000

Aggiungi le specifiche di output al comando diviso.


3
Questo è enormemente più efficiente della risposta accettata, a meno che non sia richiesta solo una frazione dei blocchi divisi. Per favore, vota.
b0fh,

1
@ b0fh: Sì, hai ragione. Ho votato e menzionato nella mia risposta :-).
sleske,

La migliore risposta di sicuro.
Stephen Blum,

quali sono le specifiche di output in modo che gli output siano file .gz stessi?
Quetzalcoatl,

7

Mentre lavori su uno stream (non riavvolgibile), vorrai utilizzare la forma '+ N' della coda per ottenere le linee a partire dalla linea N in poi.

zcat hugefile.txt.gz | head -n 40000000
zcat hugefile.txt.gz | tail -n +40000001 | head -n 40000000
zcat hugefile.txt.gz | tail -n +80000001 | head -n 40000000


3

Dividi direttamente il file .gz in file .gz:

zcat bigfile.gz | split -l 400000 --filter='gzip > $FILE.gz'

Penso che questo fosse ciò che OP voleva, perché non ha molto spazio.


2

Ecco uno script Python per aprire una serie di file traballanti da una directory, se necessario, comprimili con una pistola e leggili riga per riga. Utilizza solo lo spazio necessario in memoria per contenere i nomi dei file e la riga corrente, oltre a un po 'di sovraccarico.

#!/usr/bin/env python
import gzip, bz2
import os
import fnmatch

def gen_find(filepat,top):
    for path, dirlist, filelist in os.walk(top):
        for name in fnmatch.filter(filelist,filepat):
            yield os.path.join(path,name)

def gen_open(filenames):
    for name in filenames:
        if name.endswith(".gz"):
            yield gzip.open(name)
        elif name.endswith(".bz2"):
            yield bz2.BZ2File(name)
        else:
            yield open(name)

def gen_cat(sources):
    for s in sources:
        for item in s:
            yield item

def main(regex, searchDir):
    fileNames = gen_find(regex,searchDir)
    fileHandles = gen_open(fileNames)
    fileLines = gen_cat(fileHandles)
    for line in fileLines:
        print line

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Search globbed files line by line', version='%(prog)s 1.0')
    parser.add_argument('regex', type=str, default='*', help='Regular expression')
    parser.add_argument('searchDir', , type=str, default='.', help='list of input files')
    args = parser.parse_args()
    main(args.regex, args.searchDir)

Il comando print line invierà tutte le righe da std out, in modo da poter reindirizzare a un file. In alternativa, se ci fai sapere cosa vuoi fare con le righe, posso aggiungerlo allo script Python e non dovrai lasciare pezzi del file in giro.


2

Ecco un programma perl che può essere usato per leggere lo stdin e dividere le linee, eseguendo il piping di ogni gruppo ad un comando separato che può usare una variabile di shell $ SPLIT per instradarlo verso una destinazione diversa. Nel tuo caso, sarebbe invocato con

zcat hugefile.txt.gz | perl xsplit.pl 40000000 'cat > tmp$SPLIT.txt; do_something tmp$SPLIT.txt; rm tmp$SPLIT.txt'

Mi dispiace che l'elaborazione della riga di comando sia un po 'complicata, ma hai capito.

#!/usr/bin/perl -w
#####
# xsplit.pl: like xargs but instead of clumping input into each command's args, clumps it into each command's input.
# Usage: perl xsplit.pl LINES 'COMMAND'
# where: 'COMMAND' can include shell variable expansions and can use $SPLIT, e.g.
#   'cat > tmp$SPLIT.txt'
# or:
#   'gzip > tmp$SPLIT.gz'
#####
use strict;

sub pipeHandler {
    my $sig = shift @_;
    print " Caught SIGPIPE: $sig\n";
    exit(1);
}
$SIG{PIPE} = \&pipeHandler;

my $LINES = shift;
die "LINES must be a positive number\n" if ($LINES <= 0);
my $COMMAND = shift || die "second argument should be COMMAND\n";

my $line_number = 0;

while (<STDIN>) {
    if ($line_number%$LINES == 0) {
        close OUTFILE;
        my $split = $ENV{SPLIT} = sprintf("%05d", $line_number/$LINES+1);
        print "$split\n";
        my $command = $COMMAND;
        open (OUTFILE, "| $command") or die "failed to write to command '$command'\n";
    }
    print OUTFILE $_;
    $line_number++;
}

exit 0;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.