fonte: http://www.linuxfocus.org/Portugues/September2001/article216.shtml


Programação Shell

[Illustration]

Abstrato:

Neste artigo iremos explicar como escrever pequenos scripts shell e dar vários exemplos.

Porquê programação shell ?

Apesar existirem varias interfaces graphicas disponíveis para Linux o shell continua a ser uma ferramenta muita eficiente. O shell não é só uma colecção de comandos mas uma verdadeira boa linguagem de programação. Com ele você pode automatizar numerosas tarefas, o shell é muito bom para tarefas de administração de sistema, você pode muito rapidamente testar se as suas ideias funcionam o que torna muito útil para uma prototipagem simples e é muito útil para pequenos utilitários que executam tarefas relativamente simples onde a eficiência é menos importante que o conforto da configuração, manutenção e portabilidade.
Então vamos ver como ele funciona:

Criar um script

Existem muitos shells diferentes disponíveis para Linux mas habitualmente o bash (bourne again shell) é utilizado para programação shell porque ele é grátis e é fácil de utilizar. De modo que todos os scripts que iremos escrever neste artigo utiliza o bash (mas na maior parte do tempo funciona também com a sua velha irmã, o bourne shell).
Para escrever os nossos programas shell podemos utilizar qualquer editor de texto, ex: nedit, kedit, emacs, vi… como em qualquer outra linguagem de programação.
O programa tem de começar com a linha seguinte (deve ser a primeira linha no ficheiro):

 #!/bin/sh

Os caracteres #! informam o sistema que o primeiro argumento que segue na linha é o programa utilizado para executar este ficheiro. Neste caso /bin/sh é o shell que utilizamos.
Depois de escrever e gravar o script é necessário tornar-lo executável para o poder utilizar.
Para tornar um script executável escreva
chmod +x nomeficheiro
Então pode executar o seu script escrevendo: ./nomeficheiro

Comentários

Na programação shell os comentários começam com # e vão até o fim da linha. Recomendamos a utilização dos comentários. Se têm comentários e que não utiliza um script desde um certo tempo você saberá imediatamente do que se se trata e como funciona.

Variáveis

Como nas outras linguagens de programação não se pode viver sem variáveis. Na programação shell todas as variáveis são de tipo string e não é necessário declara-las. Para atribuir um valor a uma variável basta escrever:

variável=valor

Para ler o conteúdo de uma variável basta colocar um cifrão antes da variável;

#!/bin/sh
# atribui um valor:
a="hello world"
# e agora escreve o conteúdo de "a":
echo "A é:" echo $a

Escreva estas linhas no seu editor de texto e grave com o nome first. Ponha o script executável escrevendo chmod +x first no shell e executa escrevendo ./first
O script ira unicamente escrever:

A é:
hello world

Por vezes é possível confundir os nomes das variáveis com o resto do texto:

num=2 echo "isto é o $numnd"

Isto não vai imprimir “isto é 2nd” mas “isto é ” porque o shell vai procurar por uma variável chamada numnd que não tem valor nenhum. Para dizer ao shell que estamos a fazer referência a variável num temos que usar as chavetas:

num=2 echo "isto é o ${num}nd"

Desta forma vai escrever: isto é o 2nd

Existem variáveis que são automaticamente inicializadas. Iremos discutir delas mais abaixo a primeira utilização.

Se necessita de manipular expressões matemáticas então precisa de utilizar programas como expr (ver a tabela seguinte).
Apesar das variáveis de shell normais ser unicamente validas em programas shell existem também variáveis de ambiente. A variável precedida com a palavra-chave export é uma variável de ambiente. Não iremos mais falar sobre elas sabendo que elas são normalmente utilizadas em scripts de login.

Comandos Shell e estruturas de control

Existem 3 categorias de comandos que podem ser utilizadas em scripts shell:

