Aula 3 – Nosso Primeiro Script

Aula 2 – Antes do Primeiro Script | Índice | Aula 4 – Variáveis


Seu apoio é muito importante para a criação e a manutenção dos cursos gratuitos do canal debxp:


3.1 – As etapas de criação de um script em bash

A criação de um script sempre envolverá pelo menos três etapas:

  • Criar o arquivo do script
  • Editar o arquivo do script
  • Tornar o arquivo executável

Neste tópico, nós abordaremos cada uma dessas etapas e, ao final, criaremos juntos um script que nos ajudará a criar scripts.

3.2 – Etapa 1: Criar o arquivo do nosso script

Os scripts são basicamente arquivos de texto puro sem formatação (raw text), e você pode criá-los da forma que preferir. Obviamente, como todas as etapas seguintes exigirão a digitação de comandos no terminal, faz mais sentido fazer todos os procedimentos por lá, e esta será a abordagem que nós adotaremos.

Para criar um novo arquivo no terminal, nós precisamos do nome do arquivo que queremos criar e do local (diretório) onde ele será criado.

3.2.1 – Sobre o nome do arquivo

Como vimos no tópico anterior, escolher bem o nome do arquivo é muito importante. Além disso, nós temos que seguir algumas regrinhas básicas:

  • O nome não pode conter espaços, acentos e outros símbolos tipográficos além dos caracteres alfabéticos, números, pontos (.), o sinal de menos (-) e o sublinhado (_).

  • Quanto às extensões, principalmente a extensão .sh, muito utilizada, elas são irrelevantes para o shell e só servem para nos informar de que se trata do arquivo de um script ou para diferenciar scripts de comandos no momento da execução.

Nota: No Linux, e em outros sistemas unix like, o ponto ( . ) no nome de um arquivo é um caractere como outro qualquer e não possui nenhum significado especial.

3.2.2 – Sobre o diretório

Em produção, é bem provável que você queira que o seu script seja executado de qualquer diretório. Neste caso, é interessante que ele seja criado (ou movido após finalizado) numa pasta que esteja no PATH do sistema ou em um caminho da sua pasta pessoal que você tenha incluído no PATH.

Se você não sabe quais são essas pastas, basta executar no terminal…

:~$ echo $PATH

Aqui, o comando echo irá exibir todas as pastas que estão listadas na variável de ambiente PATH, cada uma separada por dois pontos (:) das outras. Por exemplo, aqui no meu sistema:

:~$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/home/blau/bin

Esta lista mostra que os aplicativos e scripts que eu executar no prompt de comando serão procurados nestes caminhos na seguinte ordem:

/usr/local/bin
/usr/bin
/bin
/usr/local/games
/usr/games
/home/blau/bin

Repare que a última pasta em que o shell irá procurar os executáveis será em /home/blau/bin, que é um diretório (bin) na minha pasta de usuário (/home/blau). Este caminho não existia originalmente na variável de ambiente PATH do meu sistema, eu tive que incluí-lo com o seguinte comando:

:~$ PATH=$PATH:/home/blau/bin

Este tipo de procedimento é chamado de atribuição. Para isso, nós utilizamos o operador de atribuição =. À esquerda, nós indicamos o nome da variável a que queremos atribuir um valor, que é o que vem à direita do operador:

# Atribuição de um valor à uma variável...
NOME_DA_VARIÁVEL=VALOR

No caso, a variável PATH receberá o valor $PATH:/home/blau/bin, onde:

  • $ é como nós dizemos ao shell que precisamos acessar o valor armazenado em uma variável (no caso, PATH);
  • : é o separador de cada caminho da lista;
  • e /home/blau/bin é o caminho que queremos incluir ao final da lista.

Todo esse valor é o que chamamos de string, que nada mais é do que uma sequência de caracteres. Portanto, neste procedimento de atribuição, nós estamos concatenando o valor que já está em PATH com o caractere do separador (:) e o caminho que queremos incluir na lista.

Nota: Nós teremos tópicos sobre atribuições de valores a variáveis e concatenação de string mais para frente.

Mas, se o seu arquivo não estiver em uma das pastas listadas, ele ainda poderá ser executado indicando a sua localização:

:~$ /caminho/da/pasta/do/seu_script

