Dividir para vários arquivos com base na primeira coluna quando corresponder a intervalos numéricos em outra coluna com o awk

3

Eu tenho um arquivo de texto que tem várias colunas dessa maneira:

 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

Então eu filtra da seguinte maneira, digamos, para a coluna dois.

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

Obtendo isso:

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

Eu quero imprimir em um arquivo diferente para todos os intervalos contínuos na coluna um, portanto, para este exemplo, eu teria três arquivos de saída:

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

Como posso fazer isso? Como posso estender essa rotina para todas as colunas no meu arquivo? Eu gostaria de ter uma solução geral para esse problema (não dependente dos intervalos específicos fornecidos neste exemplo) porque eu quero aplicá-lo a vários arquivos.

    
por webbynator 14.08.2017 / 20:16

3 respostas

2

O comando one liner awk :

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

Dar resultado (se você não se importar com o nome dos arquivos)

==> 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
    
por 14.08.2017 / 22:44
1

Abaixo está um programa awk . Você pode jogar com a coluna e o limite dado nos argumentos:

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() }
'

Observe que as linhas contínuas são mantidas na memória até que sejam salvas. Se você tiver centenas de milhões de linhas contínuas, essa solução deve ser adaptada (salvando cada linha em um arquivo temporário e renomeando-a quando a última linha de um bloco contínuo for atendida).

    
por 14.08.2017 / 22:49
0
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__.tmp/
   /,/!s/.*/&w file_&.tmp/
' | ed -s inputfile -

Aqui usamos as ferramentas awk/sed/ed . Awk primeiro gera números de linha para todas essas linhas são menores que 104. Para outras, imprime uma linha vazia. Então, Sed entra e observa os intervalos de linha de uma linha não vazia para a próxima linha vazia. E armazena os números de linha no porão. Agora, dois tipos de intervalos podem ser encontrados: n, m ou simples n. Usando estes nós construímos um conjunto de comandos ed que deve transformar esses intervalos para: n, mw file_n_m.tmp e file_n.tmp respectivamente. Em seguida, o ed trabalha prontamente no arquivo de entrada usando esse script ed gerado dinamicamente para obter as linhas em seus arquivos .tmp.

Aqui está uma maneira de realizar sua tarefa com 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

Resultados:

Dada a entrada, os 3 arquivos a seguir são criados: file_1_5.tmp file_7_8.tmp file_10.tmp com o conteúdo

% mais arquivo_1_5.tmp arquivo_7_8.tmp arquivo_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

Explicação:

Primeiro, a ideia básica de nível superior: Observamos se o segundo campo está atrás do numérico 104. No cenário em que isso ocorre, significa que precisamos imprimir o intervalo anterior. Apenas tenha em mente que, para intervalos unilength, o nome do arquivo é modificado de acordo para refletir isso.

No outro caso, quando estamos no processo de acumular o intervalo atual, ($ F [1] < 104), tenha em mente que, ao fazer isso, se atingirmos eof , precisaremos imprimir o intervalo agora.

P.S .: O comando system é criado dinamicamente usando um formato criado dinamicamente, seus dados são o primeiro e o segundo campos e, finalmente, o nome do arquivo .tmp é criado de acordo com o intervalo.

$a e $b são números de linha inicial / final do intervalo. Seus estados nos informarão para tomar as decisões adequadas.

    
por 14.08.2017 / 22:43