1)Comandos Unix:
Se bem que um script shell pode utilizar qualquer comando unix existem comandos que são utilizados mais vezes que outros. Esses comandos podem ser descritos como comandos para manipulação de ficheiro e de texto.

Syntax de comandos Objectivo
echo “algum texto” escreve algum texto no ecrã
ls lista ficheiros
wc -l ficheiro
wc -w ficheiro
wc -c ficheiro
conta as linhas num ficheiro ou
conta o numero de palavras
conta os numeros caracteres
cp ficheiroorigem ficheirodestino copia ficheiroorigem para ficheirodestino
mv nomeantigo novonome renomeia ou move um ficheiro
rm ficheiro apaga um ficheiro
grep ‘qualquercoisa’ ficheiro procura por strings num ficheiro
Exemplo: grep ‘qualquercoisa’ ficheiro.txt
cut -b colnum file extrai dados de uma coluna fixa de texto
Exemplo: extrai caracteres da posição 5 a 9
cut -b5-9 file.txt
Não confundir com o comando “cat” que faz uma coisa completamente diferente
cat ficheiro.txt escreve o conteúdo de ficheiro.txt no stdout (seu ecrã)
file ficheiroqualquer descreve qual é o tipo do ficheiro ficheiroqualquer
read var pede ao utilizador para escrever e coloca numa variável (var)
sort ficheiro.txt ordena as linhas no ficheiro.txt
uniq remove as linhas duplicadas, utilizado em combinação com sort visto uniq remover unicamente linhas duplicadas consecutivas
Exemplo: sort ficheiro.txt | uniq
expr faz matemática no shell
Exemplo: adiciona 2 e 3
expr 2 “+” 3
find procura ficheiros
Exemplo: procura por nome:
find . -name ficheiro -print
Este comando tem muitas possibilidades e opções. É demasiado para ser explicado ao pormenor neste artigo.
tee escreve os dados para stdout (seu ecrã) e para um ficheiro
Normalmente utilizado da seguinte forma:
umcomando | tee ficheiroout
Ele escreve o output de umcomando para o ecrã e para o ficheiro ficheiroout
basename ficheiro devolve o nome do ficheiro de um determinado ficheiro e remove o caminho
Exemplo: basename /bin/tux
devolve unicamente tux
dirname ficheiro devolve unicamente o nome da directoria de um determinado nome e remove o nome do ficheiro
Exemplo: dirname /bin/tux
devolve unicamente /bin
head ficheiro escreve umas linhas desde o inicio do ficheiro
tail file escreve umas linhas desde o fim do ficheiro
sed sed é basicamente um programa de pesquisa e substituição. Ele lê texto de um input standard (ex desde um pipe) e escreve o resultado para stdout (normalmente o ecrã). O padrão de pesquisa é uma expressão regular (ver referências). Esse padrão de pesquisa não deve ser confundido com a syntax da wildcard do shell. Para substituir a string linuxfocus por LinuxFocus num ficheiro texto faça:
cat ficheiro.txt | sed ‘s/linuxfocus/LinuxFocus/’ > novoficheiro.txt
Isto substitui a primeira ocurência da string linuxfocus em cada linha com LinuxFocus. Se existirem linhas onde linuxfocus apareça varias vezes e que queira substituir todos faça:
cat ficheiro.txt | sed ‘s/linuxfocus/LinuxFocus/g’ > novoficheiro.txt
awk A maior parte das vezes awk é utilizado para extrair campos de uma linha de texto. O separador por defeito é espaço. Para definir um outro utiliza a opção -F.

 cat ficheiro.txt | awk -F, '{print $1 "," $3 }'

Neste caso estamos a utilizar a virgula (,) como separador de campos e escrevemos a primeira e terceira coluna ($1 $3). Se ficheiro.txt tiver linhas como:

 Adam Bor, 34, India Kerry Miller, 22, USA 

ira dar como resultado:

 Adam Bor, India Kerry Miller, USA

O awk permite fazer muita mais coisas mas esta é a utilização mais frequente.

2) Conceitos: Pipes, redirecionamento e backtick
Não são realmente comandos mas são conceitos muito importantes.

pipes (|) envia o output (stdout) de um programa para o input (stdin) de um outro programa.

 grep "hello"
 ficheiro.txt | wc -l

procura as linhas com a string hello no ficheiro.txt e conta as linhas.
O output do comando grep é utilizado como input para o comando wc. Dessa forma você pode concatenar os comandos que queira (nos limites do razoável).

redirecionamento: escreve o output de um comando para um ficheiro ou adiciona dados a um ficheiro
> escreve o output para um ficheiro e sobrepõem o ficheiro antigo caso ele existir
>> adiciona dados a um ficheiro (ou cria um novo se ele ainda não existir mas nunca sobrepõem nada).

Backtick
O output de um comando pode ser utilizado como argumento da linha de comandos (não stdin como anteriormente, os argumentos da linha de comandos são qualquer string que você pode especificar depois de um comando como nomes de ficheiros e opções) para um outro comando. Você pode também utiliza-lo para definir o output de um comando para uma variável.
O comando

 find . -mtime -1 -type f -print

procura todos os ficheiros que foram modificados nas ultimas 24 horas (-mtime -2 significaria 48 horas). Se você quer armazenar esse ficheiros num arquivo tar (ficheiro.tar) a syntax para o tar teria de ser:

 tar xvf ficheiro.tar ficheiro1 ficheiro2 ...

Em vez de escrever isso tudo, você pode combinar 2 comandos (find e tar) utilizando backticks. Tar ira aquivar todos os ficheiro que find escreveu:

 #!/bin/sh
# Backticks (`)  não plicas ('):
tar -zcvf ultimamod.tar.gz `find . -mtime -1 -type f -print`

3) Estruturas de control
A instrução “if” testa se uma condição é verdadeira (código retorno é 0, verdadeiro). Se for isso a parte “then” é executado:

if ....; then
   ....
elif ....; then
   ....
else
   ....
fi

A maior parte das vezes um comando especial chamado test é utilizado dentro de uma instrução if. Ele pode ser utilizado para comparar strings ou testar se um ficheiro existe, se ele pode ser lido etc…
O comando “test” é escrito com as parênteses rectos ” [ ] “. Nota que o espaço é significativo aqui: Certifica-se que tenha sempre um espaço entre as parênteses rectos. Exemplos:

[ -f "umficheiro" ] : Testa se umficheiro é um ficheiro.
[ -x "/bin/ls" ]    : Testa se /bin/ls exista e se é um executável.
[ -n "$var" ]       : Testa se a the variável $var tem algo.
[ "$a" = "$b" ]     : Testa se as variáveis "$a" e "$b" são iguais

Executa o comando “man test” para obter uma longa lista de todos os tipos de operadores de teste para comparações e ficheiros.
Utilizar isso num script shell é facil:

#!/bin/sh
if [ "$SHELL" = "/bin/bash" ]; then
  echo "o seu shell de login é o bash (bourne again shell)"
else
  echo "o seu shell de login não é bash mas sim $SHELL"
fi

A variável $SHELL contêm o nome do shell de login e ela é testada que é comparada com a string “/bin/bash”

Atalhos de operadores
As pessoas familiarizadas com C reconhecerão a expressão seguinte:

[ -f "/etc/shadow" ] && echo "Este computador utiliza shadow passwords"

Os && podem ser utilizados como uma curta instrução if. A parte direita é executada se a parte esquerda for verdadeira. Pode ler isso como AND. Desta maneira o exemplo é: “O ficheiro /etc/shadow exista AND o comando é executada”. O operador OR (||) esta também disponível. Segue um exemplo:

#!/bin/sh
mailfolder=/var/spool/mail/james
[ -r "$mailfolder" ] || { echo "Não pode ler $mailfolder" ; exit 1; }
echo "$mailfolder têm um mail de:"
grep "^From " $mailfolder

