Tutorial Linux – Parte 4

Foto de um teclado de computador com retroiluminação branca

Capítulo 4. Programando no Shell

O uso do shell como uma linguagem de programação é uma extensão natural de sua utilidade como interpretador de comandos. As pessoas preferem programar em uma linguagem que conhecem e usam no dia-a-dia a ter que aprender uma linguagem totalmente diferente para automatizar suas tarefas.

4.1. O primeiro script shell

Vamos fazer nosso primeiro script. Tudo que queremos fazer agora é imprimir “Hello World” na tela. Nosso primeiro script fica assim:

#!/bin/bash
echo Hello World

Esse scripts deve ser gravado em um arquivo com, pelo menos, permissão de execução e leitura. A primeira linha do script é especial. O marcador #! apenas indica ao kernel que o tipo do arquivo executável é um script. O kernel vai, ao detectar esse marcador, executar o arquivo usando o interpretador de comandos logo à direita (nesse caso, /bin/bash. Verifique se esse é o caminho para o Bourn Against Shell em sua máquina.

Após isso, o que segue é uma série de comandos, exatamente como poderiam ter sido digitados no prompt do shell. Claro que em um shell script, vamos usar recursos que não são normalmente usados no dia-a-dia do uso interativo do shell, como funções, laços, etc, mas é importante lembrar que a linguagem interativa e a linguagem de script são exatamente a mesma.

4.2. Comentários em shell scripts

Os comentários em shell scripts começam com o caractere # e se estendem até o fim da linha, e podem ser colocados em qualquer lugar da linha.

Lembre-se de manter os comentários pequenos e relevantes. O comentário não deve indicar como o shell script é implementado, e sim por que. Evite, portanto, comentários evidentes do tipo:

echo Hello, World! # imprime "Hello, World!"

4.3. Escapando caracteres especiais

As regras de escape de caracteres especiais são exatamente as mesmas que em um shell interativo. Você pode escapar um caractere usando ““uma seqüência de caracteres usando '' e uma seqüência de caracteres mas ainda interpretando variáveis com "". Veja os exemplos no shell interativo:

$ echo # Hello World

$ echo # Hello World
# Hello World
$ echo '# Hello World'
# Hello World
$ echo $HOME
/home/osvaldo
$ echo $HOME
$HOME
$ echo '$HOME'
$HOME
$ echo "$HOME"
/home/osvaldo

Caso haja necessidade de inserir uma string no script, é recomendado que essa string esteja escapada dentro de ". Apenas caso haja absoluta certeza que a expansão de variáveis dentro dessa string seja necessária deve-se usar "".

4.4. Lendo entrada do teclado

Uma maneira simples de ler entrada do teclado é usando o comando read. Rodando read VARIAVEL, o comando read guarda a entrada do teclado em $VARIAVEL.

4.5. Variáveis de parâmetros

Vamos melhorar nosso primeiro exemplo. Ao invés de imprimir Hello, World, vamos imprimir o que quer que o usuário passe como parâmetro. O script ficaria assim:

#!/bin/bash
echo $*

A variável $* é uma variável especial que só faz sentido em scripts. Ela expande para todos os argumentos passados ao script. Veja como ficaria a execução desse script:

$ ./exemplo2 Ola mundo
Ola mundo

Mais usualmente, o autor do script precisa ler um parâmetro em particular passado pelo script. É possível ler cada um dos parâmetros separadamente através das variáveis $1, $2, etc. A variável $0 expande para o comando usado para iniciar o programa. Veja o exemplo:

#!/bin/bash
echo $0 $2

Executando esse script com o nome exemplo3, temos:

$ ./exemplo3 primeiro segundo
./exemplo3 segundo

Finalmente, algumas vezes a quantidade de parâmetros que um script recebe é variável. A quantidade de parâmetros passada ao script é guardada na variável $#.

4.6. O comando de decisão if

if é um comando built-in que serve para fazer uma decisão. O funcionamento dele é assim:

if expressão; then
    comando1
    comando2
    ...
fi

“Expressão” é qualquer coisa que possa retornar um valor verdadeiro ou falso para o shell. Tipicamente, é um comando – seja um comando externo ou built-in.

Vamos criar um pequeno script que lê o arquivo /etc/passwd e procura um usuário no mesmo. O script vai retornar 0 caso o usuário exista, e 1 caso o usuário não exista.

O formato do arquivo /etc/passwd é o seguinte:

login:senha:UID:GID:Nome Completo:Diretório HOME:shell

