Bash em Exemplos, Parte 1

Programação Fundamental no Bourne again shell (bash)

Daniel Robbins
President and CEO, Gentoo Technologies, Inc.
Março de 2000

Ao aprender como programar na linguagem de script bash, sua interação diária com o Linux se tornará mais divertida e produtiva, e você estará apto a montar programas a partir dos componentes padrão do Unix (como pipelines e redireção) que você já conhece e adora. Nesta série de três partes, Daniel Robbins irá ensinar a você como programa no bash através de exemplos. Ele irá cobrir o básico (o que torna esta série excelente para iniciantes) e irá apresentar funcionalidades mais avançadas conforme a série avançar.

Conteúdo

Você pode estar se perguntando por que iria aprender programação Bash. Bem, aqui tem alguns motivos:

Você já está rodando ele

Se você checar, você provavelmente vai descobrir que está rodando o bash neste exato momento. Mesmo se você trocou o seu shell default, provavelmente o bash ainda está sendo executado em algum lugar em seu sistema, por que ele é o shell padrão do Linux e é usado para uma variedade de finalidades. Como o bash já está sendo executado, qualquer scripts bash adicionais que você executar são inerentemente eficientes em termos de uso de memória, por que eles compartilham a memória com qualquer processo bash em execução. Por que carregar um interpretador de 500K, se você já está executando algo que irá fazer o serviço, e fazer bem?

Você já está usando ele

Não somente você já está rodando o bash, mas você está de fato interagindo com o bash no seu dia-a-dia. Ele está sempre ali, então faz sentido aprender como usá-lo em todo seu potencial. Fazendo isto você irá tornar sua experiência com o bash mais divertida e produtiva, mas por que você deveria aprender programação no bash? Fácil, por que você já pensa em termos de execução de comandos, CPiar arquivos, fazer pipes e redireção de saída. Você não deveria aprender uma linguagem que permite você usar e construir em cima destas construções poderosas que você já sabe como usar? O shell libera o potencial de um sistema Unix, e o bash é o shell do Linux. É a cola de alto-nível entre você e a máquina. Faça seu conhecimento do bash crescer, e você automaticamente irá aumentar sua produtividade sob o Linux e UNIX -- é assim simples.

Confusão com o Bash

Aprender o bash do jeito errado pode ser um processo que cria bastante confusão. Muitos novatos teclam "man bash" para ver a página man do bash, somente para serem confrontados com uma descrição técnica e concisa da funcionalidade do shell. Outros teclam "info bash" (para ver a documentação GNU info), fazendo com que ou a página man seja reapresentada, ou (se eles tem sorte) a documentação levemente mais amigável do info aparecer.

Enquanto isto pode ser um pouco desapontador para novatos, a documentação padrão do bash não pode ser tudo para todos, e agradar a todos que já estão familiarizados com a programação shell em geral. Definitivamente existe muita informação técnica excelente na página man, mas sua utilidade para novatos é limitada.

É aqui que esta série entra. Nela, irei mostrar a você como realmente usar os componentes de programação do bash, de forma que você vai poder escrever seus próprios scripts. Ao invés de descrições técnicas, irei apresentar explicações claras, de forma que você não irá somente saber o que algo faz, mas quando você realmente vai usar este "algo". No final desta série de três partes, você vai saber escrever seus próprios scripts bash intrincados, e estar capacitado a confortavelmente usar o bash e suplementar seu conhecimento pela leitura (e entendendo!) a documentação padrão do bash. Vamos começar.

Variáveis de ambiente

Sob o bash e quase todos os outros shells, o usuário pode definir variáveis de ambiente, que são armazenas internamente como strings ASCII. Uma das coisas mais úteis sobre as variáveis de ambiente é que elas são uma parte padrão do modelo de processos do UNIX. Isto significa que as variáveis de ambiente não são exclusivas aos shell scripts, mas podem ser utilizadas por programas compilados também. Quando "exportamos" uma variável ambiente sob o bash, qualquer programa subseqüente que seja executado pode ler nossa configuração, seja ele um script shell ou não. Um bom exemplo é o comando vipw, que normalmente permite que o root edite o arquivo de senhas do sistema. Configurando a variável ambiente EDITOR com o nome do seu editor de texto favorito, você está configurando o vipw a usar este editor ao invés do vi, uma coisa bastante útil se você está acostumado com o xemacs e não gosta do vi.

