Automatizando pipelines de bioinformática (parte I): introdução aos shell scripts

Frederico Schmitt Kremer
omixdata
Published in
6 min readSep 8, 2019

Nesta semana iniciarei uma pequena série de posts com foco em automatização de pipelines de análise de dados em bioinformática. A ideia é falar mais sobre como utilizar shell scripts e Makefiles, dentre outras abordagens, em projetos que envolvem multiplas etapas de análise.

Algumas destas abordagens já foi utilizada em tutoriais aqui do blog, mas acredito que seja interessante explicar alguns conceitos para que possamos tirar um maior proveito de suas funcionalidades no futuro, bem como para que vocês possam aplicar esta mesma metodologia nos seus próprios projetos. Neste primeiro post, falei sobre os shell scripts, e mostrarei algumas das suas principais funcinalidades :)

O que é o “shell”?

O Sistema Operacional (SO) é um software que gerencia o uso de recursos do computador disponibilizados pelo hardware, permitindo que o usuário os utilize de forma transparente e segura. As diversas aplicações que são executadas em um sistema operacional (ex: editores de texto, navegador web), acessam os recursos da máquina (ex: memória, processador) indiretamente através dele, e esta atuação como agente intermediário permite, por exemplo, que as aplicações compartilhem recursos em um mesmo ambiente (sistemas multi-tarefa) ou que diversos usuários acessem um mesmo servidor (sistema multi-usuários), por exemplo.

Para fins didáticos, podemos estruturar o SO em 3 camadas: núcleo (kernel), shell e aplicações. O kernel é a parte do software que serve para se comunicar diretamente com o hardware, e realiza o gerenciamento dos recursos propriamente ditos. Já o shell é a interface pela qual o usuário interage com o kernel, e que serve de base para a execução das aplicações. Este interface geralmente podem ser por linha de comando (commandline user interface, CLI), onde os dados são acessados por meio de comandos escritos e visualizados na forma de texto, ou gráfica (graphical user interface, GUI), onde a interação se dá por meio de elementos visuais renderizados na tela.

O shell por linha de comando nada mais é que um programa capaz de interpretador comandos que são passados através de texto, sendo geralmente chamados interpretadores de comandos. Diferentes interpretadores podem ser utilizados como shell no Linux, sendo o Bash e o ZSH alguns dos mais comuns.

Shell scripts

Os comandos executados manualmente através de um shell de sistemas operacionais Linux / Unix podem também ser escritos em arquivos chamados shell scripts. Este arquivos geralmente possuem a extensão .sh , e podem ser executados chamando-se o shell de interesse seguido do arquivo de script, conforme ilustrado abaixo.

$ bash my_script.sh

É possível também conferir ao script os privilégios para ser tratado como um arquivo executável. Para isso é necessário se alterar a suas configurações com o comando chmod , e usar o argumento +x para conferir a permissão de execução.

$ chmod +x m_script.sh
$ ./my_script.sh

Bash

O bash (Bourne-Again Shell) é uma re-implementação do Bourne Shell, um interpretador de comandos desenvolvido originalmente em 1977 para o sistema operacional UNIX. Ele é o interpretador de comandos padrão na maioria dos sistemas baseados em Linux e também no macOS, e por conta disso, usaremos ele como exemplo para ilustrar as principais funcionalidades que podem ser utilizadas em um shell script.

Comentários

Comentários são trecho do script que não são interpredos como comandos pelo shell, e que geralmente servem para documentar alguma parte do código. Uma exceção a esta regra é a “shebang”, um comentário geralmente presente no começo do script, na sua primeira linha, que é iniciado com os caracteres #!. Esta linha é usada para informar ao sistema operacional o caminho do programa que deverá interpretar este script caso ele esteja com as permissão de execução (ex: fornecidas pelo comando chmod +x).

No caso do Bash, no Ubuntu, a shebang padrão é:

#!/bin/bash

Para garantir maior compatibilidade entre distribuições Linux, é comum utilizar o comando env para mapear o caminho correto do interpretador no $PATH do sistema. Deste modo, a shebang passa a ser:

#!/usr/bin/env bash

Comandos

