2012-10-23 6 views
7

Ho un file di dati che ho bisogno di trasformare con espressioni regolari. Più in particolare, ho bisogno di mantenere le prime 6 colonne lo stesso, e dalla settima colonna in poi, selezionare solo le colonne dispari e quindi mettere insieme i campi di ogni coppia di righe consecutive. So che sembra un po 'complicato, quindi chiarirò questo attraverso un esempio. Questo è il mio file di dati originali (che potrebbe avere qualsiasi numero di colonne):Selezionare le colonne dispari, quindi mettere insieme i campi delle righe consecutive

A B C D E F 11 12 13 14 15 16 17 18 
A B C D E F 21 22 23 24 25 26 27 28 
A B C D E F 31 31 33 34 35 36 37 38 
A B C D E F 41 42 43 44 45 46 47 48 
A B C D E F 51 52 53 54 55 56 57 58 
A B C D E F 61 62 63 64 65 66 67 68 
A B C D E F 71 72 73 74 75 76 77 78 
A B C D E F 81 82 83 84 85 86 87 88 

ho capito che posso mantenere i primi 6 colonne e quindi eliminare quelle dispari con

awk '{for (i = 1; i <= NF; i++) if (i < 7 || i % 2 == 1) printf $i OFS}; {print ""} 

essendo questo il risultato:

A B C D E F 11 13 15 17 
A B C D E F 21 23 25 27 
A B C D E F 31 33 35 37 
A B C D E F 41 43 45 47 
A B C D E F 51 53 55 57 
A B C D E F 61 63 65 67 
A B C D E F 71 73 75 77 
A B C D E F 81 83 85 87 

Ma dopo che devo unire i campi di ogni coppia di righe consecutive, in questo modo:

Stavo pensando di usare sed o awk per rendere l'intero processo, dal momento che i miei file di dati sono enormi e ho bisogno di trasformarli in modo efficiente, ma non riuscivo a trovare un modo per fare anche la seconda trasformazione. Qualsiasi aiuto sarebbe molto apprezzato.

risposta

3

Ecco un modo utilizzando GNU awk. Esegui come:

awk -f script.awk file.txt 

Contenuto del script.awk:

{ 
    getline line 
    split(line, array) 
    k = 6 
    n = ((NF - k) % 2 == 0) ? 1 : 0 

    for (i=1; i<=k; i++) { 
     printf $i OFS 
    } 

    for (j=7; j<=NF-n; j+=2) { 
     x = $j OFS array[j] 
     printf (j < NF - n) ? x OFS : x "\n" 
    } 
} 

risultati:

A B C D E F 11 21 13 23 15 25 17 27 
A B C D E F 31 41 33 43 35 45 37 47 
A B C D E F 51 61 53 63 55 65 57 67 
A B C D E F 71 81 73 83 75 85 77 87 
+0

Apprezzo molto la tua risposta, è esattamente quello che stavo cercando. Proverò sia la tua soluzione che quella che ho appena scoperto, per testare quale sia più veloce (anche se a prima vista credo che la tua sia migliore). – Serchu

+1

@Serchu: Ho finito di modificare la mia risposta. L'ho reso un po 'più generale (e anche leggermente più criptico). Ora gestirà i file che hanno un numero pari di colonne o un numero dispari di colonne. Puoi persino impostare il numero di colonne iniziali da conservare. HTH. – Steve

+0

Capisco la tentazione di usare getline qui, ma è quasi sempre meglio evitarlo poiché rende difficili i cambiamenti dei requisiti semplici (tra molti altri avvertimenti - vedi http://awk.info/?tip/getline). Cosa succede se, ad esempio, si desidera mantenere anche il conteggio di tutte le linee che contengono 45? Con una soluzione non-getline devi solo aggiungere "/ 45/{C++}" al corpo di awk ma con una soluzione getline devi farlo E aggiungi "if (line ~/45 /) {C++}" dopo il getline, complicando le cose e creando codice duplicato solo per un piccolo cambiamento di requisiti concettualmente banale. –

2

Prova questo:

# d.awk 
{ 
    if (NR % 2 == 1) { 
     a = $7 
     b = $9 
     c = $11 
     d = $13 
    } else { 
     print $1, $2, $3, $4, $5, $6, a, $7, b, $9, c, $11, d, $13 
    } 
} 

Risultato:

% gawk -f d.awk data 
A B C D E F 11 21 13 23 15 25 17 27 
A B C D E F 31 41 33 43 35 45 37 47 
A B C D E F 51 61 53 63 55 65 57 67 
A B C D E F 71 81 73 83 75 85 77 87 
2

soluzione Perl:

perl -ane ' 
    BEGIN { $, = " " } 
    if ($. % 2) { 
     @p = (@F[0..5], @F[grep 1-$_ % 2, 6 .. $#F]) 
    } else { 
     print @p[0..5], (map { $p[$_], $F[2 * $_ - 6] } 6 .. $#F), "\n" 
    }' 
+1

Ah, Perl. Sempre un piacere per gli occhi :) –

+2

@Tichodroma: a differenza della tua soluzione, funziona per qualsiasi numero di colonne come specificato nella domanda. – choroba

+0

Vero, ma non richiesto :) –

0

vengo con questo:

{ 
    if (NR % 2 == 1){ 
     for(i = 7; i <= NF; i += 2){ 
      array[i] = $i 
     } 
    } 
    else{ 
     printf "%s %s %s %s %s %s", $1, $2, $3, $4, $5, $6 
     for(i = 7; i <= NF; i += 2){ 
      printf " %s %s", array[i], $i 
     } 
     print "" 
    } 
} 

Si lavora per l'esempio del post di apertura, con qualsiasi numero di campi. La mia unica preoccupazione al riguardo è che i miei file di dati attuali contengano 2774938 campi e, dato che sono nuovo ad afk, non so se questo è un modo efficace per farlo.

0
awk ' 
NR%2 { split($0,a); next } 
{ 
    for(i=7;i<NF;i+=2) { 
     $(i+1) = $i 
     $i = a[i] 
    } 
} 
1' file 

o se si preferisce una soluzione "carino" con alcuni avvertimenti (ma che lavorerà con i dati di esempio spedita):

awk ' 
!(NR%2) { printf fmt,$7,$9,$11,$13 } 
{ for (i=8;i<=NF;i+=2) $i="%s"; fmt=$0"\n" } 
' file 
0

Questo potrebbe funzionare per voi (GNU sed):

sed -r 's/(\s?\S+)\s\S+/\1/4g;h;s/.*//;N;s/(\s?\S+)\s\S+/\1/4g;H;g;s/^(.*)(.*\n)\n\1/\1\n\2/;h;s/[^\n]*\n//;:a;s/([^ \n]*)\n([^ \n]*)/\n\2 \1\n/g;s/\n \n?| \n/\n/g;/\n[^\n ]*$/!ba;y/\n/ /;H;x;s/\n.*\n//' file