Aufteilen der Datei für jeweils 10000 Zahlen (keine Zeilen)

8

Ich habe eine Datei, die wie folgt aussieht:

chr19   61336212        +       0       0       CG      CGT    
chr19   61336213        -       0       0       CG      CGG    
chr19   61336218        +       0       0       CG      CGG    
chr19   61336219        -       0       0       CG      CGC    
chr19   61336268        +       0       0       CG      CGG    
chr19   61336269        -       0       0       CG      CGA    
chr19   61336402        +       0       0       CG      CGG    
chr19   61336403        -       0       0       CG      CGT    

Ich möchte diese Datei für jedes 10000-Intervall des 2. Feldes aufteilen (NICHT Zeilen, sondern Zahlenintervall). Für diese Datei möchte ich also von der ersten Zeile (der Zeile mit 61336212) in die Zeile mit oder bis 61346211 (61336212 + 9999), dann von 61346212 bis 61356211 usw. aufteilen. Wie Sie sehen können, sind die Zahlen im 2. Feld / in der 2. Spalte nicht "gefüllt".

Gibt es eine Möglichkeit, dies zu tun?

Agathusie
quelle
Wenn in Ihrem Beispiel die nächste Zahl nach 61346211 beispielsweise 61346220 lautet, würden Sie erwarten, dass die zweite Ausgabedatei den Bereich ab 61346212 oder 61346220 abdeckt?
Joe Lee-Moyet
Der zweite Bereich sollte von 61346212 abdecken.
Agathusie

Antworten:

13
awk 'NR==1 {n=$2}
     {
       file = sprintf("file.%.4d", ($2-n)/10000)
       if (file != last_file) {
         close(last_file)
         last_file = file
       }
       print > file
     }'

Würde schreiben an file.0000, file.0001... (die Nummer ist int(($2-n)/10000)wo nist $2für die erste Zeile).

Beachten Sie, dass wir Dateien schließen, sobald wir aufgehört haben, in sie zu schreiben, da Sie sonst die Grenze für die Anzahl gleichzeitig geöffneter Dateien nach einigen hundert Dateien erreichen würden (GNU awkkann diese Grenze umgehen, aber dann verschlechtern sich die Leistungen schnell).

Wir gehen davon aus, dass diese Zahlen immer steigen.

Stéphane Chazelas
quelle
3
Können Sie erklären, was passiert?
Fiximan
Können Sie erklären, was hier los ist? Ebenso wie im folgenden Kommentar gibt es keine Möglichkeit, die Länge des Ausgabedateinamens konstant zu halten, z. B. Datei.0000, Datei.0001 anstelle von Datei.1 Datei.2 .. Datei.100 .. Datei..2320?
Agathusie
1
@Fiximan, ich glaube nicht, dass ich viel mehr erklären kann, ohne den Code zu paraphrasieren. Welchen Teil finden Sie unklar?
Stéphane Chazelas
Nun, ich verstehe die Dateinamengenerierung file = ..., aber wie funktioniert die Iteration? Es gibt keinen Teil, der etwas sagt n = n + 10000oder sagt lower_boundary <= $2 < upper_boundary. Im Allgemeinen ist das Ganze if (file != last_file) { close(last_file) ; last_file = file }nicht in meiner Liga
Fiximan
1
@Fixman, na ja, das würde ich als Paraphrasierung bezeichnen if (file != last_file): Wenn die aktuelle Datei nicht mit der vorherigen Datei identisch ist, schließen Sie die vorherige Datei (haben Sie also immer nur eine Datei geöffnet (wir müssen sie nicht behalten) alle offen wie andere Lösungen))
Stéphane Chazelas
7

Hack Einzeiler Version. Vielleicht besser für Code Golf geeignet als dieses Forum. Dadurch werden split1, split2, split3 usw. als Dateinamen generiert.

awk '{if($2>b+9999){a++;b=$2}print >"split" a}' file.txt

Um Ausgabedateien mit den Namen split001, split002, split003 zu erhalten, ist Folgendes erforderlich sprintf:

awk '{if($2>b+9999){a++;b=$2}print >sprintf("split%03d",a)}' file.txt