Se você estiver na mesma pasta do arquivo do script, basta executá-lo assim:

:~$ ./seu_script

Onde o ./ informa para o shell que o arquivo seu_script está na pasta corrente (a pasta em que você está trabalhando no momento).

Para efeito de treinamento e estudo, nós não vamos criar nem executar os nossos scripts nas pastas listadas no PATH. Afinal, nós já vimos esta não seria uma boa prática.

3.2.3 – Como criar um novo arquivo pelo terminal

Para criar um arquivo novo pelo terminal, sem abri-lo através de um editor de textos nem criando um arquivo vazio pelo gerenciador de arquivos, nós utilizamos uma coisa chamada redirecionamento de saída para arquivos, cuja sintaxe envolve o uso de um ou dois símbolos de maior (> ou >>). A diferença entre usarmos um ou dois símbolos de maior é:

  • > – Cria um novo arquivo com determinado conteúdo mesmo que ele já exista. Se o arquivo existir, ele será recriado com o novo conteúdo.
  • >> – Inclui um determinado conteúdo ao final de um arquivo existente. Se o arquivo não existir, ele será criado.

O conceito básico desse processo de criação de arquivos está no fato de que o shell é capaz de manipular entradas e saídas de comandos, permitindo que elas sejam direcionadas para outros comandos e arquivos.

Aqui, nós utilizaremos o redirecionamento da saída de um comando para um arquivo (ainda inexistente), o que obedece a sintaxe:

Novo arquivo: comando > arquivo

Ou…

Append: comando >> arquivo

Porém, se não existir um comando sendo executado à esquerda, nenhuma saída será gerada, o que resultará em um arquivo vazio, e é exatamente dessa característica que iremos nos valer para criar novos arquivos para os nossos scripts:

:~$ >> NOVO_ARQUIVO_DE_SCRIPT

Nota: As duas abordagens (> ou >>) funcionam. Contudo, só devemos utilizar um sinal de maior quando tivermos certeza de que não existe outro arquivo com o mesmo nome, ou ele seria sobrescrito. Com dois sinais de maior (>>), mesmo que exista um arquivo com o mesmo nome, ele só tentará acrescentar uma string vazia ao final desse arquivo, o que não produzirá nenhuma alteração.

Também é interessante observar que existe um outro comando (um utilitário, na verdade) capaz de criar arquivos pelo terminal: o comando touch. Apesar de o resultado ser o mesmo, quer dizer, você acaba criando o arquivo se ele não existir, esta não é a finalidade original do touch, que é um comando para alterar o marcador da data e da hora do último acesso a um arquivo. Além disso, o touch é um utilitário do sistema, não um comando interno do shell, o que pode implicar em códigos com menor desempenho.

Nota: Na linha de comando, não é possível criar vários arquivos apenas com um único redirecionamento de saída, mas é possível fazer isso com o touch. Existem formas de criar múltiplos arquivos com os redirecionamentos, mas todas elas envolvem recursos do Bash que nós veremos em outros tópicos.

Por fim, você ainda pode criar novos arquivos pelo terminal utilizando um editor de textos, como o nano ou o vim, por exemplo.

3.3 – Etapa 2: Escrever o conteúdo do script no arquivo

Você pode utilizar o editor de textos da sua preferência, mas nós estamos executando comandos no terminal e, portanto, é muito mais prático e interessante permanecer nele.

Existem várias opções de editores para a linha de comando, mas os dois mais populares são o GNU Nano e o Vim. Para uso imediato, e não tirar o foco do aprendizado do Bash, eu recomendo utilizar o nano. Ele pode não ser tão rico em recursos e nem tão poderoso quanto o Vim, mas oferece tudo de que precisamos nesta fase de descobertas.

Nota: Vale muito a pena aprender a utilizar o Vim, eu só não recomendo misturar as estações neste momento, quando estamos diante de tantas informações novas para assimilar.

Para editar o arquivo recém-criado (ou criar um arquivo que ainda não existe), basta executar:

:~$ nano nome_do_novo_arquivo

Depois disso, é só começar a escrever o código, salvar com Ctrl+S e sair com Ctrl+X.

3.3.1 – Definindo o interpretador de comandos