Nós vamos usar o comando grep com uma pequena expressão regular para procurar o usuário.Em uma expressão regular, ^ indica o começo da linha. Logo, o que queremos encontrar no arquivo é aseqüência ^primeiroparâmetro:, ou ^$1:. Se existir uma linha que combine com essa expressão, o usuário existe no sistema. Ainda, o grep por padrão mostra a linha encontrada na tela. Nós não queremos a linha, queremos apenas saber se ela existe. Para suprimir a saída, o grep tem a opção -q. Fica assim nosso script checa-usuário:

#!/bin/bash
if grep -q ^$1: /etc/passwd; then
    exit 0
fi
exit 1

O if checa o código de retorno do grep. Quando o grep encontra a string no arquivo, ele retorna 0, o que causa a execução do exit 0. Caso negativo, a execução do script segue normalmente, acabando na expressão exit 1.

Opcionalmente nesse caso, pode-se usar a parte else do if. Funciona assim:

if expressão; then
    comando-se-verdadeiro
else
    comando-se-falso
fi

Nosso script revisitado usando else ficaria assim:

#!/bin/bash
if grep -q ^$1: /etc/passwd; then
    exit 0
else
    exit 1
fi

Funcionalmente é idêntico, mas nem sempre tem-se a opção de não usar else.

É possível, ainda, negar o resultado de qualquer expressão, botando ! na frente. Por exemplo:

if ! grep -q ...

4.7. while

O laço while executa determinado código enquanto uma certa condição for satisfeita. Por exemplo, o seguinte script imprime “Esperando” até que alguém crie o arquivo /tmp/continue.

#!/bin/bash

while ! test -f /tmp/continue; do
    echo Esperando
    sleep 10 # Espera dez segundos
done

4.8. for

O for executa um comando (ou uma série de comandos) com uma variável definida cada vez para um valor em uma lista. Por exemplo, o seguinte exemplo imprime linhas com 1, 2 e 3 na tela.

#!/bin/bash

for i in 1 2 3; do
    echo $i
done

4.9. case

Às vezes é necessário checar o valor de uma variável ou a saída de um comando e, de acordo com o conteúdo, executar determinada ação. É possível fazer isso com uma série de if s, mas é mais prático fazer usando o comando case.

Por exemplo, vamos supor que uma variável $STATUS possa ter o conteúdo “OK”, “SUCCESS”, “FAILED”e “NOT RUNNING”(digamos que essa variável venha de um campo de um arquivo de log, por exemplo). Vamos criar uma pequena rotina que imprime na tela se a variável indica sucesso ou erro.

case "$STATUS" in
    OK | SUCCESS)
        echo Programa funcionando com sucesso
        ;; # Indica o fim do bloco de código
    FAILED)
        echo Programa falhou
        ;;
    "NOT RUNNING")
        echo Programa não está rodando
        ;;
esac

Esse é um exemplo bastante completo do uso de case. Note como é possível usar o pipe como o valor de OR para que dois valores executem o mesmo trecho de código. Note também que o valor NOT RUNNING deve vir entre aspas por causa do espaço.

4.10. Expressões matemáticas

Expressões matemáticas são colocadas da seguinte forma:

$ VALOR=$((2+2))
$ echo $VALOR
4
$ echo $(($VALOR+2))
6

4.11. Colocando a saída de um comando em uma variável

É possível colocar a saída de um comando em uam variável (ou de forma genérica setar o valor de uma expressão para a saída de um comando) usando $().

$ SAIDA=$(echo oi)
$ echo $SAIDA
oi

Uma maneira alternativa de fazer a mesma coisa é usando aspas invertidas:

$ SAIDA='echo oi'

Além de colocar a saída de um comando em uma variável, esse tipo de construção pode ser usada em muitas outras partes de um shell script, como em uma comparação, substituindo expressões de outro tipo, etc.

4.12. Fazendo comparações

