Concatenar arquivos por valores de tabela

Concatenar arquivos por valores de tabela

Eu tenho vários arquivos, cada um contendo um padrão específico em seus nomes, como ABC1234001os que carregam informações sobre determinados grupos de meus dados (tabelas de múltiplas colunas). Eu também tenho uma tabela info.tsvassim:

group1    ABC1234001    ABC1234010
group2    ABC1234011    ABC1234018
group3    ABC1234019    ABC1234028
...       ...           ...

Contém:

  • coluna "grupo", que especifica o grupo,
  • coluna "primeiro arquivo", que especifica o padrão para o primeiro arquivo (ordem alfabética) contendo informações para o grupo correspondente,
  • Coluna "último arquivo", que especifica o padrão para o último arquivo (ordem alfabética) contendo informações para o grupo correspondente.

Então, o que preciso fazer é combinar os arquivos de cada grupo em um arquivo - assim como

cat ABC123401{1..8}* >> group2.tsv

seria para o grupo2 como exemplo - durante a leitura deste info.tsvarquivo. Neste exemplo, todos os arquivos ( ABC1234011.tsv, ABC1234012.tsv, ABC1234013.tsv, ABC1234014.tsv, ABC1234015.tsv, ABC1234016.tsv, ABC1234017.tsv, ABC1234018.tsv) são concatenados em um group2.tsvarquivo

O que vou fazer é o seguinte:

while read $file; do
  #assign columns to variables like $1="group", $2="firstfile", $3="lastfile"
  cat *{$2..$3}* > $1.tsv;
done < info.tsv

Mas não tenho certeza de como alterar iterativamente as variáveis ​​para essa abordagem. Talvez usar awkseja mais útil, mas não sei. O script deve produzir vários arquivos chamados group1.tsv, group2.tsv, que contêm o conteúdo dos arquivos correspondentes do "primeiro arquivo" ao "último arquivo" na tabela. Por favor, ajude-me a escrever o script para fazer isso.

Responder1

O script abaixo assume que todos os arquivos que você deseja concatenar correspondem ao padrão *.tsv. Se você sabe que todos eles correspondem ABC*.tsv, talvez você queira usar esse padrão no início do script no lugar de *.tsv.

Além disso, o script assume que todos os nomes de arquivos que vão para um grupo específico são gerados como uma sublista contínua da lista que *.tsvse expande para.

#!/bin/sh

set -- *.tsv

while read -r group first last; do
        collect=false

        for name do
                if ! "$collect"; then
                        [ "$name" = "$first.tsv" ] || continue
                        collect=true
                fi

                if "$collect"; then
                        cat -- "$name"
                        [ "$name" = "$last.tsv" ] && break
                fi
        done >"$group.tsv"

done <info.tsv

O script define a lista de parâmetros posicionais para a lista de nomes correspondentes *.tsv. Em seguida , ele lê os três campos de cada linha info.tsvnas variáveis group​​e .firstlast

Para cada linha lida info.tsvdesta forma, a lista de parâmetros posicionais é varrida em busca de nomes que correspondam ao primeiro nome do grupo. Uma vez encontrado esse primeiro nome, definimos um flag, collect, que informa à lógica do script para iniciar a coleta dos dados dos arquivos nomeados na lista de parâmetros posicionais, a partir da posição atual na lista. Isso termina quando encontramos um nome que corresponde ao sobrenome de um grupo.

Observe que truee falseaqui estão sendo usados ​​como comandos e não como strings simples. O valor armazenado na variável $collectestá sendo executado, if ! "$collect"o que significa que o script executará um dos dois comandos internos do shell trueou false. O shell não possui nenhuma palavra-chave especial para verdadeiro ou falso como algumas outras linguagens (por exemplo, Python).

Teste:

$ ls
script
$ touch ABC{1234001..1234030}.tsv
$ for name in ABC*.tsv; do printf 'Name: %s\n' "$name" >"$name"; done
$ cat ABC1234015.tsv
Name: ABC1234015.tsv
$ cat >info.tsv <<END_DATA
group1 ABC1234001 ABC1234010
group2 ABC1234025 ABC1234030
END_DATA
$ ./script
$ cat group1.tsv
Name: ABC1234001.tsv
Name: ABC1234002.tsv
Name: ABC1234003.tsv
Name: ABC1234004.tsv
Name: ABC1234005.tsv
Name: ABC1234006.tsv
Name: ABC1234007.tsv
Name: ABC1234008.tsv
Name: ABC1234009.tsv
Name: ABC1234010.tsv
$ cat group2.tsv
Name: ABC1234025.tsv
Name: ABC1234026.tsv
Name: ABC1234027.tsv
Name: ABC1234028.tsv
Name: ABC1234029.tsv
Name: ABC1234030.tsv

Conforme mencionado nos comentários a esta resposta, a maneira como eu desenvolveria esse script para meu uso pessoal seria deixá-lo assim:

#!/bin/sh

while read -r group first last; do
        collect=false

        for name do
                filename=$( basename "$name" )

                if ! "$collect"; then
                        [ "$filename" = "$first.tsv" ] || continue
                        collect=true
                fi

                if "$collect"; then
                        cat -- "$name"
                        [ "$filename" = "$last.tsv" ] && break
                fi
        done >"$group.tsv"

done

Observe a exclusão do setcomando na parte superior (será substituído pelos argumentos da linha de comando) e a exclusão do redirecionamento de info.tsv(será substituído por um redirecionamento na linha de comando). Também introduzi uma filenamevariável que conterá o componente do nome do arquivo dos nomes de caminho fornecidos na linha de comando.

Eu então executaria o script assim:

$ ./script ABC*.tsv <info.tsv

O que consegui com isso é um script independente de onde a lista de grupos de entrada está armazenada ou como é chamada, e que não se importa com o ABCnome dos arquivos (desde que tenham um .tsvsufixo de nome de arquivo) ou onde estão armazenados .

Responder2

Sua abordagem é uma boa ideia, mas infelizmente não funcionará porque as variáveis ​​não são expandidas dentro das expansões de chaves:

$ echo {1..5}
1 2 3 4 5
$ a=1
$ b=5
$ echo {$a..$b}
{1..5}

Você pode contornar isso usando eval:

sed 's/ABC//g' info.tsv | 
    while read -r group start end; do 
        files=( $(eval echo ABC{$start..$end}.tsv) )
        cat "${files[@]}" > "$group.tsv"; 
    done 

Isso primeiro removerá todas as instâncias do ABCarquivo info.tsvpara que possamos obter apenas os números. Observe que isso pressupõe a estrutura de dados exata que você nos mostrou. Se ABCtambém puder estar presente no nome do grupo, isso será interrompido.

Após a remoção ABC, o resultado é canalizado para o whileloop que lê três variáveis $group: $starte $end. Eles são então passados ​​para evalo qual expandirá a variável antes de chamar a expansão de chaves, permitindo que você obtenha uma lista de nomes de arquivos:

$ eval echo ABC{1..5}
ABC1 ABC2 ABC3 ABC4 ABC5

O resultado de evalé armazenado no $filesarray, que é passado como entrada para cat:

cat "${files[@]}" > "$group.tsv";

Responder3

Se bem entendi, aqui está uma opção

$ while IFS= read -r i; do
    f=$(echo "$i" | cut -d' ' -f1)
    cat $(echo "$i" | cut -d' ' -f2- | sed -E 's/([0-9])\s+/\1.tsv /;s/([0-9])$/\1.tsv /') > "$f.txt"
  done < info.tsv

  • f=$(echo "$i" | cut -d' ' -f1)recupera o nome do grupo.
  • cat $(cut -d' ' -f2- | sed -E 's/([0-9])\s+|([0-9])$/\1.tsv /g')concatena a lista de arquivos na linha.

informação relacionada