A maneira padrão de definir uma variável de ambiente sob o bash é:

$ myvar='This is my environment variable!'

O comando acima define uma variável de ambiente chamada "myvar" e contém a string "This is my environment variable!". Existem várias coisas a serem notadas acima: primeiro, não há espaços em nenhum lado do sinal "="; qualquer espaço irá resultar em um erro (tente e veja por si mesmo). A segunda coisa a ser notada é que enquanto podemos dispensar as plicas se estamos definindo uma palavra, elas são necessárias quando o valor da variável de ambiente é mais que uma palavra (contém espaços ou tabulações).

Sobre Quoting

Para informações extremamente detalhadas sobre como as cotas (aspas ou plicas) podem ser utilizadas no bash, você deve olhar na seção "QUOTING", na página man do bash. A existência de sequências de caracteres especiais que são "expandidas" (substituídas) com outros valores complica a forma que as strings são tratadas no bash. Iremos ver nesta série somente as funcionalidade de quoting mais utilizadas.

Em terceiro lugar, enquanto podemos utilizar aspas duplas ao invés de aspas simples, fazer isto no exemplo acima teria causado um erro. Por quê? Por que usando aspas simples (plicas), nós desabilitamos uma funcionalidade do bash chamada de expansão, em que caracteres especiais e seqüências de caracteres são substituídas por valoers. Por exemplo, o caracter "!" é o caracter de expansão de histórico, que o bash normalmente substitui com um comando previamente usado (não iremos cobrir exçansão de histórico nesta série de artigos, por que não é frequentemente usada na programação do bash. Para mais informação sobre esta funcionalidade, veja a seção "HISTORY EXPANSION" na página man do bash). Enquanto esta funcionalidade tipo macro pode ser útil, agora queremos um ponto de exclamação literal no fim de nossa variável de ambiente, ao invés de uma macro.

Agora, vamos dar uma olhada em como estas variáveis de ambiente são utilizadas. Aqui temos um exemplo:

$ echo $myvar
This is my environment variable!

Quando precedemos o nome da variável de ambiente com um $, obrigamos o bash a substituir o mesmo com o valor de myvar. Na terminologia do bash, isto é chamado de "expansão de variável". Mas, e se tentarmos o seguinte:

$ echo foo$myvarbar
foo

O que queriamos era que aparecesse "fooThis is my environment variable!bar", mas não foi o que aconteceu. O que houve de errado? Em poucas palavras, a facilidade de expansão de variáveis do bash ficou confusa. Ela não conseguia saber se queríamos expandir a variável $m, $my, $myvar, $myvarbar, etc. Como podemos informar ao bash de forma mais explícita e clara a qual variáveil estamos nos referindo? Tente isto:

$ echo foo${myvar}bar
fooThis is my environment variable!bar

Como você pode ver, podemos colocar o nome da variável de ambiente entre chaves quando ela não está claramente separada do texto que a cerca. Enquanto $myvar é mais rápido de escrever e irá funcionar a maior parte do tempo, ${myvar} pode ser tratada corretamente na maioria das situações. Por outro lado, ambos fazem a mesma coisa, e você irá encontrar as duas formas de expansão de variável no resto desta série. Você vai querer lembrar de usar a forma mais explícita com chaves quando sua variável de ambiente não está isolada do texto que a cerca por brancos (espaços ou tabulações).

Lembre que mencionamos que podemos "exportar" variáveis. Quando exportamos uma variável ambiente, ela automaticamente fica disponível no ambiente de qualquer scrip ou executável que seja executado posteriormente. Os shell scripts podem acessar a variável de ambiente usando o suporte a variáveis de ambiente interno do shell, enquanto programas C podem usar a chamada de função getenv(). A seguir há um exemplo de código C que você pode copiar e compilar -- Ele permite que se entenda as variáveis de ambiente da perspectiva do C:

myvar.c -- Um exemplo de programa C que usa variáveis de ambiente

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char *myenvvar = getenv("EDITOR");
    printf( "The editor environment variable is set to %s\n", myenvvar );
}