O script testa em primeiro se ele pode ler o mailfolder. Se for o caso ele escreve a linha “têm um mail” no folder, Se ele não conseguir ler o ficheiro $mailfolder então o operador OR entra em acção. Em Inglês você pode ler “Mailfolder readable or exit program”. O problema aqui é que têm de ter exactamente um comando atras do OR mas precisamos de 2:
– escrever uma mensagem de erro
– sair do programa
Para trata-los como um único comando podemos junta-los numa função anónima utilizando chavetas. As funções em geral será explicado mais abaixo.
Pode-se fazer tudo sem ANDs e ORs utilizando unicamente instruções if mas as vezes os atalhos AND e OR são mais práticos.

A instrução case pode ser utilizado para combinar (utilizando os wildcards do shell como * e ?) uma determinada string em comparação a numerosas possibilidades.

case ... in
 ...) faz qualquer coisa aqui;; esac

Vejamos um exemplo. O comando file pode testar qual é o tipo de um ficheiro:

file lf.gz

devolve:

lf.gz: gzip compressed data, deflated, original filename, last modified: Mon
Aug 27 23:09:18 2001, os: Unix

Vamos utilizar isto agora para escrever um script chamado smartzip que pode descomprimir bzip2, gzip and zip ficheiros comprimidos automaticamente:

#!/bin/sh
ftype=`file "$1"`
case "$ftype" in
"$1: Zip archive"*)
    unzip "$1" ;;
"$1: gzip compressed"*)
    gunzip "$1" ;;
"$1: bzip2 compressed"*)
    bunzip2 "$1" ;;
*) error "Ficheiro $1 no pode ser descomprimido com smartzip";;
esac

Aqui pode reparar que estamos a utilizar uma variável especial chamada $1. Essa variável contêm o primeiro argumento dado ao programa. Por exemplo executamos
smartzip articles.zip
então $1 ira conter a the string articles.zip

A instrução select é uma extensão especifica do bash e é muita boa para utilização interactiva. O utilizador por seleccionar uma opção entre uma lista de diferentes valores:

select var in ... ; do
   break
done
....  agora $var pode ser utilizado ....

Vejamos este exemplo:

#!/bin/sh
echo "Qual é o seu SO preferido ?"
select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do
   break
done
echo "Você seleccionou $var"

O que script faz:

Qual é o seu SO preferido ?
1) Linux
2) Gnu Hurd
3) Free BSD
4) Other
#? 1
Você seleccionou Linux

No shell você têm as seguintes instruções de ciclos disponíveis:

while ...; do
 ....
done

O ciclo-while sera executada até que a expressão que estamos a testar ser verdadeira. A palavra-chave “break” pode ser utiliza para abandonar um ciclo a qualquer altura. Com a palavra-chave “continue” o ciclo continua com a proxima iteração e ignora o resto do corpo do ciclo.

O ciclo-for pega numa lista de strings (srings separadas por espaço) e atribuas a variáveis:

for var in ....; do
 ....
done

O que sega ira imprimir as letras de A a C no ecrã:

#!/bin/sh for var in A B C ; do
  echo "var é $var"
done

Um outro exemplo de script util, chamado showrpm, print uma lista do conteúdo de um pacote RPM:

 #!/bin/sh