O comando quase universal de comparações é o [. Na verdade um link para o comando test, às vezes presente como um comando built-in do shell, o [ permite comparar conteúdos de expressões como strings e numericamente, verificar permissões e comparar datas de arquivos. Um exemplo típico é comparar se o conteúdo de uma variável é igual à saída de um comando:

if [ "$MES" = $(date +%m) ]; then
    ...
fi

Note que todo comando [ termina também com ], por motivos principalmente estéticos. Note também que o [ não é muito tolerante a falta de parâmetros. Se a variável MES estivesse vazia e não estivéssemos usando aspas, o comando executado após expansão do shell seria:

if [ = $(date +%m) ]; then

Isso causaria falha no programa, então proteja adequadamente as variáveis com aspas, especialmente dentro do comando [. Alguns outros exemplos de uso do [:

# Checa se o arquivo /tmp/foo não existe
if [ ! -f /tmp/foo ]; then
    ...
fi

# Checa se $MES é menor ou igual ao mês atual
if [ "$MES" -le $(date +%m) ]; then
    ...
fi

# Checa se o arquivo /etc/passwd é mais novo que /etc/nis.map
if [ /etc/passwd -nt /etc/nis.map ]; then
    ...
fi

# Checa se o mês é menor ou igual ao mês atual OU se o arquivo /etc/nis.map
# existe
if [ "$MES" -le $(date +%m) -o -f /etc/nis.map ]; then
    ...
fi

Uma lista completa de opções que podem ser usadas com o [ está na man page do comando test. Abaixo está a lista das mais comuns:

  • -f – Checa se o arquivo existe
  • = – Checa se strings são iguais
  • != – Checa se strings são diferentes
  • -eq – Checa se strings são algebricamente iguais
  • -le – Menor ou igual
  • -ge – Maior ou igual
  • -lt – Menor que
  • -gt – Maior que
  • -ne – Diferente
  • ! – Negação
  • -a – E (AND)
  • -o – Ou (OR)

4.13. As operações mais comuns em shell scripts

4.13.1. Contando linhas, palavras, etc.

O comando wc conta linhas, palavras e caracteres em um arquivo.

  • wc -l Conta linhas
  • wc Conta palavras
  • wc -c Conta caracteres

4.13.2. Separando campos

O comando cut é a forma mais prática de separar campos. Rode da seguitne forma:

cut -d: -f 1,3

O caractere depois do -d é o separados dos campos, e depois do -f vem os campos que deseja pegar.É possível usar “ranges“, como 1-5 para indicar os campos de 1 até 5.

Note que usar o awk para fazer essa separação simples de campos é um exagero, que torna o script mais complexo sem necessidade.

4.13.3. Substituir caracteres

Para substituir caracteres, use o comando tr. A forma de usar é:

tr abc 123

Esse comando substitui as ocorrências de “a”por “1”, “b”por “2”, etc. Também é possível usar ranges:

tr [A-Z] [a-z]

Esse comando transforma todas as maiúsculas de A a Z em minúsculas.

4.13.4. Substituindo strings

O comando para substituir strings é o sed. A funcionalidade básica é:

sed s/isso/aquilo/

Porém, o sed é mais complexo que isso, pois ele aceita o uso de expressões regulares, que serão explicadas mais tarde.

4.13.5. Procurando texto em um arquivo

Para buscar texto em um arquivo ou na entrada padrão, use o comando grep, que vai retornar as linhas em que ocorre o texto:

$ grep root /etc/passwd
root:x:0:0:root:/root:/bin/csh

Mais importante ainda que isso para quem vai fazer scripts é o código que o grep retorna: 0 para string encotnrada, 1 para string não encontrada. Isso, em conjunto com a opção -q, que suprime a saída, é muito útil em um script:

if [ grep -q root /etc/passwd ]; then
    echo Usuário root existe!
fi

4.13.6. Enviando a saída padrão para a saída padrão e para um arquivo

Às vezes é desejável gravar a saída padrão de um programa e ao mesmo tempo enviá-la para um outro programa. Para isso, use o comando tee:

ps -ef | tee processos | grep named

O comando acima guarda a lista completa de processos (a saída do comando ps) no arquivo processos e imprime aquelas linhas que casam com a string “named”na tela.

4.13.7. Economizando tempo setando variáveis

Uma coisa que não foi dita antes é que a forma $VAR de uma variável é apenas uma maneira mais curta de usar a notação completa ${VAR}. A forma completa permite acrescentar alguns manipuladores especiais que economizam código e tempo. Os operadores mais comuns são:

  • ${VAR:=valor} – Seta $VAR como “valor” se a variável estiver vazia ou não existir.
    Funciona como referência normal a $VAR caso não-vazia.
  • ${VAR:-valor} – Se $VAR existe e tem um valor não nulo, age como $VAR, caso contrário $VAR passa a valer “valor“.

4.13.8. Interceptando sinais

Para manter o ambiente de seu script sempre limpo, use a função trap para interceptar sinais. Por exemplo, duas maneiras comuns de matar scripts são enviar os sinais HUP e TERM. Você deve criar um trap para interceptar esses sinais e fazer operações de limpeza (como limpar arquivos temporários).

AVISO: Como avisamos anteriormente, esta parte está incompleta!

Continua…