As linha que não iniciam com # são interpretadas como comandos. Este comandos são executados da mesma forma que os comandos passados manualmente, sendo lidos serialmente. Caso um comando seja mal executado, resultando em uma erro, o script não será interrompido.

No script abaixo, temos dois comando que serão executados, ambos baseados no utilitário echo, que serve para mostrar uma mensagem na tela com quebra de linha.

#!/usr/bin/env bash
echo "olá, "
echo "mundo"

Como os comandos são executados serialmente, o resultado será:

olá,
mundo

Variáveis

As variáveis em um shell script são definidas através de expressões de atribuição de valor, sendo sendo necessário se identificar um tipo (ex: inteiro, booleano, string). Para se criar uma nova variável, basta de definir um nome e usar o operador = para atribuir um valor e ela. O nome da variável deve iniciar com uma letra, pode possuir números nas demais posições, e também underscores (_). O valor armazenado em uma variável pode ser posteriormente acessado através do seu nome precedido por um cifrão ($). No exemplo abaixo, duas variáveis são criadas, input_file e output_file, sendo estas posteriormente acessadas na execução do programa prodigal (um preditor de genes para procariotos).

#!/usr/bin/env bashinput_file=genome.fasta
output_file=genome.gbk
prodigal -i $input_file -o $output_file

Estrutura if ... then ... fi

Em algumas situações pode ser necessário realizar teste lógicos em nossos scripts, como verificar se uma determinada variável possui um valor específico ou se um arquivo existe. Para isso, usamos a estrutura if , que segue a sintaxe abaixo. O bloco if é delimitado pelas palavras then e fi.

#!/usr/bin/env bashif [ -f sequences.fasta ]; then
echo "the file was found"
fi

O teste lógico é informato entre os colchetes ([ e ]) e pode ser constituído por diferentes tipos de expressões. No exemplo acima, utilizamos o operador -f para verificar se um determinado arquivo existe. Caso exista, a mensagem “the file was found” será mostrada, mas caso não exista, nada será mostrado. Seguindo este mesmo modelo, é possível também realizar outros testes lógicos, como por exemplo:

  • -d : verificado de um determinado diretório existe.
  • -z : verificado o se o valor de uma variável é nulo.

Além disso, é possível também realizar comparações entre dois valores, através de operadores como:

  • == : utilizado para verificar igualadade entre dois valores.
  • =~ : utilizado para verificar match entre dois valores (por expressão regular).
  • != : utilizado para verificar a desigualdade entre dois valores.

Estes operadores geralmente são utilizados entre dois colchetes ( [[ e ]]), como no exemplo abaixo.

#!/usr/bin/env bashfile=sequence.fastaif [[ $file =~ ".fasta" ]]; then
echo "Is a FASTA file"
fi

Estrutura for ... do

A estrutura de repetição for é utilizada para repetir um determinado bloco de comandos para uma quantidade definida de valores. Estes valores pode ser especificados explicitamente, no próprio código, ou selecionados a partir do uso de um caracter coringa.

Caso queiramos repetir um determinado comando para todos os arquivos presentes em um diretório, por exemplo, podemos utilizar a seguinte estrutura:

#!/usr/bin/env bashfor fasta_file in sequences/*.fasta; do
grep -c ">" $fasta_file
done

A estrutura for atribui à uma variável temporária, que no exemplo acima é a fasta_file, à cada “volta” do loop, um dos valores informados. O bloco do for é delimitado pelas palavras do e done.

Estrutura while ... do

A estrutura de repetição for serve para repetirmos um bloco de código para um conjunto definido de valores, mas nem sempre teremos esta informação quando estivermos executando o script. Em alguns casos, podem ser necessário executar um conjunto de comandos enquanto uma condição for verdadeira, e para isso utilizamos a estrutura while.

Em shell script, uma das principais aplicações da estrutura while é na leitura de arquivos linha à linha. No exemplo abaixo, a estrutura é usada para ler as linhas de um arquivo com o comando read, que para cada nova linha, armazena o seu conteúdo na variável line. Da mesma forma que o bloco for , o tambémwhile é delimitado pelas palavras do e done.

#!/usr/bin/env bashproteome=sequences.fastawhile read line; do
if [[ "$line" =~ "Escherichia" ]]; then
echo $line
fi
done < $proteome

--

--