# lista um resumo do conteúdo de um pacote RPM
UTILIZAÇÃO: showrpm rpmficheiro1 # rpmficheiro2 ...
EXEMPLO: showrpm /cdrom/RedHat/RPMS/*.rpm
for rpmpackage in $*; do
  if [ -r "$rpmpackage" ];then
     echo "=============== $rpmpackage =============="
     rpm -qi -p $rpmpackage
  else
     echo "ERRO: não consegue ler o ficheiro file $rpmpackage"
  fi
done

Como pode ver a variável especial, $* que contêm todos os argumentos da linha de comando. Se você executar
showrpm openssh.rpm w3m.rpm webgrep.rpm
então $* ira conter as 3 strings openssh.rpm, w3m.rpm e webgrep.rpm.

O bash GNU conhece também os ciclos-until mas geralmente os ciclos while e for são suficientes.

Utilizando aspas
Antes de passar qualquer argumento a um programa o shell tenta desenvolver as wildcards e variáveis. Desenvolver significa que as wildcards (ex: *) são substituídas pelo o nome do ficheiro apropriado ou que essa variável é substituída pelo o seu conteúdo. Para mudar esse comportamento você pode utilizar aspas: Digamos que temos um numero de ficheiros numa directoria. 2 deles são ficheiros jpg, mail.jpg e tux.jpg.

#!/bin/sh echo *.jpg

Isto ira escrever “mail.jpg tux.jpg”.
Aspas e plicas ira impedir essa expansão de wildcard:

#!/bin/sh echo "*.jpg"
echo '*.jpg'

Isto ira escrever “*.jpg” 2 vezes.
As plicas são mais restritivas. Elas evitam igualmente a expansão de variáveis. Aspas evitam a expansão de wildcard mas permita a expansão de variáveis:

#!/bin/sh
echo $SHELL
echo "$SHELL"
echo '$SHELL'

Isto ira escrever:

/bin/bash
/bin/bash
$SHELL

E por fim exista a possibilidade de aceitar significado especial de qualquer caracter fazendo preceder da backslash:

echo *.jpg
echo $SHELL

Isto ira escrever:

 *.jpg $SHELL

Aqui documentos
Aqui documentos é uma maneira engraçada de enviar varias linhas de texto para um comando. É útil para escrever um texto de help num script sem ter a necessidade de colocar echo a frente de cada linha. Um aqui documento começa com << seguindo de qualquer string que pode também aparecer no fim de um aqui documento. Aqui esta um script de exemplo, chamado ren, que renomeia multiplos ficheiros e utiliza um aqui documento para o texto de ajuda:

#!/bin/sh
# Temos menos de 3 argumentos. Escreve o texto do help:
if [ $# -lt 3 ] ; then
  cat <<HELP
ren -- renomeia um numero de ficheiros utilizando expressões do sed

UTILIZAÇÃO: ren 'regexp' 'replacement' files...

EXEMPLO: renomeia todos os ficheiros *.HTM para *.html: ren 'HTM$' 'html' *.HTM

HELP exit 0 fi OLD="$1" NEW="$2"
# O comando shift remove um argumento da lista dos argumentos da linha de
# comandos.
shift shift
# $* agora tem todos os ficheiros:
for file in $*; do
   if [ -f "$file" ] ; then
      newfile=`echo "$file" | sed "s/${OLD}/${NEW}/g"`
      if [ -f "$newfile" ]; then
         echo "ERROR: $newfile já existe"
      else
         echo "renomeando $file para $newfile ..."
         mv "$file" "$newfile"
      fi
   fi
done

É o script mais complexo que vimos até aqui. Vamos falar um pouco sobre ele. A primeira instrução if testa se temos ao menos 3 parâmetros na linha de comandos. (A variável especial $# contém o numero de argumentos). Se não for o caso, o texto do help é enviado ao comando cat que por sua vez mande-o para o ecrã. Depois de escrever o texto do help saímos do programa. Se tivermos 3 ou mais argumentos vamos atribuir o primeiro argumento a variável OLD e o segundo a variável NEW. A seguir deslocamos os parâmetros da linha de comandos 2 vezes para colocar o terceiro argumento na primeira posição de $*. Com $* entramos no ciclo for. Cada um dos argumentos em $* é atribuído um a um a variável $file. Aí vamos em primeiro testar se o ficheiro existe realmente e vamos construir um novo nome de ficheiro utilizando find e sed. Os backticks são utilizados para atribuir o resultado para a variável newfile. Agora temos tudo o precisamos: O antigo e novo nome do ficheiro. É o que utilizamos com o comando mv para renomear os ficheiros.

Funções
Logo que você tenha um programa mais complexo você ira achar pratico utilizar o mesmo código em vários sítios e dar-lhe alguma estrutura. Uma função tem um aspecto semelhante a este:

nomedafuncao()
{
 # Dentro do corpo $1 esta o primeiro argumento dado a função No $2 esta o
 # segundo ...
 corpo
}

Tem de “declarar” as funções no inicio do script antes de poder utiliza-las.

Aqui esta um script chamado xtitlebar com o qual você pode mudar o nome de uma janela de um terminal. Se você tiver varias janelas abertas é uma maneira fácil de as identificar. O script envia uma sequência de escape que é interpretada pelo terminal e que provoca uma mudança de nome da barra da janela. O script utiliza a função chamada help. Como pode ver a função é definida uma vez e utilizada 2 vezes:

#!/bin/sh
# vim: set sw=4 ts=4 et:

help()
{
    cat <<HELP
xtitlebar -- muda o nome de um xterm, gnome-terminal ou kde konsole

UTILIZAÇÃO: xtitlebar [-h] "texto_para_a_janela"

OPÇÕES: -h help text

EXEMPLO: xtitlebar "cvs"

HELP
    exit 0
}

# Se ocorrer algum erro ou se for introduzido -h chamamos a função help:
[ -z "$1" ] && help
[ "$1" = "-h" ] && help

# Envia a sequência de escape que muda o titulo da barra xtem:
echo -e "33]0;$107"
#

É um bom habito de ter sempre uma ajuda extensiva dentro dos scripts. Isto torna possível para os outros (e você) utilizar e perceber o script.

Argumentos da linha de comandos
Vimos que S* e $1, $2 … $9 contêm os argumentos que o utilizador pode especificar na linha de comandos (as strings escritas a seguir o nome do programa). Até aqui tivemos uma syntax de linha de comandos bastante simples ou com poucos argumentos (alguns argumentos obrigatórios e a opção -h para ajuda). Mas rapidamente você ira descobrir que necessita de um tipo de analisador para programas mais complexos onde você define as suas próprias opções. Por convenção todos esses parâmetros opcionais são precedidos por um sinal – e têm de ser introduzidos antes qualquer outro argumento (como por exemplo os nome dos ficheiros).

Existem varias possibilidades para implementar um analisador. O seguinte ciclo while combinado com uma instrução case é uma solução muita boa para um analizador genérico:

#!/bin/sh
help()
{
  cat <<HELP
Isto é uma demo de um analisador de comando.
EXEMPLO DE UTILIZAÇÃO: cmdparser -l hello -f -- -ficheiro1 ficheiro2
HELP
  exit 0
}

while [ -n "$1" ]; do
case $1 in
    -h) help;shift 1;; # a função help é chamada
    -f) opt_f=1;shift 1;; # a variável opt_f é inicializada
    -l) opt_l=$2;shift 2;; # -l pega num argumento -> shift por 2
    --) shift;break;; # fim das opções
    -*) echo "erro: não exista a opção $1. -h para help";exit 1;;
    *)  break;;
esac
done

echo "opt_f é $opt_f"
echo "opt_l é $opt_l"
echo "primeiro argumento é $1"
echo "2nd argumento é $2"

Experimenta ! POde excuta-lo com por exemplo:

cmdparser -l hello -f -- -umficheiro1 umficheiro2

Ele ira produzir

opt_f é 1
opt_l é hello
primeiro argumento é -umficheiro1
2nd argumento é umficheiro2

Como é que ele funciona ? Basicamente ele faz um loop pelo todos os argumentos e verifica se coincidam com a instrução case. Se ele encontrar algum que coincida ele carrega a variável e shift a linha de comando por um. A convenção unix é que as opções (coisas começando com o menos) têm de vir em primeiro. Você deve de indicar no fim da opção escrevendo dois sinais menos (–). Você precisa, com grep por exemplo, pesquisar uma string que começa com o sinal menos:

Procura de -xx- no ficheiro f.txt:
grep -- -xx- f.txt

O nosso option parser pode manipular os — também como pode ver na listagem acima.

Exemplos

Um esqueleto para uma utilização genérica

Agora já falamos sobre praticamente todos os componentes que necessita para escrever um script. Todos os bons scripts têm de ter uma ajuda e pode também ter uma opção por defeito mesmo tendo uma unica opção. Por isso é uma boa ideia ter um script modelo, chamado framework.sh, o qual você pode utilizar como estrutura para outros scripts. Se deseja escrever um novo script basta fazer uma copia:

cp framework.sh myscript

e inserir a actual funcionalidade dentro de “myscript”.

Vamos agora ver mais dois exemplos:

Conversão de um numero binário para decimal

O script b2d converte um numero binário (ex: 1101) para o seu equivalente em decimal. É um exemplo que mostra que pode fazer operações matemáticas simples com expr:

#!/bin/sh
# vim: set sw=4 ts=4 et:
help()
{
 cat <<HELP b2h
-- converte binário para decimal

UTILIZAÇÃO: b2h [-h] binarynum

OPÇÕES: -h help text

EXEMPLO: b2h 111010 retorna 58 HELP exit 0 }

error()
{
    # escreve um erro e sai
    echo "$1" exit 1
}

lastchar()
{
    # devolve o ultimo caracter de uma string no $rval
    if [ -z "$1" ]; then
        # string vazia
        rval="" return fi
    # wc coloca algum espaço atras o output é por caso disso que necessitamos
    # de sed:
    numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `
    # agora corta o ultimo caractero
    rval=`echo -n "$1" | cut -b $numofchar`
}

chop()
{
    # remove o ultimo caracter numa string and retorno dentro de $rval
    if [ -z "$1" ]; then
        # string vazia
        rval="" return fi
    # wc coloca algum espaço atras o output é por caso disso que necessitamos
    # de sed:
    numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `
    if [ "$numofchar" = "1" ]; then
        # unicamente um caracter na string
        rval=""
        return
    fi
    numofcharminus1=`expr $numofchar "-" 1`
    # agora corta tudo mas o ultimo caractero:
    rval=`echo -n "$1" | cut -b 0-${numofcharminus1}`
}


while [ -n "$1" ]; do
case $1 in
    -h) help;shift 1;; # função help é chamada
    --) shift;break;; # fim das opções
    -*) error "erro: no such option $1. -h for help";;
     *)  break;;
esac
done

# O programa principal
sum=0
weight=1
# Têm de ser dado um argumento:
[ -z "$1" ] && help
binnum="$1"
binnumorig="$1"

while [ -n "$binnum" ]; do
   lastchar "$binnum"
   if [ "$rval" = "1" ]; then
       sum=`expr "$weight" "+" "$sum"`
   fi
   # remove a ultima posição no $binnum
   chop "$binnum"
   binnum="$rval"
   weight=`expr "$weight" "*" 2`
done

echo "binário $binnumorig é decimal $sum"
#

O algoritmo utilizado neste script pega no peso decimal (1,2,4,8,16,..) de cada numero começando com o numero mais a direita e adiciona-o a soma se o número é um 1. Desta maneira “10” é:
0 * 1 + 1 * 2 = 2
Para obter os números da string utilizamos a função lastchar. Esta ultima utiliza wc -c para contar o numero de caracteres da string e vai cortando o último caracter. A função chop têm a mesma lógica mas remove o último caracter, isto é que ele remove tudo desde o inicio até o penúltimo caracter.

Um programa de rotação de ficheiros
Provavelmente você é um desses que grava todo o mail que sai para um ficheiro. Depois de algum meses esse ficheiro se torna bastante grande e torna o acesso lente se você o lê dentro do seu programa de mail. O script seguinte rotatefile pode ajudar. Ele renomeia o mailfolder, vamos chama-lo outmail, para outmail.1 se já existir um então ele torna-se outmail.2 etc…

#!/bin/sh
# vim: set sw=4 ts=4 et:
ver="0.1"
help()
{
    cat <<HELP
rotatefile -- roda de ficheiro

UTILIZAÇÃO: rotatefile [-h] ficheiro

OPÇÕES: -h help text

EXEMPLO: rotatefile out Isto ira renomeiar por exemplo out.2 para out.3, out.1
para out.2, out para out.1 e criar um ficheiro out vazio

O numero máximo é 10

version $ver
HELP
   exit 0
}

error()
{
    echo "$1"
    exit 1
}
while [ -n "$1" ]; do
case $1 in
    -h) help;shift 1;;
    --) break;;
    -*) echo "erro: no such option $1. -h for help";exit 1;;
     *) break;;
esac
done

# verificação de input:
if [ -z "$1" ] ; then
 error "ERRO: têm de especificar um ficheiro, utiliza -h para ajuda"
fi
filen="$1"

# renomeia qualquer ficheiro .1 , .2 etc:
for n in  9 8 7 6 5 4 3 2 1; do
   if [ -f "$filen.$n" ]; then
      p=`expr $n + 1`
      echo "mv $filen.$n $filen.$p"
      mv $filen.$n $filen.$p
   fi
done

# renomeia o ficheiro original:
if [ -f "$filen" ]; then
   echo "mv $filen $filen.1"
   mv $filen $filen.1
fi
echo touch $filen
touch $filen

Como funciona o programa ? Após verificação que o utilizador introduziu um ficheiro vamos para um ciclo que vai de 9 a 1. O ficheiro 9 é agora renomeado para 10, ficheiro 8 para 9 e assim por diante. Depois do ciclo renomeamos o ficheiro original para 1 e criamos um ficheiro vazio com o nome do ficheiro original.

Debugging

A ajuda mais simples para debugging é obviamente o comando echo. Você pode utiliza-lo para ver o conteúdo de uma variável especifica perto do sitio onde você suspeita estar o erro. É provavelmente o que os programadores em shell fazem 80% das vezes para procurar um erro. A avantajem do shell scripting é que não é necessário recompilar e inserir um comando “echo” é feito muito rapidamente.

O shell tem também um verdadeiro modo debug. Se existir um erro no seu script “strangescript” então pode fazer debug desta forma:

sh -x strangescript

Isto ira executar o script e mostrar todos as instruções que são executadas com as variáveis e wildcards já expandido.

O shell têm também um modo para verificar a sintaxe sem realmente executar o programa. Para utilizar fazer:

sh -n o_script

Se não retornar nada então o seu programa esta sem erros de sintaxe.

Esperamos que agora ira começar a escrever os seus próprios scripts. Divirta-se !

Referências

  • A pagina man e pagina info do pash são muita boas e ira descobrir mais truques aqui:
    man bash
    tkinfo bash
    (Eu detesto o browser standard mas tkinfo ou konqueror (o url é info:/bash/Top ) são bastante bons)
  • O artigo na LinuxFocus em GNU file utilities
  • O artigo na LinuxFocus em Regular expressions
  • O artigo na LinuxFocus em AWK
  • O artigo na LinuxFocus em Basic UNIX commands
  • Se bash ainda não esta instalado no seu sistema ou quer obter a ultima versão você pode fazer download do site GNU em http://www.gnu.org (para descobrir se o bash já esta instalado no seu sistema basta escrever bash. Se não tiver nenhuma mensagem de erro então o bash já esta instalado)

Anúncios

Escrito por zrhans

Professor at UFSM

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s