
Eu tenho vários arquivos, cada um contendo um padrão específico em seus nomes, como ABC1234001
os que carregam informações sobre determinados grupos de meus dados (tabelas de múltiplas colunas). Eu também tenho uma tabela info.tsv
assim:
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.tsv
arquivo. 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.tsv
arquivo
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 awk
seja 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 *.tsv
se 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.tsv
nas variáveis group
e .first
last
Para cada linha lida info.tsv
desta 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 true
e false
aqui estão sendo usados como comandos e não como strings simples. O valor armazenado na variável $collect
está sendo executado, if ! "$collect"
o que significa que o script executará um dos dois comandos internos do shell true
ou 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 set
comando 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 filename
variá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 ABC
nome dos arquivos (desde que tenham um .tsv
sufixo 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 ABC
arquivo info.tsv
para que possamos obter apenas os números. Observe que isso pressupõe a estrutura de dados exata que você nos mostrou. Se ABC
também puder estar presente no nome do grupo, isso será interrompido.
Após a remoção ABC
, o resultado é canalizado para o while
loop que lê três variáveis $group
: $start
e $end
. Eles são então passados para eval
o 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 $files
array, 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.