Verwenden Sie Perl, um das von @ Stéphane Chazelas identifizierte Problem der Gawk-Verlangsamung zu vermeiden:

perl -ne '(undef,$a)=split(/\s+/,$_);if($a>$b+9999){$c++;$b=$a}open(D,sprintf(">>ysplit%03d",$c));print D' <file.txt
Steve
quelle
1
Gibt es für diese Methode eine Möglichkeit, die Dateinamen nacheinander zu ändern? Dies gibt split1 .... split100 ... split1000 aus, aber etwas mehr in der Zeile von split00001 ... split 00100 .. split01000 ..?
Agathusie
1
Sicher, sprintfjetzt wurde zusätzliche Magie hinzugefügt.
Steve
Beachten Sie, dass, wenn die Eingabe 0, 9999, 12000, 19999, 21000, 22000 hat, 0, 9999 in Datei1, aber 12000, 19999, 21000 in Datei2 eingefügt werden, was mit den Anforderungen ungerade zu sein scheint.
Stéphane Chazelas
1
Beachten Sie, dass dies die Grenze für die Anzahl gleichzeitig geöffneter Dateien nach einigen hundert Dateien erreichen würde (GNU awk kann diese Grenze umgehen, aber dann verschlechtert sich die Leistung schnell).
Stéphane Chazelas
1
Ja. Ich habe gerade das von Ihnen erwähnte Problem bemerkt.
Agathusie
4
#!/bin/bash
first=$( head -n1 file | awk -F" +" '{print $2}' )
last=$( tail -n1 file | awk -F" +" '{print $2}' )
for (( i=$first ; i<=$last ; i=i+10000 )) ; do
   awk -v start=$i -v end=$(($i+10000)) 'BEGIN { FS == " +" } { if ( $2 >= start && $2 < end ) print $0 }' file \
   >> interval_"$i"_to_"$(( $i+10000 ))"
done

Test mit auf 100 eingestelltem Intervall:

more inter*
::::::::::::::
interval_61336212_to_61346212
::::::::::::::
chr19   61336212        +       0       0       CG      CGT    
chr19   61336213        -       0       0       CG      CGG    
chr19   61336218        +       0       0       CG      CGG    
chr19   61336219        -       0       0       CG      CGC    
chr19   61336268        +       0       0       CG      CGG    
chr19   61336269        -       0       0       CG      CGA    
::::::::::::::
interval_61336312_to_61346312
::::::::::::::
chr19   61336402        +       0       0       CG      CGG    
chr19   61336403        -       0       0       CG      CGT  

Hinweis: Erzeugt leere Dateien für leere Intervalle. Fügen Sie zum Entfernen leerer Dateien Folgendes hinzu:

for file in interval* ; do
  if [ ! -s "$file" ] ; then
    rm "$file"
  fi
done

Läuft die Datei für jeden Schritt in der forSchleife über, daher nicht die effizienteste.

Fiximan
quelle
3

Wenn Sie nur Berechnung, nicht Zeilenzählung meinen:

awk 'NR==1 || n+10000<$2{n=$2; portion++}{print > FILENAME "." portion}' file
Costas
quelle
Beachten Sie, dass, wenn die Eingabe 0, 9999, 12000, 19999, 21000, 22000 hat, 0, 9999 in Datei1, aber 12000, 19999, 21000 in Datei2 eingefügt werden, was mit den Anforderungen ungerade zu sein scheint.
Stéphane Chazelas
Beachten Sie, dass dies die Grenze für die Anzahl gleichzeitig geöffneter Dateien nach einigen hundert Dateien erreichen würde (GNU awk kann diese Grenze umgehen, aber dann verschlechtert sich die Leistung schnell).
Stéphane Chazelas
@ StéphaneChazelas Ich bin mir nicht sicher, ob ich dich klar verstehe. Wenn Sie 21000 in der 3. Datei möchten, verwenden Sie 9999 statt 10000.
Costas
Nach meinem Verständnis der Frage möchte das OP Zeilen mit 0 bis 9999 in der ersten Datei, 10000 bis 19999 in der zweiten Datei.
Stéphane Chazelas