Programação com o shell |
O shell, específicamente o bash, no nosso caso, é muito mais que uma simples interface para interpretação e execução de comandos. Ele é uma linguagem de programação poderosa, com estruturas de controle de alto nível, capaz de resolver muitos problemas com uma perda insignificante de velocidade na maioria das aplicações.
Já vimos como o shell executa interativamente. Na realidade a grande maioria dos comandos que estivemos analisando aqui foram comandos submtetidos ao shell para execução. Como poderíamos ampliar esse conceito, colocando comandos mais usados em um arquivo para posterior execução? Vejamos um simples exemplo:
#!/bin/sh echo "O último comando executado retornou $?" echo "O nome deste programa é $0" echo "O PID deste processo é $$" echo "O número de argumentos é $#" echo "Eles são $*" echo "O primeiro é $1" exit |
Editando este programa no arquivo mostra-args
e transformando ele
em executável com chmod +x mostra-args
, podemos
testá-lo com: ./mostra-args Linux invade o Brasil.
Isto
retornará a seguinte saida:
~$ mostra-args Linux invade o Brasil O último comando executado retornou 0 O nome deste programa é ./mostra-args O PID deste processo é 1305 O número de argumentos é 4 Eles são Linux invade o Brasil O primeiro é Linux |
A primeira linha do script contém uma sequência "mágica"
de caracteres que indica ao shell que este programa é
executável, e que deve ser invocado com o programa /bin/sh
(que é o próprio bash!). As linhas com
"#"
são comentários, exceto pelo caso especial
"#!"
quando no começo do arquivo. Os parâmetros com
nome $1
a $9
são os argumentos passados ao
programa, $0
é o nome do comando (o arquivo onde foi
colocado este script acima), $*
é a lista de todos os
argumentos (exceto o nome do programa), e $#
é o
número de argumentos passados ao programa, $?
é o
resultado (produzido com exit <resultado>
) do último
comando executado, e $$
é o PID (Process id) do process
corrente.
Observe este outro script: (programa à esquerda, execução à direita)
#!/bin/sh # putdelim -- devolve parametros como # lista delimitada "",""... for x in $* do echo -n \"$x\", done echo -e "\b " exit |
~$ putdelim recife s.paulo brasilia "recife","s.paulo","brasilia" |
A construção for variavel in lista do ... done
repete todo o interior (entre do
e done
), atribuindo
para cada repetição um dos valores da lista
para a
variavel
. Observe que o do
tem que estar em linha
separada do for
. O echo
no interior do loop
(repeticão) usa a chave -n
para evitar o avanço de
linha (newline) no final da impressão, enquanto que o echo
final usa a chave -e
para permitir a interpretação
de caracteres especiais como o \b
(backspace), usado neste caso
para apagar a última vírgula deixada pelo loop.
#!/bin/sh # tst-hora-almoco if [ `date +%H%M` -eq 1200 ] then echo Hora de almoçar! fi exit |
~$ tst-hora-almoco Hora de almoçar! |
Agora introduzimos outra estrutura de controle if ... then ...
fi
. O teste é uma função que tem duas formas:
[ expressão ]
ou
test expressão
. Preferimos a primeira, mais elegante.
O comando date +%H%M
devolve 4 dígitos com a hora e minuto
corrente concatenados., que é usado para testar igualdade (chave
-eq
do comando test
) com o valor 1200
.
Se a hora atual coincidir com 12:00 h, a mensagem será mostrada,
senão nada acontecerá. A estrutura if pode ter múltiplas
cláusulas com elif .. then
ou uma cláusula
alternativa com else ...
.
O comando test
suporta muitos tipos de testes distintos,
não só para comparações entre variáveis,
mas também para testar strings (cadeias de caracteres) e várias
propriedades de arquivos. A tabela abaixo sumariza essas
operações.
-op arquivo | arquivo existe e é/tem... | arq1 -op arq2 | significa... | comp. diversas | (arg = argumento) |
-b
-c -d -e -f -g -k -L -p -r -s -S -u -w -x -O -G |
dispositivo de bloco
dispositivo de caracter diretório existe (e mais nada) arquivo normal sgid (set group id) bit stick ligado link simbólico pipe readable tamanho > 0 socket suid (set user id) writable executável propriedade do EUID atual propriedade do EGID atual |
-nt
-ot -ef |
arq1 mais novo que arq2
arq1 mais velho que arq2
são hard link um do outro |
-z string
-n string string1 = string2 string1 != string2 ! expr expr1 -a expr2 expr1 -o expr2 arg1 -eq arg2 arg1 -ne arg2 arg1 -lt arg2 arg1 -le arg2 arg1 -gt arg2 arg1 -ge arg2 |
string com tamanho 0
string com tamanho > 0 strings iguais strings diferentes expressão falsa expr1 AND expr2 expr1 OR expr2 arg1 = arg2 arg1 != arg2 arg1 < arg2 arg1 <= arg2 arg1 > arg2 arg1 >= arg2 |
Comandos do shell podem estar na mesma linha, se separados por ";". Veja como
ficaria o if
se reescrevessemos com vários comandos na
mesma linha:
if [ `date +%H%M` -eq 1200 ]; then echo Hora de almoçar! ; fi exit
Esse tipo de "economia de espaço" deve ser evitada a todo custo, pois
torna o script quase ilegível. Note que o then
não
tem vírgula após, pois ele não é realmente um
comando.
#!/bin/sh # pergunta-nome echo -n "Entre seu nome:" read NOME SOBRENOME echo "Prazer em encontrá-lo $NOME!" exit |
~$ pergunta-nome |
Aqui introduzimos o comando read
, que lê stdin um ou mais
argumentos e armazena em variáveis (no caso NOME
e
SOBRENOME
) do environment. Os separadores contidos na
variável IFS são usados para quebrar a linha em tokens
(palavras. "Token" é ficha). Note também que, para
obter o nome do usuário, não seria necessário essa
pergunta, pois ele está disponível em $LOGNAME
.
#!/bin/sh # le-arquivos -- le os vários arquivos # intoduzidos como argumentos e os # mostra na saida LINE= # linha obtida do arquivo IFS= # sem separadores while [ $# -gt 0 ] do if [ ! -r "$1" ]; then echo "Não consigo encontrar $1" >&2 exit 1 else while read LINE do echo "$LINE" done < $1 fi shift done exit |
~$ le-arquivos fortune1 fortune2 "I am not an Economist. I am an honest man!" -- Paul McCracken The truth is what is; what should be is a dirty lie. -- Lenny Bruce |
Neste exemplo, o while <teste> do ... done
é uma
estrutura que repete os comandos no seu interior enquanto o
<teste>
for verdade. Existe também o
until
, que repete igualmente mas enquanto o
<teste>
for falso. O comando read
é usado
para ler a entrada do stdin, mas nesse exemplo, stdin está
redirecionado para o arquivo (linha com done < $1
).
Finalmente, o comando shift
permite ler mais que 9 argumentos
(variáveis $1 ... $9
), deslocando os seus valores. A
variável $9
assumirá o valor da próxima
(seria $10
, que não é possível), e a
$1
será perdida.
Observe que este programa (le-arquivos
) é similar ao
comando cat
, já existente.
#!/bin/sh # sel-edit -- seleciona um arquivo # fonte (*.c *.h) e edita select FNAME in *.c *.h do joe $FNAME done exit |
~$ sel-edit 1) checklist.c 7) kvptyaux.c 13) ptytest.c 2) dialog.c 8) menubox.c 14) rc.c 3) guage.c 9) mouse.c 15) tcl_curses.c 4) inputbox.c 10) msgbox.c 16) textbox.c 5) kvadap_main.c 11) pcnt.c 17) util.c 6) kvpty.c 12) ptypair.c 18) yesno.c #? |
Como podemos ver, este comando gera um menu com todos os argumentos dado na
lista (no caso *.c *.h
) e cria um prompt diferente (que pode ser
editado na variável PS3 do environment). Então o usuário
deve escolher uma opção do menu e os comandos entre do ... done
serão executados com a variável (no caso FNAME
)
atribuida o valor correspondente à opção do menu
escolhida. O comando é repetido continuadamente até o
usuário introduzir eof (Ctrl-d).
Agora iremos elaborar um programa para nos lembrar dos feriados mais
importantes. Usaremos uma nova estrutura de controle, o case
, que
é uma espécide de múltiplo if...fi
. Sua
forma genérica é
case <var> in <pad1>) cmds1 ;; <pad2>) cmds2 ;; esac
.
#!/bin/sh # feriados -- mostra principais # feriados nacionais HOJE=`date +%d/%m` case $HOJE in 01/01) echo Confraternização universal ;; 24/06) echo São João ;; 07/09) echo Independência ;; 15/11) echo Procl. República ;; 25/12) echo Natal ;; *) echo Um dia como qualquer outro ;; esac exit |
~$ feriados Um dia como qualquer outro |
O comando date +%m/%d
irá gerar a data no formato dd/mm
(exemplo, hoje é 15/01
), que é armazenada na
variável HOJE
.
O padrão "*", evidentemente, "casa" com qualquer coisa, por isso foi
usado como última opção.
O bash, com qualquer linguagem procedural, possui funções. Seu
formato genérico é <nome da função> ( ) {
... }
. Adicionalmente, funções podem ter variáveis
locais, precedendo a tribuição da palavra local
.
Funçoes do shell podem ser recursivas, como mostra a
função fatorial abaixo. Edite o arquivo em qualquer arquivo
(pode até mesmo ter o nome fat
, mas não
necessariamente) e execute alguns exemplos. Esta função cresce
rapidamente, portanto não use valores que não caibam em um
número inteiro, senão os valores obtidos serão
inconsistentes (zero). Se for requerido maior precisão, use o programa
bc
para fazer os cálculos.
#!/bin/sh # função fatorial fat () { local num=$1; if [ "$num" = 1 ] ; then echo 1 return ; fi; echo $(( $num * $(fat $(( $num - 1 )) ) )) } echo `fat $1` exit |
~$ fat 5 120 ~$ fat 23 862453760 |
Seria necessário um livro inteiro somente sobre o bash para cobrir de forma compreensiva as suas características. Demos apenas uma amostra do que pode ser feito com este shell, mas gostaríamos de deixar claro alguns comandos importantes que foram deixados de fora e sua finalidade.
break
while, until
ou for
),
abortando a execução deste. Com um parâmetro
"n"
aborta n
loops de profundidade.
continue
return
wait
trap
trap <cmando> <sinal>
.
exec
exec 3< carta.txt
abrirá o arquivo para
leitura com o descritor 3.
Outro exemplo:
exec 3< .profile; cat <&3
irá
listar o arquivo, mas usando outro descritor (3) para a leitura.
eval
~$ MEU_TERMINAL= \$TERM ~$ echo $MEU_TERMINAL $TERM ~$ eval echo $MEU_TERMINAL xterm