A primeira linha do nosso script, antes de todos os comandos e instruções, deve conter a definição do interpretador de comandos. Esta linha é chamada de várias formas, sendo as mais comuns: hashbang e shebang, como uma referência aos dois caracteres iniciais da linha (#!).

No caso de querermos utilizar o Bash, nós podemos escrever a linha do interpretador de duas formas:

#!/bin/bash

Ou…

#!/usr/bin/env bash

A diferença entre elas, é que a primeira (#!/bin/bash) pressupõe a existência do executável do Bash na pasta /bin, enquanto a segunda (#!/usr/bin/env bash) consultará os recursos do ambiente para descobrir onde está o executável.

As duas formas estão corretas e têm grandes chances de funcionar na maioria das distribuições Linux, mas a segunda forma é mais portável, ou seja, pode ser compatível com mais plataformas.

3.3.2 – E se eu não definir um interpretador?

Neste caso, o script será interpretado pelo shell padrão do sistema, seja ele qual for, e os seus comandos e instruções podem não ser reconhecidos, o que resultaria em erros de sintaxe.

Importante! Lembre-se de que o shell pode ser utilizado de forma interativa (linha de comando) ou não-interativa (script) e, geralmente, os sistemas GNU/Linux utilizam algum shell compatível com as normas POSIX no modo não-interativo, como é o caso do Debian, que utiliza o dash como shell padrão para o modo não-interativo.

Como regra geral, nós sempre definimos o interpretador, mas o script ainda poderá funcionar corretamente caso ele contenha apenas uma lista de programas utilitários a serem executados em sequência.

3.4 – Etapa 3: Tornar o arquivo executável

Todo arquivo possui atributos de permissão, como leitura, escrita e execução. Além disso, essas permissões podem ser dadas a grupos de usuários diferentes, inclusive para todos os usuários.

Para alterar as permissões de um arquivo, existe o utilitário chmod, que é usado segundo a sintaxe geral:

chmod opções arquivo

Para tornar o nosso arquivo executável, basta utilizá-lo desta forma:

:~$ chmod +x arquivo

Onde a opção +x ativa o indicador (flag) de executável do arquivo.

3.4.1 – Como saber se o arquivo é executável?

A forma mais simples e eficiente de descobrir se um arquivo é executável ou não é com o comando:

:~$ ls -l nome_do_arquivo

Se ele for executável, você verá diversos x na primeira coluna das informações exibidas sobre o arquivo. Por exemplo:

:~$ ls -l teste.sh
-rwxr-xr-x 1 blau blau 6084 out 1 17:15 teste.sh
   ^  ^  ^
   |  |  |
   +--+--+--- indicadores de arquivo executável

3.4.2 – O comando builtin ‘test’

Mas existe uma forma bem mais interessante de fazer isso utilizando os comandos builtin do Bash:

test -x nome_do_arquivo; echo $?

Executando isso na linha de comando ou em um script, você verá um 0, se for executável, ou um número diferente de zero se não for executável. Por exemplo:

:~$ test -x teste.sh; echo $?
0

:~$ test -x arquivo.txt; echo $?
1

Aqui, $? é uma variável especial do shell que armazena o estado de saída do último comando executado. Se o programa anterior foi executado com sucesso, ela armazenará o valor 0. Caso contrário, ela armazenará um valor diferente de 0 (1, no exemplo acima).

Uma forma ainda mais bacana de executar o comando test no Bash seria:

[[ -x nome_do_arquivo ]]; echo $?

Vejamos nos exemplos:

:~$ [[ -x teste.sh ]]; echo $?
0

:~$ [[ -x arquivo.txt ]]; echo $?
1

Nota: O comando test também pode ser representado com apenas um par de colchetes ([ expressão ]), mas o Bash oferece recursos adicionais quando utilizamos pares duplos de colchetes.

3.5 – Nosso primeiro script!

Nosso primeiro script será um pequeno aprimoramento do exemplo do tópico anterior. Antes, porém, precisamos criar o arquivo e torná-lo executável.

Se você ainda não criou uma pasta de testes para o nosso curso, essa é a sua chance!

:~$ mkdir ~/testes

Com a pasta criada, você pode mudar o diretório corrente para ela:

:~$ cd ~/testes
:~/testes$

Agora crie o arquivo do nosso primeiro script:

:~/testes$ >> infos

E torne-o executável com o comando:

:~/testes$ chmod +x infos

Pronto, nosso arquivo já existe e é executável! Mas ele ainda está vazio, e é isso que nós resolveremos agora:

:~/testes$ nano infos

Com o editor aberto, vamos digitar as linhas do código da forma que está abaixo:

#!/usr/bin/env bash 

whoami
hostname
uptime -p
uname -rms

Quando terminarmos, vamos salvar o arquivo (Ctrl+S) e sair do nano (Ctrl+X).

Se você digitou tudo direitinho e seguiu todos os passos anteriores, nós já podemos executar o nosso script!

:~/testes$ ./infos

O que deve apresentar na saída algo parecido com isso:

:~/testes$ ./infos
blau
enterprise
up 2 weeks, 3 days, 19 hours, 42 minutes
Linux 4.19.0-6-amd64 x86_64

Mas as informações jogadas assim no terminal não fazem muito sentido. Vamos melhorar isso!

Abra novamente o arquivo com o editor nano e altere o conteúdo para que fique exatamente da forma que eu mostro aqui:

#!/usr/bin/env bash 

clear 

echo ""
echo "Informações super importantes!"
echo "-------------------------------------" 

echo -n "Usuário : "
whoami 

echo -n "Hostname: "
hostname 

echo -n "Uptime  : "
uptime -p 

echo -n "Kernel  : "
uname -rms 

echo "-------------------------------------"
echo "" 

Salve, saia do editor e execute seu script melhorado:

:~/testes$ ./infos

Se você digitou tudo direitinho, o resultado deve ficar mais ou menos assim:

Informações super importantes!
-------------------------------------
Usuário : blau
Hostname: enterprise
Uptime  : up 3 hours, 18 minutes
Kernel  : Linux 4.19.0-6-amd64 x86_64
------------------------------------- 

De tudo que aparece nesse primeiro script, as novidades são:

  • clear – comando para "limpar" o terminal.
  • echo -n – onde -n é uma opção do comando echo que impede que ele crie uma nova linha ao final da string exibida.

Nota: Nos próximos tópicos nós veremos outras formas de trabalhar com o echo sem precisar da opção -n para concatenar strings com saídas de comandos.

3.6 – Um script para criar scripts

Vamos pensar um pouco em tudo que vimos neste tópico e em todos os comandos que teremos que executar para criar um novo script:

Etapa Comandos
Decidir onde criar o arquivo Nenhum comando diretamente associado
Criar um novo arquivo >> nome_do_arquivo
Tornar o arquivo executável chmod -x nome_do_arquivo
Editar o arquivo nano nome_do_arquivo
Escrever a linha do interpretador #!/usr/bin/env bash

Para mim, isso se parece exatamente com o tipo de processo repetitivo que merece ser transformado em script! Então, vamos criá-lo!

3.6.1 – Decidindo onde criar o novo script

Para começar, um script para criar scripts só faz sentido se pudermos chamá-lo da pasta em que estivermos querendo criar um novo script. Sendo assim, esta é uma boa oportunidade de criar uma pasta para os nossos scripts que podem ser acessados de qualquer local. Para isso, crie uma pasta chamada bin e entre nela:

:~$ mkdir ~/bin
:~$ cd ~/bin
:~/bin$

Alternativamente, se você não tem certeza de que a pasta ~/bin não existe, você pode tentar criá-la com a opção -p do comando mkdir. O resultado será o mesmo, mas isso evitará erros caso a pasta já exista:

:~$ mkdir -p ~/bin
:~$ cd ~/bin
:~/bin$

Agora, vamos incluir esta pasta no seu PATH. Note que algumas distribuições GNU/Linux já configuram o Bash para reconhecer a pasta ~/bin como um dos caminhos de executáveis do usuário. Para conferir, execute:

:~/bin$ echo $PATH

Se a pasta /home/nome_do_usuario/bin não aparecer na lista, você pode incluí-la no final do seu arquivo ~/.bashrc.

Nota: O arquivo ~/.bashrc é um arquivo de configurações pessoais do Bash. Você deve ter muito cuidado ao manipulá-lo, mas geralmente é seguro se você se limitar a incluir coisas no final dele enquanto está aprendendo.

Abra o arquivo ~/.bashrc com o editor nano, vá até o final e inclua esta linha:

export PATH="$PATH:/$HOME/bin"

Nota: O comando export serve para definir que a variável será uma variável de ambiente do shell, enquanto $HOME é uma variável de ambiente que contém o caminho da sua pasta de usuário sem a barra (/) no final.

Salve (Ctrl+S) e saia do editor (Ctrl+X). Para aplicar as mudanças feitas em ~/.bashrc, execute o comando:

:~/bin$ source ~/.bashrc

O comando source para executar, no shell atual, os comandos existentes em um arquivo. Na prática, o que estamos fazendo é recarregar as configurações em ~/.bashrc sem precisarmos fechar e abrir novamente o terminal.

Execute novamente o comando abaixo e veja se a usa pasta /home/nome_do_usuario/bin aparece na lista.

:~$ echo $PATH

Se tudo estiver correto, vamos ao próximo passo.

3.6.2 – Criando o arquivo do nosso script

Ainda na pasta ~/bin, vamos criar o arquivo do nosso script criador de scripts, a que vamos chamar de novo-script.sh. Em princípio, nós podemos criá-lo com o comando:

:~/bin$ >> novo-script.sh

Porém, em vez de criarmos um arquivo vazio, existe um "macete" que nos permite criar o arquivo já com a linha do interpretador, o que nos poupa de mais uma etapa do processo. A ideia básica é aproveitar o redirecionamento de saída para arquivos da seguinte forma:

:~/bin$ echo '#!/usr/bin/env bash' >> novo-script.sh

Observe que a saída do comando echo será a string #!/usr/bin/env bash seguida de uma quebra de linha, e é exatamente isso que será incluído no arquivo novo-script.sh através do redirecionamento (>>).

Importante! É obrigatório que #!/usr/bin/env bash esteja entre aspas simples! Curiosamente, a exclamação (!) é um comando do shell que nos permite buscar eventos no histórico de comandos (os comandos que você já executou) e, com aspas duplas, ele seria executado!

Antes de continuarmos, vamos atualizar os nossos procedimentos?

Etapa Comandos
Decidir onde criar o arquivo Nenhum comando diretamente associado
Criar um novo arquivo echo '#!/usr/bin/env bash' >> nome_do_arquivo
Tornar o arquivo executável chmod -x nome_do_arquivo
Editar o arquivo nano nome_do_arquivo
Escrever a linha do interpretador #!/usr/bin/env bash

3.6.3 – Tornando nosso script executável

Em seguida, vamos tornar o script novo-script.sh executável:

:~/bin$ chmod +x novo-script.sh

3.6.4 – Editando o nosso script

Chegou a hora de abrirmos o nosso arquivo recém-criado para escrevermos algum código nele. Se você seguiu todos os passos até aqui, ao abrir o arquivo com o comando…

:~/bin$ nano novo-script.sh

Você deve encontrar apenas esta linha:

#!/usr/bin/env bash

Agora, nós só temos que escrever as instruções relativas a cada um dos passos necessários para a criação de um novo script:

#!/usr/bin/env bash

echo '#!/usr/bin/env bash' >> nome_do_arquivo
chmod + x nome_do_arquivo
nano nome_do_arquivo

Mas… Espera um pouco!

Como o script vai saber o nome do arquivo que eu pretendo criar?

Uma das formas de resolver esse problema é recorrendo a outra variável especial do shell que está no mesmo grupo da variável $0, que nós vimos no fim do primeiro tópico. Este grupo de variáveis especiais é chamado de parâmetros posicionais (nós teremos um tópico completo dedicado a eles), e correspondem ao nome do programa ou script em execução ($0) e tudo que for passado para ele como argumento após o nome ($1, $2, $3, etc…). Ou seja, nós podemos executar o nosso script da seguinte forma:

:~$ novo-script nome_do_arquivo

Onde nome_do_arquivo, seja ele qual for, ficará armazenado no parâmetro posicional $1, e nós poderemos acessá-lo de dentro do nosso script assim:

#!/usr/bin/env bash

echo '#!/usr/bin/env bash' >> $1
chmod + x $1
nano $1

Nota: Cada argumento passado para um script será armazenado nos parâmetros posicionais segundo a ordem em que forem escritos considerando-se os espaços entre eles como separadores.

3.6.5 – Evitando problemas

Nós já garantimos que um arquivo de mesmo nome não será totalmente sobrescrito utilizando o redirecionamento >>. Mas, caso aconteça a coincidência dos nomes, a linha do interpretador seria escrita no final desse arquivo. Portanto, nós precisamos garantir que isso jamais aconteça.

Voltando ao comando test e à sua sintaxe [[ expressão ]], um dos testes que podemos fazer é verificar se um determinado arquivo já existe, o que é feito através do operador -f. Então, vamos incluir um teste no começo do nosso script:

#!/usr/bin/env bash

[[ -f $1 ]] && echo "Arquivo já existe! Saindo..." && exit 1

echo '#!/usr/bin/env bash' >> $1
chmod + x $1
nano $1

Nesta nova linha, nós testamos se o nome de arquivo armazenado em $1 já existe na pasta atual. Se existir, o comando test irá retornar um status de saída 0 (sucesso). Depois do teste em si, nós temos o conector lógico &&, cuja função é permitir a execução do comando que vier depois dele apenas se o comando anterior foi executado com sucesso (retornou um status de saída 0). Consequentemente, se o teste retornar status 0, o comando echo "Arquivo já existe! Saindo..." será executado e, em seguida, o segundo conector lógico && permitirá a execução do comando exit 1, que encerrará a execução do script e retornará para o shell o status 1, indicado por nós.

No fim das contas, toda esta linha do código serve para impedir que o script continue sendo executado caso exista outro arquivo na mesma pasta com um nome igual ao do que estamos tentando criar.

Mas, ainda falta prevenir um outro tipo de problema: se o nome do novo arquivo contiver espaços, apenas a primeira parte desse nome será armazenada em $1. Além disso, nós podemos nos esquecer de informar um nome para o arquivo do novo script, o que também precisa ser tratado de alguma forma.

Felizmente, o comando test pode nos ajudar novamente! Desta vez, para verificar se o número de argumentos passados para o script no prompt de comandos está correta. Mais de um argumento, ou nenhum argumento, não são opções válidas. Então, só nos serve continuar executando o script se houver um e apenas um argumento.

Sendo assim, vamos incluir mais uma linha antes das outras no nosso script:

#!/usr/bin/env bash

[[ $# -ne 1 ]] && echo "Digite o nome de apenas um arquivo! Saindo..." && exit 1

[[ -f $1 ]] && echo "Arquivo já existe! Saindo..." && exit 1

echo '#!/usr/bin/env bash' >> $1
chmod + x $1
nano $1

O que acontece depois do teste é idêntico ao que vimos no teste de arquivo existente. A diferença aqui está no teste em si. Nesta nova linha, [[ $# -ne 1 ]], a variável especial do shell, $#, armazena a quantidade de argumentos passados para um script. Em seguida, nós utilizamos o operador de comparação numérica, -ne (not equal, "diferente"), para verificar se a quantidade de argumentos é diferente de 1. Se houver zero argumentos ou mais de um argumento, o teste retornará status 0 (sim, o valor em $# é diferente de 1), fazendo com que a mensagem seja exibida e o script seja encerrado imediatamente, retornando status de saída 1 para o shell.

3.6.6 – Agora temos dois scripts!

O nosso script criador de scripts está pronto! Basta salvar e utilizar à vontade. A essa altura, você já deve estar entendendo bem melhor como pode ser útil criar scripts. Além disso, nós demos vários saltos no conteúdo dos próximos tópicos:

  • Variáveis de ambiente HOME, PATH e o comando export
  • Parâmetros posicionais
  • Variáveis especiais $? e $#
  • Comando teste alguns dos seus operadores
  • O conector lógico &&
  • Redirecionamentos para arquivos > e >>

Mas, não se preocupe. Todos eles serão bastante explorados no momento certo.


Aula 2 – Antes do Primeiro Script | Índice | Aula 4 – Variáveis

1 Response

  1. Muito obrigado por disponibilizar este conteúdo de forma aberta!! estou aprendendo muito! em breve vou da uma força no pix do projeto; Gratidão! que Deus te abençoe grandemente. Abraços.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Post comment