Dicas de boas práticas de programação shell

Mo Budlong, Unix Insider, 01/09/1999

O que o #! realmente faz

Certa vez, há muito tempo atrás, o Unix possuia apenas um shell, o Bourne shell, e quando um script era escrito, o shell lia o script e executava os comandos. Então outro shell apareceu, e depois, outro. Cada shell possuia sua própria sintaxe e alguns, como o C shell, eram bastante diferentes do original. Isto significava que, se um script usava os recursos de um shell ou de outro, ele deveria ser executado usando aquele shell. Em vez de escrever:

doit

O usuáiro tinha que escrever:

/bin/ksh doit

ou:

/bin/csh doit

Para remediar esta situação, uma alteração inteligente foi feita no kernel Unix -- agora um script poderia ser escrito começando com uma combinação hash-bang (#!) na primeira linha, seguida pelo shell que executaria o script. Por exemplo, dê uma olhada no seguinte script, chamado doit:

#!/bin/ksh
#
# do some script here
#

Neste exemplo, o kernel lê o script doit, vê o hash-bang, e continua a ler o resto da linha, onde ele encontra /bin/ksh. O kernel então inicia o Korn shell com doit como um dos argumentos, e passa a ele o script, como se o seguinte comando tivesse sido escrito:

/bin/ksh doit

Quandi o /bin/ksh começa a ler o script, ele vê o hash-bang na primeira linha como um comentário (por que ele começa com um hash), e ignora o mesmo. Para ser executado, o path completo do shel é necessário, já que o kernel não irá examinar sua variável PATH. O manipulador de hash-bang do kernel faz mais que só executar um shell alternativo, ele realmenet pega os argumentos que seguem o hash-bang e usa os mesmos como um comando, e acrescente o nome do arquivo como argumento àquele comando.

Você pode iniciar um script Perl, chamado doperl, usando o hash-bang:

#!/bin/perl

# do some perl script here

Se você escrever doperl, o kernel examina o hash-bang, extrai o comando /bin/perl, e executa o mesmo como se você tivesse escrito:

/bin/perl doperl

Existem dois mecanismos em funcionamento que permitem que isto funcione. O primeiro é a interpretação do kernel do hash-bang, o segundo é que o Perl vê a primeira linha como um comentário e ignora a mesma. Esta técnica não irá funcionar com linguagens de script que não tratam linhas que começam com um hash como comentário -- nestes casos, provavelmente você terá um erro. Você não precisa se limitar a usar este método apenas para executar scripts, apesar que é onde ele é mais útil.

O seguitne script, chamado helpme, apresenta a si mesmo no terminal quando você executa o comando helpme:

#!/bin/cat
vi	unix editor
man	manual pages
sh	Bourne Shell
ksh	Korn Shell
csh	C Shell
bash	Bourne Again Shell

Este truque do kernel irá executar um argumento após o nome do comando. Para esconder a primeria linha, faça que o arquivo use o more, iniciando na segunda linha, mas certifique-se de usar o path correto:

#!/bin/more +2
vi      unix editor
man     manual pages
sh      Bourne Shell
ksh     Korn Shell
csh     C Shell
bash    Bourne Again Shell

Ao escrever o helpme como um comando, o kernel converte isto para:

/bin/more +2 helpme

Tudo a partir da linha dois em diante é mostrado:

helpme
vi      unix editor
man     manual pages
sh      Bourne Shell
ksh     Korn Shell
csh     C Shell
bash    Bourne Again Shell
etc.

Você pode usar esta técnica para criar scripts aparentemente inúteis, como um arquivo que remove a si mesmo:

#!/bin/rm

Se você der o nome de flagged a este script, a execução do mesmo irá fazer com que o comando seja executado como se você tivesse escrito:

/bin/rm flagged

Você pode usar isto em um script para indicar que você está executando alguma coisa, então execute o script para remover o mesmo:

#!/bin/ksh
# first refuse to run if the flagged file exists

if [ -f flagged ]
then
	exit
fi

# create de thalg file

echo #! /bin/rm" > flagged
chmod a+x flagged

# do some logic here

# unflag the process by executing the flag file
flagged

Antes que você comece a executar comandos longos com esta técnica, tenha em mente que os sistemas geralmente tem um limite superior (tipicamente 32 caracteres) no comprimento do código na linha #!.

Testando argumentos de linha de comando e modo de usar

Quando você escreve um shell script, os argumentos são normalmente necessários para que o script funcione corretamente. Para garantir que os argumentos façam algum sentido, é geralmente necessário validar os mesmos.

Testar para ver se há argumentos suficientes é geralmente o método mais fácil de validação. por exemplo, se você criou um script shell que pede dois nomes de arquivos para operar, teste a existência de dois argumentos na linha de comando. para fazer isto no Bourne e no Korn shell, verifique o valor de $# -- uma variável que contém a contagem de argumentos, além do próprio comando. É também uma boa pratica incluir uma mensagem que detalhe as razões por que o comando falhou. Isto é criado em uma função usage.

O script twofiles abaixo, testa se há dois argumentos na linha de comando:

#!/bin/ksh

# twofile script handles two files named on the command line

# a usage function to display help for the hapless user

usage ()
{
	echo "twofiles"
	echo 	"usage: twofiles file1 file2"
	echo	"Processes two files"
}

# test if we have two arguments on the command line
if [ $# != 2 ]
then
	usage
	exit
fi

Uma prática mais segura é validar o máximo que você puder antes de executar alguma coisa. A seguinte versão de twofiles verifica a contagem de argumentos e testa os dois arquivos. Se o arquivo 1 não existe ( if [ ! -f $1 ]) uma mensagem de erro é configurada, uma mensagem de uso é apresentada, e o programa termina. O mesmo é feito para o arquivo 2:

#!/bin/ksh

# twofiles script handles two files named on the command line

# a usage function to display help for the hapless user
# plus an additional error message if it has been filled in

usage ()
{
	echo "twofiles"
	echo 	"usage: twofiles file1 file2"
	echo 	"Processes two files"
	echo " "
	echo $errmsg
}

# test if we have two arguments on the command line
if [ $# != 2 ]
then
	usage
	exit
fi

# test if file one exists and send an additional error message
# to usage if not found

if [ ! -f $1 ]
then
	errmsg=${1}": File Not Found"
	usage
	exit
fi

# same for file two
if [ ! -f $2 ]
then
        errmsg=${2}": File Not Found"
	usage
	exit
fi

# we are ok at this point so continue processing here

Note que no Korn shell você pode usar a sintaxe de teste de colchetes duplos, que é mais rápida. O colchete simples na verdade chama um programa test para testar os valores, enquanto que com o colchete duplo o teste é implementado no Korn shell e não precisa chamar um programa separado.

O teste de colchete duplo não funciona no Bourne shell:

if [[ $# != 2 ]]

ou

if [[ ! -f $1 ]]

ou

if [[ ! -f $2 ]]

Esta validação pode evitar erros posteriores na lógica do programa quando subitamente percebe-se que um arquivo está faltando. Considere isto como uma boa prática de programação.


Mo Budlong, o presidente da King Computer Services, especializado em Web Linux/Unix e desenvolvimento cliente/servidor, e atualmente está trabalhando na conversão de aplicações do Windows para o Linux. Ele publicou livros e artigos em assuntos que variam da linguagem Assembly a World Wide Web, e é o autor do best-seller Teach Yourself COBOL in 21 Days, agora em sua terceira edição. Ele está trabalhando atualmente em um livro sobre o básico do Unix.

Links para este artigo