Diviser en plusieurs files en fonction de la première colonne lorsque les plages numériques sont appariées dans une autre colonne avec awk

J'ai un file text qui a plusieurs colonnes de cette manière:

1 102.0184 109.5487 107.3716 2 100.2430 107.5874 108.7558 3 103.9029 105.7406 106.9046 4 102.7495 108.1275 104.4731 5 102.8825 105.6664 107.2141 6 104.5323 108.8850 106.0267 7 103.0479 107.3056 105.5108 8 101.2433 108.6113 107.2551 9 104.4821 108.4339 105.9864 10 101.5879 106.3859 102.825 

Ensuite, je le filter de la façon suivante, disons, pour la deuxième colonne.

 awk '$2<104 {print $1,$2}' file.txt 

Obtention de ceci:

 1 102.0184 2 100.2430 3 103.9029 4 102.7495 5 102.8825 7 103.0479 8 101.2433 10 101.5879 

Je veux imprimer dans un file différent pour toutes les plages continues de la première colonne, donc pour cet exemple j'aurais trois files de sortie:

 file_1-5.tmp 1 102.0184 2 100.2430 3 103.9029 4 102.7495 5 102.8825 
 file_7-8.tmp 7 103.0479 8 101.2433 
 file_10.tmp 10 101.5879 

Comment puis je faire ça? Comment puis-je étendre cette routine pour toutes les colonnes de mon file? Je voudrais avoir une solution générale à ce problème (ne dépend pas des gammes particulières données dans cet exemple) parce que je veux l'appliquer à plusieurs files.

La command un liner awk :

 awk -v P=-1 '$2<104 {if ($1>P+1)N=$1;P=$1;print $1,$2>"file"N".tmp"}' 

Donner des résultats (si cela ne vous dérange pas le nom des files)

 ==> file1.tmp <== 1 102.0184 2 100.2430 3 103.9029 4 102.7495 5 102.8825 ==> file7.tmp <== 7 103.0479 8 101.2433 ==> file10.tmp <== 10 101.5879 

Voici un programme awk . Vous pouvez jouer avec la colonne et le seuil donné dans les arguments:

 awk -v column=2 -v threshold=104 ' function save() { if (lines != "") print lines >"file_" first "-" last ".txt" } ! ($column < threshold) { save() first = last = lines = "" next } { if (first == "") first = $1 last = $1 lines = lines $1 OFS $column ORS } END { save() } ' 

Notez que les lignes continues sont conservées en memory jusqu'à ce qu'elles soient sauvegardées. Si vous avez des centaines de millions de lignes continues, cette solution doit être adaptée (en sauvegardant chaque ligne dans un file temporaire, puis en la renommant lorsque la dernière ligne d'un bloc continu est satisfaite).

 awk '{$0 = $2 < 104 ? NR : RS}1' inputfile | sed -Ee ' $aq /./,/^$/!d;/./H;$ba;/^$/ba;d;:a g;s/.//;s/\n.*\n|\n/,/;x;s/.*//;x s/(.*),(.*)/&w file_\1_\2.tmp/ /,/!s/.*/&w file_&.tmp/ ' | ed -s inputfile - 

Ici, nous utilisons les outils awk/sed/ed . Awk génère d'abord des numéros de ligne pour toutes les lignes inférieures à 104. Pour d'autres, il imprime une ligne vide. Ensuite, Sed entre et regarde les lignes allant de la ligne non vide à la ligne vide suivante. Et stocke les numéros de ligne dans la soute. Maintenant, deux types de plages peuvent être trouvés: n, m ou n simple. A l'aide de ceux-ci, nous construisons un jeu de commands ed qui doit transformer ces plages en: n, mw file_n_m.tmp et file_n.tmp respectivement. Puis ed travaille rapidement sur le file d'input en utilisant ce script ed généré dynamicment pour get les lignes dans leurs files .tmp.

Voici une façon d'accomplir votre tâche avec Perl :

 perl -lane ' BEGIN { $fmt = sprintf q[%s%%s\n%s], (chr 39)x2; } if ( $F[1] < 104 ) { push @A, "@F[0,1]"; if ( eof ) { my $f = join $", q<printf>, $fmt, map(qq[\"$_\"], @A), q[>], (( ! defined $a ) ? qq[file_${.}.tmp] : qq[file_${a}_${.}.tmp]); system("$f"); } else { $a //= $.; } } else { next if ! defined $a; $b //= $.-1; my $f = join $", q<printf>, $fmt, map(qq[\"$_\"], @A), q[>], (( $a == $b ) ? qq[file_$b.tmp] : qq[file_${a}_$b.tmp]); system("$f"); ($a, $b, @A) = (undef)x2; } ' yourfile 

Résultats:

Compte tenu de la saisie, les 3 files suivants sont créés: file_1_5.tmp file_7_8.tmp file_10.tmp avec le contenu

% plus file_1_5.tmp file_7_8.tmp file_10.tmp

 :::::::::::::: file_1_5.tmp :::::::::::::: 1 102.0184 2 100.2430 3 103.9029 4 102.7495 5 102.8825 :::::::::::::: file_7_8.tmp :::::::::::::: 7 103.0479 8 101.2433 :::::::::::::: file_10.tmp :::::::::::::: 10 101.5879 

Explication:

Tout d'abord, l'idée de base: nous surveillons le deuxième champ qui se trouve derrière le chiffre 104. Dans le scénario, cela signifie que nous devons imprimer la plage précédente. Il suffit de garder à l'esprit que pour les plages unilength, le nom de file est modifié en conséquence pour refléter cela.

Dans l'autre cas, lorsque nous sums en train d'accumuler la plage actuelle ($ F [1] <104), gardez à l'esprit que si nous eof nous devons imprimer la plage maintenant.

PS: La command system est créée dynamicment à l'aide d'un format créé dynamicment, ses données sont les premier et deuxième champs, et enfin le nom de file .tmp est créé en fonction de la plage.

$a et $b sont les numéros de début / fin de plage. Leurs états nous informer de prendre les bonnes décisions.