O objectivo da programação em shell, é simplificar a execução de múltiplos comandos, e a realização de ciclos repetitivos tirando partido dos potentes comandos existentes no UNIX. Estes "programas" apesar de serem muitas vezes mais lentos a executar que os programas escritos em C/C++ ou ADA, têm a vantagem de não necessitarem de ser compilados. E como na maior parte das vezes, as capacidades que necessitamos podem ser conseguidas com um ou mais comandos UNIX, os shell scripts tornam a vida de um administrador muito mais simples.
O primeiro passo na criação de um script, é fazer um ficheiro de texto com o script. Para dizer à shell, que o ficheiro de texto é um script, devemos escrever nos dois primeiros caracteres da 1ª linha o código #!, que é o magic code referente a script (ver comando file). A seguir a este código deve-se indicar o interpretador para o script, no nosso caso /bin/bash, seguido dos parâmetros de configuração da shell (ex. -noprofile, para não carregar os ficheiros de profile). Os scripts perl e tcl/tk, etc. também utilizam este código, sendo somente alterado o interpretador.
A partir deste momento (se a flag x estiver activa), podemos executar o script directamente na linha de comando (./nome_do_script). A definição do interpretador do script é importante, pois se corrermos dentro de uma shell csh um script feito para bash sem a indicação da shell a utilizar, irão ocorrer erros devidos à diferente sintaxe destas shells (experimentem correr o script ola_mundo com a csh ou a tcsh).
O carácter # é também um carácter especial, que indica à shell que tudo o que está até ao fim da linha é comentário. No entanto, alguns comandos que utilizam este carácter (ex. ${#variável} - indica o tamanho em caracteres do conteúdo da variável)
De seguida apresenta-se um exemplo de como deve ser um script. Apesar de não serem necessários, e muitas vezes os programadores não os utilizarem, os comentários podem ser muito úteis mais tarde, quando quisermos saber para que serve um script, ou alguma parte do código. Ver o que fazer e o que não fazer nesta página.
#!/bin/bash -noprofile # Autor: Paulo Matos # Data: 4/11/2002 # Programa: Olá Mundo # Versão: 1.0 # Modo de Utilização: ola_mundo.sh [<device>] # Função: Escrever Olá Mundo para o device passado como parâmetro ou para o # device devolvido pelo comando tty # # Ficheiros: Ficheiros que o script utiliza # # Notas: Notas sobre mais pormenorizadas que as descritas em Função # sobre o script # Historia: Revisões e alterações efectuadas ao script # echo Olá Mundo > ${1:-`tty`} 2> /dev/null # escreve olá mundo para o device e os erros para o "lixo"
O essencial sobre variáveis foi dito na página relativa à shell. No entanto ainda existem algumas coisas relativas à programação que são explicadas de seguida.
Variável | Significado |
---|---|
$0 | nome do script executado |
$1 até $9 | os primeiros 9 parâmetros passados ao script |
$# | número de parâmetros passados ao script |
$* e $@ | indicam todos os argumentos passados ao script (ver o script param.sh) |
$! | pid do último comando executado |
$? | valor devolvido pelo último comando executado |
Como só é possível aceder a 9 parâmetros de cada vez, existe o comando shift, que atribui ao parâmetro $0 o $1, ao $1 o $2, e assim sucessivamente. No caso de ainda existirem mais de 9 parâmetros, ao $9 é atribuído o 10º parâmetro.
A partir do momento que é efectuado um shift, não é mais possível aceder ao parâmetro eliminado. O comando shift altera também as variáveis $#, $* e $@. No script param.sh, podem-se ver as diferenças entre a utilização de $*, "$*", $@, "$@" e shift.
#!/bin/bash # nome: param.sh # utilização: param.sh [<lista de parâmetros>] # exemplo: param.sh texto 123 numero olá "teste com aspas" 'teste com plicas !!!' echo '---- $* ----' i=1 for valor in $* do echo "arg $i: $valor" i=$[$i+1] done echo '---- "$*" ----' i=1 for valor in "$*" do echo "arg $i: $valor" i=$[$i+1] done echo '---- $@ ----' i=1 for valor in $@ do echo "arg $i: $valor" i=$[$i+1] done echo '---- "$@" ----' i=1 for valor in "$@" do echo "arg $i: $valor" i=$[$i+1] done echo "---- shift ----" i=1 while [ "$1" ] # equivalente a teste "$1" (ver comando test, ou executar help test) do echo "arg $i: $1" i=$[$i+1] shift done
Sempre que o utilizador necessita ler dados do stdin, pode utilizar o comando read
#!/bin/bash # Nome: read.sh echo -n "escreva qualquer coisa:" # ver a sintaxe do echo com o comando help echo read texto # ver a sintaxe do read com o comando help read echo "Escreveu:$texto"
Em todos as comandos que aceitam condições (ex. if, while) deve ser posto um comando nessa condição, sendo avaliado o valor devolvido por esse comando.
if ls "$1" > /dev/null 2>&1; then echo true else echo false fi
No exemplo anterior, se o comando ls devolver true (0) o código relativo ao then é executado, se devolver false (1) é executado o código relativo ao else.
Muitas das vezes o que queremos usar numa condição, é um teste a uma variável, uma comparação de valores, ou ver se um ficheiro existe e se pode ser escrito. Para conseguir isto, existe o comando test, que pode ser simplificado na sua escrita para [ (como utilizado no script param.sh)
Devem ter em atenção que como a separação de argumentos para o comando test é efectuada pela shell se uma variável tiver o valor "olá mundo", esta variável deve ser passada ao test entre aspas, pois de outro modo serão passados dois parâmetros e será gerado um erro. Do mesmo modo todos os argumentos devem ser separados por espaços pois de outro modo são identificados como um único parâmetro.
Existem vários switches que podem ser utilizados com o comando test e que podem ver nesta página. Na tabela seguinte está um resumo dos switches do comando test.
Expressão | Verdadeira se |
---|---|
-z string | tamanho de string é 0 |
-n string | tamanho de string não é 0 |
string1 = string2 | a string1 é igual à string2 |
string1 != string2 | a string1 é diferente à string2 |
string | a string não é nula |
int1 -eq int2 | o int1é igual ao int2 |
int1 -ne int2 | o int1é diferente ao int2 |
int1 -gt int2 | o int1é maior ao int2 |
int1 -ge int2 | o int1é maior ou igual ao int2 |
int1 -lt int2 | o int1é menor ao int2 |
int1 -le int2 | o int1é menor ou igual ao int2 |
-r ficheiro | o ficheiro existe e pode ser lido |
-w ficheiro | o ficheiro existe e pode ser escrito |
-x ficheiro | o ficheiro existe e pode ser executado |
-f ficheiro | o ficheiro existe e é um ficheiro normal |
-d ficheiro | o ficheiro existe e é um directório |
-h ficheiro | o ficheiro existe e é um link simbólico |
-c ficheiro | o ficheiro existe e é um device da caracteres |
-b ficheiro | o ficheiro existe e é um device de blocos |
-p ficheiro | o ficheiro existe e é um named pipe |
-u ficheiro | o ficheiro existe e é tem o setuid activo |
-g ficheiro | o ficheiro existe e é tem o setgid activo |
-k ficheiro | o ficheiro existe e é tem o seticky bit activo |
-s ficheiro | o ficheiro existe e tem um tamanho maior do que 0 |
! | o resultado inverso de uma expressão |
-a | o operador lógico e (and) |
-o | o operador lógico ou (or) |
( expr ) | agrupa uma expressão, como os parêntesis têm significado especial para a shell, tem de ser de lhes ser retirado esse significado para o comando test. |
De seguida são apresentadas as duas sintaxes possíveis para o comando if. Podem ser encadeados vários if. Devem ter em atenção que se o then ficar na mesma linha do if, têm separá-lo da condição com um ; (tentem descobrir porquê)
if comando; then comando_if_1 comando_if_2 .... else # opcional comando_else_1 comando_else_2 .... fi # obrigatório
if comando; then comando_if_1 comando_if_2 .... elif comando; then comando_elif_1 comando_elif_2 .... fi
Se o comando devolver 0 (true), é executado o código do then, se devolver 1 (false) é executado o código do else (se existir else).
No exemplo seguinte foi escrito o if numa só linha, sendo necessário colocar o ; a separar os vários "sub comandos".
if ls "fich.txt" > /dev/null 2>&1; then echo true; else echo false; fi
De seguida é apresentada as sintaxe do comando case. Nesta página existem vários exemplos com case.
case palavra in padrao11 [| padrao12...]) comando11 comando12 .... ;; padrao21 [| padrao22...]) comando21 comando22 .... ;; ..... esac
A seguir é apresentado um exemplo de um case, sendo utilizado o | para separar as alternativas de padrões.
echo -n "Escreva a sua resposta (s/n)?" read resposta case "$resposta" in s* | S*) echo "A resposta foi sim" ;; [nN]*) echo "A resposta foi não" ;; *) echo "A resposta foi talvez :)" # isto é um else ;; esac
A seguir é apresentado o case anterior, condensado numa só linha.
case "$resposta" in s* | S*) echo "A resposta foi sim" ;; \ [nN]*) echo "A resposta foi não" ;; *) echo "A resposta foi \ talvez :)" ;; esac
A seguir é apresentada a sintaxe dos ciclos while e until.
while comando do comando1 comando2 .... done
until comando do comando1 comando2 .... done
A seguir são apresentados exemplos do comando while e until.
i=10 while [ $i -ne 0 ] do echo "$i"; i=$[$i-1] done
i=10; while [ $i -ne 0 ]; do echo "$i"; i=$[$i-1];done
i=10; until [ $i -eq 0 ]; do echo "$i"; i=$[$i-1];done
O stdin (ou o stdout) de um ciclo while ou until, pode ser redireccionado para (de) um ficheiro tal como demonstrado no exemplo seguinte.
while read buffer do echo -e "`echo $buffer | cut -d" " -f9` \t\t `echo $buffer | cut -d" " -f5`" done < fich.lst # ficheiro com uma listagem longa de um directório
Nesta página podem ser vistos exemplos dos ciclos while e until
A seguir é apresentada a sintaxe do ciclo for. Se a opção in lista_de_valores for omitida, o for utiliza a variável "$@".
for variavel [in lista_de_valores] do comando1 comando2 .... done
A seguir são apresentados alguns exemplos do comando for.
for numeros in "1 um" "2 dois" "3 tres" "4 quatro" "5 cinco" "6 seis" do set -- $numeros # faz o parsing da variável $numeros e atribui às variáveis posicionais # se necessitar dos argumentos originais, estes devem ser copiados. # args_originais=("$@") antes de entrar no ciclo echo "$1($2)" done
for i in 10 9 8 7 6 5 4 3 2 1; do echo "$i"; done
Nesta página podem ser vistos exemplos do ciclo for
Há alturas em que é necessário sair de um ciclo a meio, ou saltar de novo para o teste do ciclo.
Para interromper um ciclo é utilizada o comando break, que pode ter como parâmetro o número de níveis a sair (para ciclos encadeados). Ao sair de um ciclo a execução normal do script, é retomada depois o código do(s) ciclo(s).
Para continuar na próxima iteração de um ciclo, existe o comando continue que também pode receber um inteiro como parâmetro, que indica qual é o número de níveis que sai (ao contrário do break não continua a execução normal, mas vai para o teste do ciclo).
A seguir estão exemplos simples do break e do continue. Mais exemplos podem ser obtidos nesta página.
for i in 10 9 8 7 6 5 4 3 2 1 do if [ $i -lt 6 -a $i -gt 3 ]; then continue; fi echo "$i" done
for i in 10 9 8 7 6 5 4 3 2 1 do if [ $i -lt 6 -a $i -gt 3 ]; then break; fi echo "$i" done
Em programação shell, também é possível criar funções, e inclusive executar funções revulsivas. A seguir é apresentada um exemplo de uma função. Repare que a variável $num dentro da função é definida como local, sendo por isso diferente da variável $num existente fora da função.
#!/bin/bash ERRO=-1 function factorial # também podia ser definida como factorial() { local num=$[$1-1] if [ $1 -lt 0 -o $1 -gt 12 ]; then return $ERRO # devolve o valor definido para os erros (podiam ser diferentes) fi if [ $1 -eq 0 ]; then return 1 else factorial $num res_fac=$[$?*($num+1)] # o valor num é sempre local fi return $res_fac # devolve o resultado } number() { echo $1|grep -E -q -e "^[-+]?[0-9]*$" # o resultado devolvido, é o resultado do último comando executado } num=${1:-0} if number $num; then factorial $num res_factorial=$? if [ "$res_factorial" -ne $ERRO ]; then echo "o factorial de $num e' $res_factorial" else echo "Erro no factorial, número negativo ou maior que 12" fi else echo "o argumento passado não é um número" fi
Quando estamos a criar scripts, pode ser necessário executar comandos em background, e mais à frente esperar que esse comando acabe para executar outro. O comando wait pid permite ficar à espera que o comando com o PID igual a pid acabe de executar. Se não for utilizado o argumento pid, é utilizado o pid do último comando executado ($!).
echo "vou correr um sleep de 10 segundos em background" sleep 10 & pid_sleep10=$! echo "vou correr um sleep de 4 segundos em background" sleep 4 & pid_sleep4=$! echo "estou a fazer outra coisa qualquer" echo "vou esperar que o sleep de 4 acabe" wait $pid_sleep4 echo "o sleep 4 acabou" echo "vou esperar que o sleep de 10 acabe" wait $pid_sleep10 echo "o sleep 10 acabou"
Quando um determinado script recebe um sinal enviado pelo kill, existe a possibilidade de executar algum código relativo a esse sinal. O comando trap <comandos> <sinais> permite ao script quando recebe um determinado sinal, enviado pelo kill, executar os comandos (ex. apagar ficheiros temporários). Não é possível fazer um trap a um sinal SIGKILL(9). Existem vários exemplos do trap nesta página
#!/bin/bash trap 'echo "recebi um SIGHUP"' SIGHUP # igual a 1 ou HUP trap 'rm fich[12].tmp;echo "saida normal do programa"' EXIT # sinal de saida normal trap 'echo "saida com SIGQUIT ou SIGILL";exit 1' QUIT ILL # trata 2 sinais trap 'echo "Control-C disabled."' 2 # trata o ctrl-c SIGINT : > fich1.tmp # Cria um ficheiro vazio. o comando : não faz nada. : > fich2.tmp i=0 while [ $i -lt 35 ] do i=$[$i+1] echo $i if [ $i -eq 15 ]; then trap SIGINT # desactiva trap para o SIGINT echo "O Control-C já funciona" fi sleep 1 done exit 0
Este comando já foi apresentado na secção relativa à shell e serve para avaliar um comando 2 vezes. Uma das utilidades deste em scripting é guardar nomes de variáveis em variáveis, e aceder a estas variáveis em run-time. Nesta página existem alguns exemplos com o comando eval.
nome_var=var1 var1="Este é o conteudo da variavel var1" echo "resultado do comando sem eval=\$$nome_var" eval echo "resultado do comando com eval=\$$nome_var"
Apesar da notação aqui apresentada ser a mais usual em scripts para a bash, nas versões mais recentes da bash, existe uma notação alternativa parecida com a do C, que se pode aplicar a atribuições, condições aritméticas, ciclos for e ciclos while.
Esta notação utiliza uma série de macros, e é conseguida utilizando as sintaxe (( ... )), não sendo necessário incluir o $ para referir variáveis.
Ultima alteração: terça-feira, 01 de Abril de 2003 às 16:06