Salve o trecho acima em um arquivo chamado myenv.c e então compile o mesmo com o comando:

$ gcc myenv.c -o myenv

Agora, você tem um executável em seu diretório que, quando executado, irá escrever o valor da variável de ambiente EDITOR, se houver uma. Isto é o que acontece quando executo este programa no meu computador:

$ ./myenv
The editor environment variable is set to (null)	

Hmm. Como a variável não está configurada para nenhum valor, o programa C recebe uma string nula. Vamos tentar configurar a variável para algum valor específico:

$ EDITOR=xemacs
$ ./myenv
The editor environment variable is set to (null)	

Você esperava que myenv imprimisse o valor "xemacs", mas isto não aconteceu, por que não exportamos a variável de ambiente EDITOR. Desta vez, vamos fazer isto funcionar:

$ export EDITOR
$ ./myenv
The editor environment variable is set to xemacs

Agora você viu com seus próprios olhos que outro processo (no caso nosso programa em C) não pode ver a variável de ambiente até que ela seja exportada. Se você quiser, pode definir e exportar a variável usando uma linha:

$ export EDITOR=xemacs

Funciona exatamente da mesma forma que a versão com duas linhas. Agora é uma boa hora para mostrar como apagar uma variável de ambiente usando o comando unset:

$ unset EDITOR
$ ./myenv
The editor environment variable is set to (null)	

Cortando strings

dirname e basename

Note que tanto o comando dirname quanto o comando basename não examinam nenhum diretório ou arquivo no disco, eles são apenas comandos de manipulação de string.

Cortar strings -- ou seja, dividir a string original em pedaços separados, menores -- é uma das tarefas executadas diariamente pelos shell scripts. Muitas vezes, os shell scripts precisam receber um nome de caminho completo, e encontrar o nome do arquivo ou do diretório. Enquanto isto é possível (e divertido!) de fazer no bash, o executável padrão Unix basename faz isto muito bem:

$ basename /usr/local/share/doc/foo/foo.txt
foo.txt
$ basename /usr/home/drobbins
drobbins

O basename é uma ferramenta bastante útil para cortar strings. Sua contraparte, chamada dirname, retorna a "outra" parte do caminho que o basename jogou fora:

$ dirname /usr/local/share/doc/foo/foo.txt
/usr/local/share/doc/foo
$ dirname /usr/home/drobbins
/usr/home

Substituição de comandos

Uma coisa bastante útil para saber é como criar uma variável de ambiente que contém o resultado de um comando executável. Isto é fácil de fazer:

$ MYDIR=`dirname /usr/local/share/doc/foo/foo.txt`
$ echo $MYDIR
/usr/local/share/doc/foo

O que fizemos acima é chamado de "substituição de comando". Várias coisas devem ser notadas neste exemplo. Na primeira linha, simplesmente colocamos o comando que queríamos executar em back quotes (crase ou acento grave). Estas não são aspas simples ou plicas padrão, mas vem da tecla que normalmente fica acima da tecla Tab (nos teclados US). Podemos fazer exatamente a mesma coisa com a sintaxe alternativa de substituição de comando do bash:

$ MYDIR=$(dirname /usr/local/share/doc/foo/foo.txt)
$ echo $MYDIR
/usr/local/share/doc/foo

Como você pode ver, o bash fornece várias formas de se fazer exatamente a mesma coisa. Usando substituição de comandos, podemos colocar qualquer comando ou pipeline de comandos entre `` ou $(), e atribuir o resultado a uma variável de ambiente. Muito útil! Segue um exemplo de como usar um pipeline com a substituição de comando:

bash-2.03$ MYFILES=$(ls /etc | grep pa)
bash-2.03$ echo $MYFILES
pam.d passwd

Cortando strings como um profissional

Enquanto basename e dirname são grandes ferramentas, existem horas que precisamos executar cortes em strings mais avançados que as manipulações de nome de diretório. Quando precisamos de cortes mais avançados, podemos usar a funcionalidade avançada de expansão de variáveis do bash. Já usamos a expansão de variável padrão, que é o ${MYVAR}. Mas o bash pode executar alguns cortes bastante úteis. Veja estes exemplos:

$ MYVAR=foodforthought.jpg
$ echo ${MYVAR##*fo}
rthought.jpg
$ echo ${MYVAR#*fo}
odforthought.jpg

No primeiro exemplo, escrevemos ${MYVAR##*fo}. O que exatamente significa isto? Basicamente, dentro do ${} nós escrevemos o nome de uma varíavel de ambiente, dois ##s, e uma expressão coringa ("*fo"). Então o bash pegou MYVAR, encontrou a maior substring do início da string "foodforthought.jpg" que combina com a expressão "*fo", e cortou ela do início da string. Isto é um pouco difícil de entender a princípio, então para ter um entendimento de como esta opção especial "##" funciona, vamos seguir passo a passo como o bash completou esta expansão. Primeiro, ele começou procurando por substrings no início de "foodforthought.jpg" que combinam com a expressão "*fo". Aqui estão as substrings que ele checou:

f
fo                Combina com *fo
foo
food
foodf
foodfo            Combina com *fo
foodfor
foodfort
foodforth
foodfortho
foodforthou
foodforthoug
foodforthough
foodforthought
foodforthought.
foodforthought.j
foodforthought.jp
foodforthought.jpg

Após procurar por combinações na string, você pode ver que o bash encontrou duas combinações. Ele seleciona a maior combinação, remove ela do início da string original, e retorna o resultado.

A segunda forma de expansão de variável apresentada acima parece ser idêntica à primeira, exceto que ela usa somente um "#" -- e o bash faz um processo quase idêntico. Ele checa o mesmo conjunto de substrings que o nosso primeiro exemplo fez, exceto que ele remove a combinação menor de nossa string original, e retorna o resultado. Assim, logo que ele encontra a substring "fo", ele a remove de nossa string e retorna "odforthought.jpg".

Isto pode parecer extremamente críptico, de forma que eu vou mostrar uma forma simples de lembrar esta funcionalidade. Quando estiver procurando pela maior combinação, use ## (por que ## é maior que #). Quando estiver procurando pela combinação mais curta, use #. Como vemos, não é difícil de lembrar! Espere, como você vai lembrar que o caracter "#" é usado para remover do "início" de uma string? Simples! você deve notar que em um teclado US, o Shift-4 é o "$", que é o caracter de expansão de variável. No teclado, imediatamente na esquerda do "$" está o "#". Assim, voc~e pode ver que o "#" está "no início" de "$", e assim (de acordo com nosso mneumônico), o "#" remove caracteres do início da string. Você pode estar se perguntando como removemos caracteres do fim da string. Se você "chutar" que usamos o caracter imediatamente à direita do "$" no teclado US ("%"), você acertou! Aqui tem alguns rápidos exemplso de como cortar porções finais de strings:

$ MYFOO="chickensoup.tar.gz"
$ echo ${MYFOO%%.*}
chickensoup
$ echo ${MYFOO%.*}
chickensoup.tar

Como você pode ver, as opções de expansão de variável % e %% funcionam de forma idêntica ao # e ##, exceto que removem a substring que combina no fim da string. Note que vicê não precisa usar o "*" se você quer remover uma substring específica do fim:

$ MYFOOD="chickensoup"
$ echo ${MYFOOD%%soup}
chicken

Neste exemplo, não importa se usamos o "%%" ou o "%", uma vez que somente uma combinação é possível. E lembre-se, se você esquecer se deve usar o "#" ou o "%", olhe para as teclas 3, 4 e 5 no seu teclado e descubra por si.

Podemos usar outra forma de expansão de variável para selecionar uma substring específica, baseado em um deslocamento e comprmento de caracteres específicos. Tente escrever as seguintes linhas no bash:

$ EXCLAIM=cowabunga
$ echo ${EXCLAIM:0:3}
cow
$ echo ${EXCLAIM:3:7}
abunga

Esta forma de cortar strings é bastante útil. Basta especificar o carcter a partir do qual iniciar e o comprimento da substring, todos separados por dois pontos.

Aplicando o corte de strings

Agora que aprendemos tudo sobre cortar strings, vamos escrever um pequeno script shell simples. Nosso script irá aceitar um nome de arquivo como argumento, e vai imprimir se ele parece ser um tarball. Para determinar se é um tarball, ele vai procurar pelo padrão ".tar" no fim do arquivo. Aqui está o script:

mytar.sh -- um script de exemplo

#!/bin/bash

if [ "${1##*.}" = "tar" ]
then
        echo This appears to be a tarball.
else
        echo At first glance, this does not appear to be a tarball.
fi

Para executar este script, entre o mesmo em um arquivo chamado mytar.sh, e execute o comando "chmod 755 mytar.sh" para torná-lo executável. A seguir, tente o mesmo com um tarball, conforme segue:

$ ./mytar.sh thisfile.tar
This appears to be a tarball.
$ ./mytar.sh thatfile.gz
At first glance, this does not appear to be a tarball.

OK, funciona, mas não é muito funcional. Antes que o tornemos um pouco mais útil, vamos dar uma olhada na declaração "if" acima. Nela, temos uma expressão booleana. No bash, o operador de comparação "=" verifica igualdade de strings. No bash, todas expressões booleanas ficam entre colchetes. Mas o que a expressão booleana está testando? Vamos dar uma olhada no lado esquerdo. De acordo com o que aprendemos sobre corte de strings, "${##1*.}" irá remover a combinação mais longa de "*." do início da string contida na variável de ambiente "1", retornando o resultado. Isto fará com que tudo que esteja após o último "." no arquivo será retornado. Obviamente, se o arquivo termina em ".tar", iremos obter "tar" como resultado, e a condição será verdadeira.

Você pode estar se perguntando o que a variável de ambiente "1" é, em primeiro lugar. Muito simples -- $1 é o primeiro argumento da linha de comando do script, $2 é o segundo, etc. OK, agora que já revisamos a função, podemos dar nossa primeira olhada nas declarações "if".

Declaração if

Como a maioria das linguagens, o bash possu sua própria forma de condicional. Quando estiver usando condicionais, use a forma acima, ou seja, coloque o "if" e o "then" em linhas separadas, e coloque o "else" e o "fi" obrigatório em alinhamento horizontal com o "if". Isto faz com que o código seja mais fácil de ler e depurar. Alpem da forma "if, else", existem várias outras formas de declaração "if":

if [ condition ]
then
        action
fi

Este "if" somente executa uma ação se condition for verdadeiro, senão ele não executa ação alguma e continua executando quaisquer linhas que seguem o "fi".

if [ condition ]
then
	action
elif [ condition2 ]
then
	action2
.
.
.
elif [conditionN ]
then
	actionN
else
	actionx
fi

A forma "elif" acima irá testar cada condição consecutivamente e executar a ação correspondente à primeira condição verdadeira. Se nenhuma das condições for verdadeira, ele irá executar a ação "else", se houver uma presente, e então continuará executando as linhas que seguem a declaração "if, elif, else".

A próxima vez

Agora que cobrimos a funcionalidade mais básica do bash, é hora de aproveitar o embalo e preparar-se para escrever alguns scripts reais. No próximo artigo, irei cobrir construções de repetição, funções, espaços de nomes, e outros tópicos essenciais. Então estaremos prontos para escrever alguns scripts mais complicados. No terceiro artigo, iremos nos concentrar quase exclusivamente em um script bastante complexo e funções, bem como várias opções de projeto de scripts bash. Até lá!

Recursos

Sobre o autor

Residindo em Albuquerque, Novo México, Daniel Robbins é o Chief Architect do Gentoo Project, CEO da Gentoo Technologies, Inc., o mentor do Linux Advanced Multimedia Project (LAMP), e um autor-contribuinte dos livros da Macmillan Caldera OpenLinux Unleashed, SuSE Linux Unleashed, e Samba Unleashed. Daniel está envolvido com computadores de alguma forma desde o segundo grau, quando ele foi exposto pela primeira vez à linguagem de programação Logo, bem como a uma dose podencialmente perigosa de Pac Man. Isto provavelmente explica por que ele tem servido desde então como Lead Graphic Artist na SONY Eletronic Publishing/Psygnosis. Daniel gosta de passar o tempo com sua esposa, Mary, que está esperando uma criança para esta primavera. Ele pode ser encontrado no email drobbins@gentoo.org.

1