Estou tentando construir um wrapper para executar uma ferramenta várias vezes e, em seguida, concatenar alguns dos resultados. Gostaria de passar dois conjuntos de arquivos para meu script wrapper e, em seguida, executar a ferramenta para cada par de arquivos. Gostaria que ela se comportasse assim:
multitool.sh -a a*.txt -b b*.txt
(expandindo os curingas para corresponder a todos os arquivos disponíveis)
Então multitool.sh
, dentro do , executo a ferramenta em a1.txt b1.txt
, a2.txt b1.txt
, a1.txt b2.txt
, a2.txt b2.txt
, etc, com números variáveis de arquivos a e b.
Eu segui este tutorial explicando os conceitos básicos de manipulação de opções e consegui usar getops
para manipular um -h
, mas nada mais.
É aqui que estou agora:
#!/bin/bash
while getopts ":hpg:" option; do
case $option in
h) # display help
echo "-h to print this help and exit, -p is peak files, -g is bedgraph files."
exit;;
p) # get list of peak files to process
l_peaks=$OPTARG;;
g) # get list of bedgraph files to process
l_bgraphs=$OPTARG;;
\?) # Invalid option
echo "Error! Invalid option!"
echo "-h to print this help and exit, -p is peak files, -g is bedgraph files."
exit;;
esac
done
echo "$l_peaks"
echo "$l_bgraphs"
Estou trabalhando com uma equipe que não tem muita familiaridade com computadores, então, se eu puder manter o wrapper em uma execução simples de uma linha, seria melhor.
Como posso passar essas listas de arquivos como uma opção cada?
Não, não esmague vários nomes de arquivo em uma única string. Primeiro, porque os shells não suportam isso(*), mas também porque, em geral, nomes de arquivo podem ser quaisquer strings . Não há caractere ou sequência de caracteres que você possa usar como separador que não seja válido em um nome de arquivo. Exceto pelo byte NUL, mas você não pode passar isso como um argumento de linha de comando de qualquer maneira.
(* Com a possível exceção do zsh.)
Então, basta preencher dois arrays com os nomes de arquivo que você obtém como argumentos. A partir daí, também é simples fazer um loop sobre as listas.
No entanto,
getopts
ele irá parar quando vir um argumento não opcional, então teremos que procurar manualmente por alguma string separadora. (O que, novamente, necessariamente é algo que poderia ser um nome de arquivo, mas pelo menos precisará corresponder a um nome de arquivo completo para que as coisas fiquem confusas. Vamos escolher::
, e se você tiver um nome de arquivo como esse, certifique-se de passá-lo como./::
.)correndo que dá algo assim:
Ajuste o loop getopts conforme necessário, mas lembre-se de manter o comando shift para limpar as opções getopts já processadas.
Ou seja, presumi acima que você queria combinar o primeiro A com todos os Bs, e o segundo A com todos os Bs, etc.
Se, em vez disso, você quiser o primeiro A com o primeiro B, o segundo A com o segundo B, etc., então o último loop precisaria ser algo como:
Porém, como @terdon observou em um comentário, o GNU parallel poderia fazer isso diretamente.
Assim:
Ele passa o comando dado por um shell, se caracteres especiais precisariam de algumas aspas extras. E é claro que você precisa ter cuidado com os separadores, com esse extra
+
sendo bastante significativo.Se você estiver feliz em copiar a abordagem usada por
find
e citar os dois padrões glob curinga, você pode usar seu script para expandir os valores apropriadamente. Nesta configuração, os padrões citados são então manipulados pelo shell (e, portanto, porgetopts
) como apenas um argumento cada.Aqui está um exemplo, onde peguei seu código existente praticamente inalterado e o encolhi para (minha) conveniência. Seu código está melhor disposto, mas as partes interessantes são o processamento dos argumentos
-a
and-b
:Exemplo
Uma das abordagens comuns para comandos que lidam com opções que aceitam mais de um argumento é chamar as opções várias vezes, como em:
(acima mostrando exemplos onde o argumento é fornecido como um argumento separado ou no mesmo argumento)
Para aplicar isso aos globs, se estiver usando
zsh
, você pode fazer:Onde o
P[prefix]
qualificador glob insere um argumento extra-i
antes de cada arquivo gerado para que ele acabe sendo executadocmd -vv -i file1 -i file2 ... -o out
.(observe também o
n
qualificador para garantir que os arquivos sejam classificados numericamente, de modo quefile10
venha depois defile9
e não entrefile1
e,file2
como aconteceria na ordem lexical padrão).Ou:
Ou:
Ou, com a
histsubstpattern
opção habilitada:Ter
-i
adicionado (no mesmo argumento) a cada nome de arquivo, para que ele acabe executandocmd -vv -ifile1 -ifile2 ... -o out1
.No bash (ou ksh93 de onde o bash copiou essa sintaxe), você pode fazer:
Porém, tenha cuidado, pois o bash não tem equivalente para o qualificador do zsh,
n
entãofile10
ele será classificado entrefile1
efile2
e, a menos que você ative afailglob
opção, ele será chamadocmd
com um-ifile*
argumento literal se não houver nenhum arquivo correspondente.No
cmd
próprio script, você faria algo como:Sabemos que a lista de arquivos será um número par, dado que eles devem ser pareados uns com os outros. Usando isso, podemos usar um loop sobre todos os nomes de arquivo fornecidos e, enquanto iteramos sobre eles, nós os contamos. Quando tivermos atingido o ponto médio, podemos começar a parear o primeiro nome na lista com o nome atual no loop, então
shift
(o que remove o primeiro item da lista) e repetir.Isso funciona porque um
for
loop sempre itera sobre uma lista estática. Assim, "salvamos" a lista de nomes ao fazer um loop sobre ela, e podemos modificar a lista de parâmetros posicionais dentro do loop sem perturbar o loop em si.Observe que nada disso requer o
bash
shell; podemos executá-lo com umsh
shell mais simples.Teste:
Se você precisar fazer uma análise de opções, faça isso antes do código que emparelha os nomes dos arquivos: