Bash para unir colunas de vários arquivos

0

Tenho cerca de 20 arquivos em pastas diferentes para as quais criei um arquivo separado chamado pathtofiles.in , que lista as informações caminho :

/home/users/gray_wolf/unix/File_1.tsv
/home/users/gray_wolf/unix/File_2.tsv
.
.
.

Cada arquivo contém cerca de 11 colunas e aproximadamente 100.000 linhas . Exemplos:

File_1:

Chromosome    begin    end    .....
chr1          1000     2000
chr1          2000     3000
chr1          4000     5000
chr1          5000     6000
chr1          10000    12000
chr1          12000    13000

Arquivo_2:

Chromosome    begin    end    .....
chr1          1000     2000
chr1          4000     5000
chr1          5000     6000
chr1          6000     7000
chr1          10000    12000
chr1          13000    14000

Formato de arquivo obrigatório final:

Chromosome    begin    end     Column5                    column8 
chr1          1000     2000    File1,File2,File3...       File1,File2,File3...
chr1          2000     3000    File1,File2(0),File3       File1,File2(0),File3...
chr1          4000     5000    File1,File2,File3...       File1,File2,File3...
chr1          5000     6000    File1,File2,File3...       File1,File2,File3...
chr1          6000     7000    File1,File2,File3...       File1,File2,File3...
chr1          10000    12000   File1,File2,File3...       File1,File2,File3...
chr1          12000    13000   File1,File2,File3...       File1,File2,File3...
chr1          13000    14000   File1(0),File2,File3...    File1(0),File2,File3...

Quando eu passar pathtofile.in para script.sh da seguinte forma:

./script.sh < pathtofile.in

... o script deve ler os arquivos fornecidos em pathtofiles.in e saída uma lista separada da guia 5 -column. As primeiras colunas 3 de saída devem ser as colunas 3 de cada arquivo combinado . A coluna de saída 4 th deve ser a linha correspondente da coluna th 5 de todos os arquivos de entrada (na mesma ordem que pathtofiles.in ), separados por vírgulas. A coluna de saída 5 th deve ser a linha correspondente da coluna 8 th de todos os arquivos de entrada, separados por vírgulas.

No caso de uma determinada linha de entrada não possuir uma coluna 8 , (ou coluna 5 ), imprima um "(0)" em seu lugar.

Eu tentei cortar as colunas separadamente e usar as funções paste / join , mas como há diferentes números de linhas em cada arquivo, o pedido fica errado. Como posso fazer isso usando o awk ou algum outro comando executado sob o bash ?

Obrigado antecipadamente.

~ M

    
por user3668772 11.08.2015 / 16:18

2 respostas

1

para uma solução simples: paste os três arquivos juntos e, em seguida, obtenha as colunas desejadas:

paste -d' ' file1 file2 file3  |\
awk 'BEGIN { FS = " +" } { NR ==1} { printf "%-10s%-7s%-7s  %-12s  %-12s\n" $1,$2,$3,$6,$7 } { NR >=2 } { printf "%-10s%-7s%-7s  %s,%s%s  %s%s%s\n" $1,$2,$3,$6,$7,$8,$9,$10,$11 } '

Isso terá que ser adotado de acordo com seus arquivos e preferências para o formato de saída. Explicações:

1) paste -d' ' - > mesclar os arquivos de árvore na direção vertical, use o espaço como -d elimitador.

2) canalize-o para awk (e continue o comando na nova linha |\ para legibilidade aqui)

2.1) BEGIN { FS = " +" } - para todos os que seguem, use um ou mais (+) espaços como delimitador de campo

2.2) na primeira linha { NR ==1} print fields 1,2,3,6,7 ( $1,$2 ... ) com o seguinte formato (entre aspas duplas)

%-10s uma cadeia fixa de 10 caracteres (restante preenchido com espaços, alinhados à esquerda).

duas vezes o mesmo com 7 caracteres, depois dois espaços, uma cadeia com 12 caracteres, dois espaços e 12 caracteres. Adicione uma nova linha \n no final.

(encontrado na parte { printf "%-10s%-7s%-7s %-12s %-12s\n" $1,$2,$3,$6,$7 } )

2.2) os dados: das linhas dois e maiores { NR >=2 } imprimir colunas $1,$2,$3,$6,$7,$8,$9,$10,$11 com o formato %-10s%-7s%-7s %s,%s,%s %s,%s,%s\n

semelhante ao acima, mas agora, e. colunas 6,7,8 são de comprimento arbitrário e separadas por uma vírgula %s,%s,%s

Espero que isso ajude você a criar a saída conforme necessário.

    
por 11.08.2015 / 16:54
0

Você vai querer um programa que tenha todos os seus arquivos de entrada abertos ao mesmo tempo. awk tem uma sintaxe getline <file , de modo que perl ou seria boas escolhas. Ou qualquer outra linguagem de alto nível que você conheça.

Eu iria com perl para isso. Há link com módulos perl especificamente para processar formatos de arquivo de dados de sequência genética.

Eu consegui meio caminho e percebi que pode ser mais complexo do que eu pensava. Você tem que fazer um tipo de algoritmo de diff multi-way para lidar com o caso geral de quando um dos arquivos de entrada tem um valor de coluna 2/3 diferente dos outros. Você não pode simplesmente continuar lendo as linhas até chegar a uma correspondência para o par inicial / final que está procurando, porque pode não haver uma.

Então, acho que sua melhor aposta é uma fila de prioridade ou algo para obter entradas classificadas. Para cada arquivo, leia até que a linha que você está inserindo na fila fique atrás de uma que estava lá antes de você iniciar o arquivo atual. (Ou, até a linha que você acabou de ler é a nova cauda do pqueue).

Se todos os seus arquivos se encaixarem na memória ao mesmo tempo, construir a saída com um array associativo (indexado por column2: column3) tornará mais fácil o código. Então você não precisa de uma fila de prioridades ou para descobrir qual arquivo avançar em seguida.

Aqui está o código que tenho até agora. Ele apenas percorre as linhas de entrada sem manipular o caso fora de ordem ou a mesclagem. Parei quando percebi que isso era maior do que o razoável para uma resposta de troca de pilha, mas isso pode lhe dar um começo.

#!/usr/bin/perl -w

my @f = @ARGV;  # list of files to process


sub getfields($) {
    my $file = $_[0];
    my $ln = <$file> or return ();  # sentinel for EOF
    my @fl = split ' ', $ln, 9;
    return ( $fl[0], $fl[1], $fl[2], $fl[4], $fl[7] );
}

# open each filename in @f, storing the file handles in @f.
foreach (@f) {
    open $_, '<', $_  or die "opening $_: $!";
}

my $newdata = 0;
do {
    $newdata = 0;
    foreach my $fd (@f) {
    my @fl = getfields($fd);
    next if ! (@fl);  # end of file on $fd.  TODO: take it out of @f?
    $newdata = 1;
    print join("|", @fl), "\n";  # debug
    }
} while ($newdata);  # done when all files are EOF
    
por 11.08.2015 / 19:25

Tags