Sei sulla pagina 1di 55

LINUX USER Papo de Botequim

Curso de Shell Script

Papo de Botequim
Você não agüenta mais aquele seu porque, em inglês, Shell significa con-
cha, carapaça, isto é, fica entre o u-
amigo usuário de Linux enchendo o suário e o sistema operacional, de
forma que tudo que interage com
seu saco com aquela história de que o o sistema operacional, tem que
passar pelo seu crivo.
sistema é fantástico e o Shell é uma

ferramenta maravilhosa? A partir


O ambiente Shell
Bom já que para chegar ao
desta edição vai ficar mais fácil en- núcleo do Linux, no seu ker-
nel que é o que interessa a
tender o porquê deste entusiasmo... todo aplicativo, é necessária a
filtragem do Shell, vamos enten-
POR JULIO CEZAR NEVES der como ele funciona de forma a
tirar o máximo proveito das inú-
meras facilidades que ele nos oferece.

D
iálogo entreouvido em uma mesa O Linux, por definição, é um sistema
de um botequim, entre um multiusuário – não podemos nunca nos
usuário de Linux e um empur- esquecer disto – e para permitir o acesso
rador de mouse: O ambiente Linux de determinados usuários e barrar a en-
• Quem é o Bash? Para você entender o que é e como fun- trada de outros, existe um arquivo cha-
• É o filho caçula da família Shell. ciona o Shell, primeiro vou te mostrar mado /etc/passwd, que além de fornecer
• Pô cara! Estás a fim de me deixar como funciona o ambiente em camadas dados para esta função de “leão-de-chá-
maluco? Eu tinha uma dúvida e você do Linux. Dê uma olhada no gráfico cara” do Linux, também provê informa-
me deixa com duas! mostrado na Figura 1. ções para o início de uma sessão (ou
• Não, maluco você já é há muito tem- Neste gráfico podemos ver que a ca- “login”, para os íntimos) daqueles que
po: desde que decidiu usar aquele sis- mada de hardware é a mais profunda e é passaram por esta primeira barreira. O
tema operacional que você precisa formada pelos componentes físicos do último campo de seus registros informa
reiniciar dez vezes por dia e ainda por seu computador. Em torno dela, vem a ao sistema qual é o Shell que a pessoa
cima não tem domínio nenhum sobre camada do kernel que é o cerne do vai receber ao iniciar sua sessão.
o que esta acontecendo no seu com- Linux, seu núcleo, e é quem põe o hard- Lembra que eu te falei de Shell, fa-
putador. Mas deixa isso prá lá, pois ware para funcionar, fazendo seu geren- mília, irmão? Pois é, vamos começar a
vou te explicar o que é Shell e os com- ciamento e controle. Os programas e entender isto: o Shell é a conceituação
ponentes de sua família e ao final da comandos que envolvem o kernel, dele de concha envolvendo o sistema opera-
nossa conversa você dirá: “Meu Deus se utilizam para realizar as tarefas para cional propriamente dito, é o nome
do Shell! Porque eu não optei pelo que foram desenvolvidos. Fechando tudo genérico para tratar os filhos desta idéia
Linux antes?”. isso vem o Shell, que leva este nome que, ao longo dos muitos anos de exis-

Quadro 1: Uma rapidinha nos principais sabores de Shell


Bourne Shell (sh): Desenvolvido por Stephen do sh e a elas agregou muitas outras. A com- C Shell (csh): Desenvolvido por Bill Joy, da
Bourne do Bell Labs (da AT&T, onde também patibilidade total com o sh vem trazendo Universidade de Berkley, é o Shell mais uti-
foi desenvolvido o Unix), foi durante muitos muitos usuários e programadores de Shell lizado em ambientes BSD. Foi ele quem intro-
anos o Shell padrão do sistema operacional para este ambiente. duziu o histórico de comandos. A
Unix. É também chamado de Standard Shell Boune Again Shell (bash): Desenvolvido ini- estruturação de seus comandos é bem simi-
por ter sido durante vários anos o único, e é cialmente por Brian Fox e Chet Ramey, este é lar à da linguagem C. Seu grande pecado foi
até hoje o mais utilizado. Foi portado para o Shell do projeto GNU. O número de seus ignorar a compatibilidade com o sh, partindo
praticamente todos os ambientes Unix e dis- adeptos é o que mais cresce em todo o por um caminho próprio.
tribuições Linux. mundo, seja por que ele é o Shell padrão do Além destes Shells existem outros, mas irei
Korn Shell (ksh): Desenvolvido por David Linux, seja por sua grande diversidade de falar somente sobre os três primeiros, tratan-
Korn, também do Bell Labs, é um supercon- comandos, que incorpora inclusive diversos do-os genericamente por Shell e assinalando
junto do sh, isto é, possui todas as facilidades comandos característicos do C Shell. as especificidades de cada um.

82 Agosto 2004 www.linuxmagazine.com.br


Papo de Botequim LINUX USER

tência do sistema operacional Unix, redirecionamento, que pode ser de


foram aparecendo. Atualmente existem
Com que Shell eu vou? entrada (stdin), de saída (stdout) ou dos
Quando digo que o último campo do arqui-
diversos sabores de Shell (veja Quadro 1 erros (stderr), conforme vou explicar a
vo /etc/passwd informa ao sistema qual é o
na página anterior). seguir. Mas antes precisamos falar de...
Shell que o usuário vai usar ao se “logar”, isto
deve ser interpretado ao pé-da-letra. Se este
Como funciona o Shell campo do seu registro contém o termo prog, Substituição de Variáveis
O Shell é o primeiro programa que você ao acessar o sistema o usuário executará o Neste ponto, o Shell verifica se as even-
ganha ao iniciar sua sessão (se quiser- programa prog. Ao término da execução, a tuais variáveis (parâmetros começados
mos assassinar a língua portuguesa sessão do usuário se encerra automatica- por $), encontradas no escopo do
mente. Imagine quanto se pode incremen-
podemos também dizer “ao se logar”) no comando, estão definidas e as substitui
tar a segurança com este simples artifício.
Linux. É ele quem vai resolver um monte por seus valores atuais.
de coisas de forma a não onerar o kernel
com tarefas repetitivas, poupando-o para volvidos (inclusive o próprio programa), Substituição de Meta-
tratar assuntos mais nobres. Como cada e retorna um erro caso o usuário que Caracteres
usuário possui o seu próprio Shell inter- chamou o programa não esteja autor- Se algum meta-caracter (ou “coringa”,
pondo-se entre ele e o Linux, é o Shell izado a executar esta tarefa. como *, ? ou []) for encontrado na linha
quem interpreta os comandos digitados e de comando, ele será substituído por
examina as suas sintaxes, passando-os $ ls linux seus possíveis valores.
esmiuçados para execução. linux Supondo que o único item no seu
• Êpa! Esse negócio de interpretar co- diretório corrente cujo nome começa
mando não tem nada a ver com inter- Neste exemplo o Shell identificou o ls co- com a letra n seja um diretório chamado
pretador não, né? mo um programa e o linux como um pa- nomegrandeprachuchu, se você fizer:
• Tem sim: na verdade o Shell é um in- râmetro passado para o programa ls.
terpretador que traz consigo uma po- $ cd n*
derosa linguagem com comandos de Atribuição
alto nível, que permite construção de Se o Shell encontra dois campos separa- como até aqui quem está manipulando a
loops, de tomadas de decisão e de ar- dos por um sinal de igual (=) sem espa- linha de comando ainda é o Shell e o
mazenamento de valores em variáveis, ços em branco entre eles, ele identifica programa cd ainda não foi executado, o
como vou te mostrar. esta seqüência como uma atribuição. Shell expande o n* para nomegrandepra-
• Vou explicar as principais tarefas que o chuchu (a única possibilidade válida) e
Shell cumpre, na sua ordem de exe- $ valor=1000 executa o comando cd com sucesso.
cução. Preste atenção, porque esta
ordem é fundamental para o entendi- Neste caso, por não haver espaços em Entrega da linha de comando
mento do resto do nosso bate papo. branco (que é um dos caracteres reserva- para o kernel
dos), o Shell identificou uma atribuição e Completadas todas as tarefas anteriores,
Análise da linha de comando colocou 1000 na variável valor. o Shell monta a linha de comando, já
Neste exame o Shell identifica os carac- com todas as substituições feitas e
teres especiais (reservados) que têm sig- Resolução de chama o kernel para executá-la em um
nificado para a interpretação da linha e Redirecionamentos novo Shell (Shell filho), que ganha um
logo em seguida verifica se a linha pas- Após identificar os componentes da li- número de processo (PID ou Process
sada é um comando ou uma atribuição nha que você digitou, o Shell parte para IDentification) e fica inativo, tirando
de valores, que são os ítens que vou a resolução de redirecionamentos. uma soneca durante a execução do pro-
descrever a seguir. O Shell tem incorporado ao seu elenco grama. Uma vez encerrado este processo
de habilidades o que chamamos de (e o Shell filho), o “Shell pai” recebe
Comando novamente o controle e exibe um
Quando um comando é digi- “prompt”, mostrando que está pronto
tado no “prompt” (ou linha de Shell para executar outros comandos.
comando) do Linux, ele é divi-
dido em partes, separadas por
Programas e Comandos Cuidado na Atribuição
espaços em branco: a primeira Núcleo ou Kernel Jamais faça:
parte é o nome do programa,
cuja existência será verificada; Hardware $ valor = 1000
bash: valor: not found
em seguida, nesta ordem, vêm
Neste caso, o Bash achou a palavra valor iso-
as opções/parâmetros, redire-
lada por espaços e julgou que você estivesse
cionamentos e variáveis.
mandando executar um programa chama-
Quando o programa identifi-
do valor, para o qual estaria passando dois
cado existe, o Shell verifica as parâmetros: = e 1000.
permissões dos arquivos en- Figura 1: Ambiente em camadas de um sistema Linux

www.linuxmagazine.com.br Agosto 2004 83


LINUX USER Papo de Botequim

Decifrando a Pedra de Roseta $ echo \* esperando pelo teclado (Entrada Padrão)


Para tirar aquela sensação que você tem $ echo * e como também não citei a saída, o que
quando vê um script Shell, que mais eu teclar irá para a tela (Saída Padrão),
parece uma sopa de letrinhas ou um con- Viu a diferença? criando desta forma – como eu havia
junto de hieróglifos, vou lhe mostrar os • Aspas (“): exatamente iguais ao após- proposto – um programa gago. Experi-
principais caracteres especiais para que trofo, exceto que, se a cadeia entre mente!
você saia por aí como Champollion deci- aspas contiver um cifrão ($), uma
frando a Pedra de Roseta. crase (`), ou uma barra invertida (\), Redirecionamentop da Saída
estes caracteres serão interpretados Padrão
Caracteres para remoção do pelo Shell. Para especificarmos a saída de um pro-
significado. Não precisa se estressar, eu não te dei grama usamos o símbolo “>” ou o
É isso mesmo, quando não desejamos exemplos do uso das aspas por que “>>”, seguido do nome do arquivo pa-
que o Shell interprete um caractere você ainda não conhece o cifrão ($) ra o qual se deseja mandar a saída.
específico, devemos “escondê-lo” dele. nem a crase (`). Daqui para frente - Vamos transformar o programa ante-
Isso pode ser feito de três maneiras difer- veremos com muita constância o uso rior em um “editor de textos”:
entes, cada uma com sua peculiaridade: destes caracteres especiais; o mais
• Apóstrofo (´): quando o Shell vê uma importante é entender seu significado. $ cat > Arq
cadeia de caracteres entre apóstrofos,
ele retira os apóstrofos da cadeia e não Caracteres de O cat continua sem ter a entrada especi-
interpreta seu conteúdo. redirecionamento ficada, portanto está aguardando que os
A maioria dos comandos tem uma entra- dados sejam teclados, porém a sua saída
$ ls linuxm* da, uma saída e pode gerar erros. Esta está sendo desviada para o arquivo Arq.
linuxmagazine entrada é chamada Entrada Padrão ou Assim sendo, tudo que esta sendo tecla-
$ ls 'linuxm*' stdin e seu dispositivo padrão é o teclado do esta indo para dentro de Arq, de for-
bash: linuxm* no such file U do terminal. Analogamente, a saída do ma que fizemos o editor de textos mais
or directory comando é chamada Saída Padrão ou curto e ruim do planeta.
stdout e seu dispositivo padrão é a tela Se eu fizer novamente:
No primeiro caso o Shell “expandiu” o do terminal. Para a tela também são
asterisco e descobriu o arquivo linux- enviadas normalmente as mensagens de $ cat > Arq
magazine para listar. No segundo, os erro oriundas dos comandos, chamada
apóstrofos inibiram a interpretação do neste caso de Saída de Erro Padrão ou Os dados contidos em Arq serão perdi-
Shell e veio a resposta que não existe o stderr. Veremos agora como alterar este dos, já que antes do redirecionamento o
arquivo linuxm*. estado de coisas. Shell criará um Arq vazio. Para colocar
• Contrabarra ou Barra Invertida (\): i- Vamos fazer um programa gago. Para mais informações no final do arquivo eu
dêntico aos apóstrofos exceto que a isto digite (tecle “Enter” ao final de cada deveria ter feito:
barra invertida inibe a interpretação linha – comandos do usuário são ilus-
somente do caractere que a segue. trados em negrito): $ cat >> Arq
Suponha que você, acidentalmente,
tenha criado um arquivo chamado * $ cat Redirecionamento da Saída
(asterisco) – o que alguns sabores de E-e-eu sou gago. Vai encarar? de Erro Padrão
Unix permitem – e deseja removê-lo. E-e-eu sou gago. Vai encarar? Assim como por padrão o Shell recebe os
Se você fizesse: dados do teclado e envia a saída para a
O cat é um comando que lista o con- tela, os erros também vão para a tela se
$ rm * teúdo do arquivo especificado para a você não especificar para onde eles de-
Saída Padrão (stdout). Caso a entrada vem ser enviados. Para redirecionar os
Você estaria na maior encrenca, pois o não seja definida, ele espera os dados da erros, use 2> SaidaDeErro. Note que en-
rm removeria todos os arquivos do stdin (a entrada padrão). Ora como eu tre o número 2 e o sinal de maior (>)
diretório corrente. A melhor forma de não especifiquei a entrada, ele a está não existe espaço em branco.
fazer o serviço é: Vamos supor que durante a execução
Redirecionamento Perigoso de um script você pode, ou não (depen-
$ rm \* Como já havia dito, o Shell resolve a linha e dendo do rumo tomado pela execução
depois manda o comando para a execução. do programa), ter criado um arquivo
Desta forma, o Shell não interpreta o Assim, se você redirecionar a saída de um chamado /tmp/seraqueexiste$$. Como
asterisco, evitando a sua expansão. arquivo para ele próprio, primeiramente o não quer ficar com sujeira no disco
Faça a seguinte experiência científica: Shell “esvazia”este arquivo e depois manda rígido, ao final do script você coloca a
o comando para execução! Desta forma, linha a seguir:
para sua alegria, você acabou de perder o
$ cd /etc
conteúdo de seu querido arquivo.
$ echo '*' rm /tmp/seraqueexiste$$

84 Agosto 2004 www.linuxmagazine.com.br


Papo de Botequim LINUX USER

caprichamos, né? Então ao invés de sair


Dados ou Erros? redigindo o mail direto no “prompt”, de
Etiquetas Erradas
Preste atenção! Não confunda >> com 2>. O forma a tornar impossível a correção de Um erro comum no uso de labels (como o
primeiro anexa dados ao final de um arqui- fimftp do exemplo anterior) é causado pela
uma frase anterior onde, sem querer,
vo, e o segundo redireciona a Saída de Erro presença de espaços em branco antes ou
você escreveu um “nós vai”, você edita
Padrão (stderr) para um arquivo que está após o mesmo. Fique muito atento quanto a
um arquivo com o conteúdo da mensa-
sendo designado. Isto é importante! isso, por que este tipo de erro costuma dar
gem e após umas quinze verificações
uma boa surra no programador, até que seja
sem constatar nenhum erro, decide
detectado. Lembre-se: um label que se preze
Caso o arquivo não existisse seria envi- enviá-lo e para tal faz:
tem que ter uma linha inteira só para ele.
ado para a tela uma mensagem de erro.
Para que isso não aconteça faça: $ mail chefe@chefia.com.br < U
arquivocommailparaochefe nada a partir deste ponto até encontrar
rm /tmp/seraqueexiste$$ 2> U o ‘label’ fimftp. Você não entenderia
/dev/null e o chefe receberá uma mensagem com o droga nenhuma, já que são instruções
conteúdo do arquivocommailparaochefe. específicas do ftp”.
Para que você teste a Saída de Erro Pa- Outro tipo de redirecionamento “muito Se fosse só isso seria simples, mas
drão direto no prompt do seu Shell, vou louco” que o Shell permite é o chamado pelo próprio exemplo dá para ver que
dar mais um exemplo. Faça: “here document”. Ele é representado por existem duas variáveis ($Usuario e
<< e serve para indicar ao Shell que o $Senha), que o Shell vai resolver antes
$ ls naoexiste escopo de um comando começa na linha do redirecionamento. Mas a grande
bash: naoexiste no such file U seguinte e termina quando encontra uma vantagem deste tipo de construção é
or directory linha cujo conteúdo seja unicamente o que ela permite que comandos tam-
$ ls naoexiste 2> arquivodeerros “label” que segue o sinal <<. bém sejam interpretados dentro do
$ Veja o fragmento de script a seguir, escopo do “here document”, o que,
$ cat arquivodeerros com uma rotina de ftp: aliás, contraria o que acabei de dizer.
bash: naoexiste no such file U Logo a seguir te explico como esse
or directory ftp -ivn hostremoto << fimftp negócio funciona. Agora ainda não dá,
user $Usuario $Senha estão faltando ferramentas.
Neste exemplo, vimos que quando fize- binary • O comando user é do repertório de
mos um ls em naoexiste, ganhamos uma get arquivoremoto instruções do ftp e serve para passar o
mensagem de erro. Após redirecionar a fimftp usuário e a senha que haviam sido
Saída de Erro Padrão para arquivodeerros lidos em uma rotina anterior a este
e executar o mesmo comando, recebe- neste pedacinho de programa temos um fragmento de código e colocados res-
mos somente o “prompt” na tela. Quan- monte de detalhes interessantes: pectivamente nas duas variáveis:
do listamos o conteúdo do arquivo para • As opções usadas para o ftp (-ivn) $Usuario e $Senha.
o qual foi redirecionada a Saída de Erro servem para ele listar tudo que está • O binary é outra instrução do ftp, que
Padrão, vimos que a mensagem de erro acontecendo (opção -v de “verbose”), serve para indicar que a transferência
tinha sido armazenada nele. para não ficar perguntando se você de arquivoremoto será feita em modo
É interessante notar que estes carac- tem certeza que deseja transmitir cada binário, isto é o conteúdo do arquivo
teres de redirecionamento são cumula- arquivo (opção -i de “interactive”) e não será inteerpretado para saber se
tivos, isto é, se no exemplo anterior finalmente a opção -n serve para dizer está em ASCII, EBCDIC, …
fizéssemos o seguinte: ao ftp para ele não solicitar o usuário e • O comando get arquivoremoto diz ao
sua senha, pois estes serão informados cliente ftp para pegar este arquivo no
$ ls naoexiste 2>> U pela instrução específica (user); servidor hostremoto e trazê-lo para a
arquivodeerros • Quando eu usei o << fimftp, estava nossa máquina local. Se quiséssemos
dizendo o seguinte para o interpreta- enviar um arquivo, bastaria usar, por
a mensagem de erro oriunda do ls seria dor: “Olha aqui Shell, não se meta em exemplo, o comando put arquivolocal.
anexada ao final de arquivodeerros.
Direito de Posse Redirecionamento de
Redirecionamento da O $$ contém o PID,isto é,o número do seu comandos
Entrada Padrão processo. Como o Linux é multiusuário,é Os redirecionamentos de que falamos até
Para fazermos o redirecionamento da En- bom anexar sempre o $$ ao nome dos seus agora sempre se referiam a arquivos, isto
trada Padrão usamos o < (menor que). arquivos para não haver problema de propri- é, mandavam para arquivo, recebiam de
“E pra que serve isso?”, você vai me per- edade,isto é,caso você batizasse o seu ar- arquivo, simulavam arquivo local, … O
guntar. Deixa eu dar um exemplo, que quivo simplesmente como seraqueexiste,a que veremos a partir de agora, redirecio-
você vai entender rapidinho. primeira pessoa que o usasse (criando-o na a saída de um comando para a entra-
então) seria o seu dono e a segunda ganharia
Suponha que você queira mandar um da de outro. É utilíssimo e, apesar de não
um erro quando tentasse gravar algo nele.
mail para o seu chefe. Para o chefe nós ser macaco gordo, sempre quebra os

www.linuxmagazine.com.br Agosto 2004 85


LINUX USER Papo de Botequim

maiores galhos. Seu nome é “pipe” (que $ echo "Existem who | wc -l U $ (pwd ; cd /etc ; pwd)
em inglês significa tubo, já que ele cana- usuarios conectados" /home/meudir
liza a saída de um comando para a Existem who | wc -l usuarios U /etc
entrada de outro) e sua representação é a conectados $ pwd
| (barra vertical). /home/meudir
Hi! Olha só, não funcionou! É mesmo,
$ ls | wc -l não funcionou e não foi por causa das “Quequeiiisso” minha gente? Eu estava
21 aspas que eu coloquei, mas sim por que no /home/meudir, mudei para o /etc,
eu teria que ter executado o who | wc -l constatei que estava neste diretório com
O comando ls passou a lista de arquivos antes do echo. Para resolver este proble- o pwd seguinte e quando o agrupamento
para o comando wc, que quando está ma, tenho que priorizar a segunda parte de comandos terminou, eu vi que conti-
com a opção -l conta a quantidade de li- do comando com o uso de crases: nuava no /etc/meudir!
nhas que recebeu. Desta forma, pode- Hi! Será que tem coisa do mágico
mos afirmar categoricamente que no $ echo "Existem `who | wc -l` U Mandrake por aí? Nada disso. O interes-
meu diretório existiam 21 arquivos. usuarios conectados" sante do uso de parênteses é que eles
Existem 8 usuarios U invocam um novo Shell para executar os
$ cat /etc/passwd | sort | lp conectados comandos que estão em seu interior.
Desta forma, fomos realmente para o
A linha de comandos acima manda a Para eliminar esse monte de brancos diretório /etc, porém após a execução de
listagem do arquivo /etc/passwd para a antes do 8 que o wc -l produziu, basta todos os comandos, o novo Shell que
entrada do comando sort. Este a classi- retirar as aspas. Assim: estava no diretório /etc morreu e retor-
fica e envia para o lp que é o gerenciador namos ao Shell anterior que estava em
da fila de impressão. $ echo Existem `who | wc -l` U /home/meudir.
usuarios conectados Que tal usar nossos novos conceitos?
Caracteres de ambiente Existem 8 usuarios conectados
Quando queremos priorizar uma expres- $ mail suporte@linux.br << FIM
são, nós a colocamos entre parênteses, As aspas protegem da interpretação do Ola suporte, hoje as `date U
não é? Pois é, por causa da aritmética é Shell tudo que está dentro dos seus lim- “+%hh:mm”` ocorreu novamente U
normal pensarmos deste jeito. Mas em ites. Como para o Shell basta um espaço aquele problema que eu havia U
Shell o que prioriza mesmo são as crases em branco como separador, o monte de reportado por telefone. De U
(`) e não os parênteses. Vou dar exemp- espaços será trocado por um único após acordo com seu pedido segue a U
los para você entender melhor. a retirada das aspas. listagem do diretorio:
Eu quero saber quantos usuários estão Outra coisa interessante é o uso do `ls -l`
“logados” no computador que eu admi- ponto-e-vírgula. Quando estiver no Shell, Abracos a todos.
nistro. Eu posso fazer: você deve sempre dar um comando em FIM
cada linha. Para agrupar comandos em
$ who | wc -l uma mesma linha, temos que separá-los Finalmente agora podemos demonstrar o
8 por ponto-e-vírgula. Então: que conversamos anteriormente sobre
“here document”. Os comandos entre
O comando who passa a lista de usuários $ pwd ; cd /etc; pwd ;cd -;pwd crases tem prioridade, portanto o Shell
conectados ao sistema para o comando /home/meudir os executará antes do redirecionamento
wc -l, que conta quantas linhas recebeu e /etc do “here document”. Quando o suporte
mostra a resposta na tela. Muito bem, /home/meudir receber a mensagem, verá que os
mas ao invés de ter um número oito comandos date e ls foram executados
solto na tela, o que eu quero mesmo é Neste exemplo, listei o nome do diretório antes do comando mail, recebendo então
que ele esteja no meio de uma frase. Ora, corrente com o comando pwd, mudei um instantâneo do ambiente no
para mandar frases para a tela eu só pre- para o diretório /etc, novamente listei o momento de envio do email.
ciso usar o comando echo; então vamos nome do diretório e finalmente voltei pa- - Garçom, passa a régua! ■
ver como é que fica: ra o diretório onde estava anteriormente
(cd -), listando seu nome. Repare que Julio Cezar Neves é Analista de Su-
SOBRE O AUTOR

Buraco Negro coloquei o ponto-e-vírgula de todas as porte de Sistemas desde 1969 e tra-
Em Unix existe um arquivo fantasma. formas possíveis, para mostrar que não balha com Unix desde 1980, quando
Chama-se /dev/null.Tudo que é enviado importa se existem espaços em branco fez parte da equipe que desenvolveu
para este arquivo some. Assemelha-se a um antes ou após este caracter. o SOX, sistema operacional, similar
Buraco Negro. No caso do exemplo, como ao Unix, da Cobra Computadores. É
Finalmente, vamos ver o caso dos
não me interessava guardar a possível men- professor do curso de Mestrado em
parênteses. No exemplo a seguir, colo-
sagem de erro oriunda do comando rm, redi- Software Livre das Faculdades Estácio
camos diversos comandos separados por
recionei-a para este arquivo. de Sá, no Rio de Janeiro.
ponto-e-vírgula entre parênteses:

86 Agosto 2004 www.linuxmagazine.com.br


Papo de Botequim LINUX USER

Curso de Shell Script

Papo de Botequim - Parte II


Nossos personagens voltam à mesa do bar para discutir expressões regulares e linhas que usavam a palavra grep, em
todos os arquivos terminados em .sh.
colocar a “mão na massa” pela primeira vez, construindo um aplicativo simples Como uso essa extensão para definir
meus arquivos com programas em Shell,
para catalogar uma coleção de CDs. POR JÚLIO CÉSAR NEVES malandramente, o que fiz foi listar as lin-
has dos programas que poderia usar
como exemplo do comando grep.

G
arçom! Traz um “chops” e dois Olha que legal! O grep aceita como
“pastel”. O meu amigo hoje não entrada a saída de outro comando, redi-
vai beber porque está finalmente recionado por um pipe (isso é muito
sendo apresentado a um verdadeiro sis- comum em Shell e é um tremendo
tema operacional, e ainda tem muita acelerador da execução de coman-
coisa a aprender! dos). Dessa forma, no 3° exemplo,
– E então, amigo, tá entendendo o comando who listou as pessoas
tudo que te expliquei até agora? “logadas” na mesma máquina que
– Entendendo eu tô, mas não vi você (não se esqueça jamais: o
nada prático nisso… Linux é multiusuário) e o grep foi
– Calma rapaz, o que te falei até usado para verificar se o Carvalho
agora serve como base ao que há estava trabalhando ou “coçando”.
de vir daqui pra frente. Vamos usar O grep é um comando muito con-
essas ferramentas que vimos para hecido, pois é usado com muita fre-
montar programas estruturados. Você qüência. O que muitas pessoas não
verá porque até na TV já teve pro- sabem é que existem três comandos na
grama chamado “O Shell é o Limite”. família grep: grep, egrep e fgrep. A princi-
Para começar vamos falar dos coman- pais diferenças entre os 3 são:
dos da família grep • grep - Pode ou não usar expressões
– Grep? Não conheço nenhum termo em $ grep franklin /etc/passwd regulares simples, porém no caso de
inglês com este nome… não usá-las, o fgrep é melhor, por ser
– É claro, grep é um acrônimo (sigla) Pesquisando em vários arquivos: mais rápido.
para Global Regular Expression Print, • egrep (“e” de extended, estendido) - É
que usa expressões regulares para $ grep grep *.sh muito poderoso no uso de expressões
pesquisar a ocorrência de cadeias de regulares. Por ser o mais poderoso dos
caracteres na entrada definida. Pesquisando na saída de um comando: três, só deve ser usado quando for
Por falar em expressões regulares (ou necessária a elaboração de uma
regexp), o Aurélio Marinho Jargas es- $ who | grep carvalho expressão regular não aceita pelo grep.
creveu dois artigos [1 e 2] imperdíveis • fgrep (“f” de fast, rápido) - Como o
para a Revista do Linux sobre esse No 1º exemplo, procurei a palavra nome diz, é o ligeirinho da família,
assunto e também publicou um livro [3] franklin em qualquer lugar do arquivo executando o serviço de forma muito
pela Editora Novatec. Acho bom você ler /etc/passwd. Se quisesse procurar um veloz (por vezes é cerca de 30% mais
esses artigos, eles vão te ajudar no que nome de usuário, isto é, somente no iní- rápido que o grep e 50% mais que o
está para vir. cio dos registros desse arquivo, poderia egrep), porém não permite o uso de
digitar $ grep ‘^franklin’ /etc/passwd. expressões regulares na pesquisa.
Eu fico com grep,você com gripe “E para que servem o circunflexo e os –Agora que você já conhece as difer-
Esse negócio de gripe é brincadeira, só apóstrofos?”, você vai me perguntar. Se enças entre os membros da família, me
um pretexto para pedir umas caipirinhas. tivesse lido os artigos que mencionei, diga: o que você acha dos três exemplos
Eu te falei que o grep procura cadeias de saberia que o circunflexo serve para limi- que eu dei antes das explicações?
caracteres dentro de uma entrada defi- tar a pesquisa ao início de cada linha e – Achei que o fgrep resolveria o teu prob-
nida, mas o que vem a ser uma “entrada os apóstrofos servem para o Shell não lema mais rapidamente que o grep.
definida”? Bem, existem várias formas interpretar esse circunflexo, deixando-o – Perfeito! Tô vendo que você está
de definir a entrada do comando grep. passar incólume para o comando grep. atento, entendendo tudo que estou te
Veja só. Para pesquisar em um arquivo: No 2º exemplo mandei listar todas as explicando! Vamos ver mais exemplos

www.linuxmagazine.com.br Setembro 2004 87


LINUX USER Papo de Botequim

Quadro 1 - Listando subdiretórios ser explorado).


– Péra aí! De onde eu vou receber os
$ ls -l | grep ‘^d’ dados dos CDs?
drwxr-xr-x 3 root root 4096 Dec 18 2000 doc – Vou mostrar como o programa pode
drwxr-xr-x 11 root root 4096 Jul 13 18:58 freeciv receber parâmetros de quem o estiver
drwxr-xr-x 3 root root 4096 Oct 17 2000 gimp executando e, em breve, ensinarei a ler
drwxr-xr-x 3 root root 4096 Aug 8 2000 gnome os dados da tela ou de um arquivo.
drwxr-xr-x 2 root root 4096 Aug 8 2000 idl
drwxrwxr-x 14 root root 4096 Jul 13 18:58 locale Passando parâmetros
drwxrwxr-x 12 root root 4096 Jan 14 2000 lyx Veja abaixo a estrutura do arquivo con-
drwxrwxr-x 3 root root 4096 Jan 17 2000 pixmaps tendo a lista das músicas:
drwxr-xr-x 3 root root 4096 Jul 2 20:30 scribus
drwxrwxr-x 3 root root 4096 Jan 17 2000 sounds nomedoálbum^intérprete1~nomeU
drwxr-xr-x 3 root root 4096 Dec 18 2000 xine damúsica1:...:intérpreten~nomeU
drwxr-xr-x 3 root root 4096 Jun 19 2000 xplns damúsican

para clarear de vez as diferenças de -l). Os apóstrofos foram usados para o Isto é, o nome do álbum será separado
uso entre os membros da família. Shell não “ver” o circunflexo. Vamos ver por um circunflexo do resto do registro,
Eu sei que em um arquivo qualquer mais um. Veja na Tabela 1 as quatro formado por diversos grupos compostos
existe um texto falando sobre Linux, só primeiras posições possíveis da saída de pelo intérprete de cada música do CD e a
não tenho certeza se está escrito com L um ls -l em um arquivo comum (não é música interpretada. Estes grupos são
maiúsculo ou minúsculo. Posso fazer diretório, nem link, nem …). separados entre si por dois pontos (:) e,
uma busca de duas formas: Para descobrir todos os arquivos exe- internamente, o intérprete será separado
cutáveis em um determinado diretório por um til (~) do nome da música.
egrep (Linux | linux) arquivo.txt eu poderia fazer: Quero escrever um programa chamado
musinc, que incluirá registros no meu
ou então: $ ls -la | egrep ‘^-..(x|s)’ arquivo músicas. Passarei cada álbum
como parâmetro para o programa:
grep [Ll]inux arquivo.txt novamente usamos o circunflexo para
limitar a pesquisa ao início de cada $ musinc “álbum^interprete~U
No primeiro caso, a expressão regular linha, ou seja, listamos as linhas que musica:interprete~musica:...”
complexa (Linux | linux) usa os parênte- começam por um traço (-), seguido de
ses para agrupar as opções e a barra ver- qualquer coisa (o ponto), novamente Desta forma, musinc estará recebendo os
tical (|) é usada como um “ou” (or, em seguido de qualquer coisa, e por fim um dados de cada álbum como se fosse uma
inglês) lógico, isto é, estou procurando x ou um s. Obteríamos o mesmo resul- variável. A única diferença entre um
Linux ou linux. tado se usássemos o comando: parâmetro recebido e uma variável é que
No segundo, a expressão regular os primeiros recebem nomes numéricos
[Ll]inux significa: começado por L ou l $ ls -la | grep ‘^-..[xs]’ (o que quis dizer é que seus nomes são
seguido de inux. Como esta é uma formados somente por um algarismo,
expressão simples, o grep consegue e além disso, agilizaríamos a pesquisa. isto é, $1, $2, $3, …, $9). Vamos, fazer
resolvê-la, por isso é melhor usar a mais alguns testes:
segunda forma, já que o egrep tornaria a A “CDteca”
pesquisa mais lenta. Vamos começar a desenvolver progra- $ cat teste
Outro exemplo. Para listar todos os mas! Creio que a montagem de um #!/bin/bash
subdiretórios do diretório corrente, basta banco de dados de músicas é bacana #Teste de passagem de parametros
usar o comando $ ls -l | grep ‘^d’. Veja o para efeito didático (e útil nestes tempos echo “1o. parm -> $1”
resultado no Quadro 1. de downloads de arquivos MP3 e echo “2o. parm -> $2”
No exemplo, o circunflexo (^) serviu queimadores de CDs). Não se esqueça echo “3o. parm -> $3”
para limitar a pesquisa à primeira que, da mesma forma que vamos desen-
posição da saída do ls longo (parâmetro volver um monte de programas para Agora vamos rodar esse programinha:
organizar os seus CDs de música, com
Tabela 1 pequenas adaptações você pode fazer o $ teste passando parametros para U
mesmo para organizar os CDs de soft- testar
Posição Valores possíveis
1ª -
ware que vêm com a Linux Magazine e bash: teste: cannot execute
2ª r ou -
outros que você compra ou queima, e
3ª w ou - disponibilizar esse banco de software Ops! Esqueci-me de tornar o script exe-
4ª x,s(suid) ou - para todos os que trabalham com você cutável. Vou fazer isso e testar nova-
(o Linux é multiusuário, e como tal deve mente o programa:

88 Setembro 2004 www.linuxmagazine.com.br


Papo de Botequim LINUX USER

$ chmod 755 teste Execute o programa: inclusão de CDs no meu banco chamado
$ teste passando parametros para U musicas. O programa é muito simples
testar $ teste passando parametros para testar (como tudo em Shell). Veja a Listagem 1.
1o. parm -> passando O programa teste recebeu 4 U O script é simples e funcional; limito-
2o. parm -> parametros parametros me a anexar ao fim do arquivo musicas o
3o. parm -> para 1o. parm -> passando parâmetro recebido. Vamos cadastrar 3
2o. parm -> parametros álbuns para ver se funciona (para não
Repare que a palavra testar, que seria o 3o. parm -> para ficar “enchendo lingüiça,” suponho que
quarto parâmetro, não foi listada. Isso Para listar todos de uma U em cada CD só existem duas músicas):
ocorreu porque o programa teste só lista “tacada” eu faco passando U
os três primeiros parâmetros recebidos. parametros para testar $ musinc “album3^Artista5U
Vamos executá-lo de outra forma: ~Musica5:Artista6~Musica5”
Repare que antes das aspas usei uma $ musinc “album1^Artista1U
$ teste “passando parametros” U barra invertida, para escondê-las da ~Musica1:Artista2~Musica2”
para testar interpretação do Shell (se não usasse as $ musinc “album 2^Artista3U
1o. parm -> passando parametros contrabarras as aspas não apareceriam). ~Musica3:Artista4~Musica4”
2o. parm -> para Como disse, os parâmetros recebem
3o. parm -> testar números de 1 a 9, mas isso não significa Listando o conteúdo do arquivo musicas:
que não posso usar mais de nove
As aspas não deixaram o Shell ver o parâmetros. Significa que só posso $ cat musicas
espaço em branco entre as duas endereçar nove. Vamos testar isso: album3^Artista5~Musica5:Artista6U
primeiras palavras, e elas foram consid- ~Musica6
eradas como um único parâmetro. E $ cat teste album1^Artista1~Musica1:Artista2U
falando em passagem de parâmetros, #!/bin/bash ~Musica2
uma dica: veja na Tabela 2 algumas var- # Programa para testar passagem U album2^Artista3~Musica3:Artista4U
iáveis especiais. Vamos alterar o pro- de parametros (3a. Versao) ~Musica4
grama teste para usar as novas variáveis: echo O programa $0 recebeu $# U
parametros Podia ter ficado melhor. Os álbuns estão
$ cat teste echo “11o. parm -> $11” fora de ordem, dificultando a pesquisa.
#!/bin/bash shift Vamos alterar nosso script e depois testá-
# Programa para testar passagem U echo “2o. parm -> $1” lo novamente. Veja a listagem 2. Sim-
de parametros (2a. Versao) shift 2 plesmente inseri uma linha que classifica
echo O programa $0 recebeu $# U echo “4o. parm -> $1” o arquivo musicas, redirecionando a
parametros saída para ele mesmo (para isso serve a
echo “1o. parm -> $1” Execute o programa: opção -o), após cada álbum ser anexado.
echo “2o. parm -> $2”
echo “3o. parm -> $3” $ teste passando parametros para U $ cat musicas
echo Para listar todos de uma U testar album1^Artista1~Musica1:Artista2U
\”tacada\” eu faco $* O programa teste recebeu 4 U ~Musica2
parametros que são: albu2^Artista3~Musica3:Artista4U
Listagem 1: Incluindo CDs 11o. parm -> passando1 ~Musica4
na “CDTeca” 2o. parm -> parametros album3^Artista5~Musica5:Artista6U
4o. parm -> testar ~Musica6
$ cat musinc
#!/bin/bash Duas coisas muito interessantes aconte- Oba! Agora o programa está legal e
# Cadastra CDs (versao 1) ceram neste script. Para mostrar que os quase funcional. Ficará muito melhor em
# nomes dos parâmetros variam de $1 a $9 uma nova versão, que desenvolveremos
echo $1 >> musicas digitei echo $11 e o que aconteceu? O após aprender a adquirir os dados da tela
Shell interpretou como sendo $1 seguido e formatar a entrada.
do algarismo 1 e listou passando1;
Listagem 2 O comando shift, cuja sintaxe é shift n, Tabela 2: Variáveis especiais
podendo o n assumir qualquer valor
$ cat musinc Variável Significado
numérico, despreza os n primeiros
#!/bin/bash $0 Contém o nome do programa
parâmetros, tornando o parâmetro de
# Cadastra CDs (versao 2) $# Contém a quantidade de
ordem n+1. parâmetros passados
#
Bem, agora que você já sabe sobre $* Contém o conjunto de todos os
echo $1 >> musicas
passagem de parâmetros, vamos voltar à parâmetros (muito parecido com $@)
sort -o musicas musicas
nossa “cdteca” para fazer o script de

www.linuxmagazine.com.br Setembro 2004 89


LINUX USER Papo de Botequim

Ficar listando arquivos com o grep <cadeia de caracteres> U Listagem 5 - musexc


comando cat não está com nada, vamos [arq1, arq2, ..., arqn]
fazer um programa chamado muslist $ cat musexc
para listar um álbum, cujo nome será O grep entendeu que deveria procurar a #!/bin/bash
passado como parâmetro. Veja o código cadeia de caracteres album nos arquivos # Exclui CDs (versao 1)
na Listagem 3: 2 e musicas. Como o arquivo 2 não #
Vamos executá-lo, procurando pelo existe, grep gerou o erro e, por encontrar grep -v “$1” musicas > /tmp/mus$$
album 2. Como já vimos antes, para pas- a palavra album em todos os registros de mv -f /tmp/mus$$ musicas
sar a string album 2 é necessário pro- musicas, listou a todos.
tegê-la da interpretação do Shell, para É melhor ignorarmos maiúsculas e
que ele não a interprete como dois minúsculas na pesquisa. Resolveremos mando. Estamos então prontos para
parâmetros. Exemplo: os dois problemas com a Listagem 4. desenvolver o script para remover CDs
Nesse caso, usamos a opção -i do grep empenados da sua “CDteca”. Veja o
$ muslist “album 2” que, como já vimos, serve para ignorar código da Listagem 5.
grep: can’t open 2 maiúsculas e minúsculas, e colocamos o Na primeira linha mandei para
musicas: album1^Artista1~Musica1U $1 entre aspas, para que o grep continu- /tmp/mus$$ o arquivo musicas, sem os
:Artista2~Musica2 asse a ver a cadeia de caracteres resul- registros que atendessem a consulta feita
musicas: album2^Artista3~Musica3U tante da expansão da linha pelo Shell pelo comando grep. Em seguida, movi
:Artista4~Musica4 como um único argumento de pesquisa. /tmp/mus$$ por cima do antigo musicas.
musicas:album3^Artista5~Musica5U Usei o arquivo /tmp/mus$$ como arqui-
:Artista6~Musica6 $ muslist “album 2” vo de trabalho porque, como já havia
album2^Artista3~Musica3:Artista4U citado no artigo anterior, o $$ contém o
Que lambança! Onde está o erro? Eu tive ~Musica4 PID (identificação do processo) e, dessa
o cuidado de colocar o parâmetro pas- forma, cada um que editar o arquivo
sado entre aspas para o Shell não o Agora repare que o grep localiza a musicas o fará em um arquivo de tra-
dividir em dois! É, mas repare como o cadeia pesquisada em qualquer lugar do balho diferente, evitando colisões.
grep está sendo executado: registro; então, da forma que estamos Os programas que fizemos até aqui
fazendo, podemos pesquisar por álbum, ainda são muito simples, devido à falta
grep $1 musicas por música, por intérprete e mais. de ferramentas que ainda temos. Mas é
Quando conhecermos os comandos bom praticar os exemplos dados porque,
Mesmo colocando álbum 2 entre aspas, condicionais, montaremos uma nova eu prometo, chegaremos a desenvolver
para que fosse encarado como um único versão de muslist que permitirá especi- um sistema bacana para controle dos
parâmetro, quando o $1 foi passado pelo ficar por qual campo pesquisar. seus CDs. Na próxima vez que nos
Shell para o comando grep, transformou- Ah! Em um dia de verão você foi à encontrarmos, vou te ensinar como fun-
se em dois argumentos. Dessa forma, o praia, esqueceu os CDs no carro, aquele cionam os comandos condicionais e
conteúdo da linha que o grep executou “solzinho” de 40 graus empenou seu aprimoraremos mais um pouco esses
foi o seguinte: disco favorito e agora você precisa de scripts. Por hoje chega! Já falei demais e
uma ferramenta para removê-lo do estou de goela seca! Garçom! Mais um
grep album 2 musicas banco de dados? Não tem problema, sem colarinho! ■
vamos desenvolver um script chamado
Como a sintaxe do grep é: musexc, para excluir estes CDs. INFORMAÇÕES
Antes de desenvolver o “bacalho”,
Listagem 3 - muslist quero te apresentar a uma opção bas- [1] http://www.revistadolinux.com.br/ed/003/
tante útil da família de comandos grep. É ferramentas.php3
$ cat muslist
a opção -v, que quando usada lista todos [2] http://www.revistadolinux.com.br/ed/007/
#!/bin/bash
os registros da entrada, exceto o(s) local- ereg.php3
# Consulta CDs (versao 1)
izado(s) pelo comando. Exemplos: [3] http://www.aurelio.net/er/livro/
#
grep $1 musicas
$ grep -v “album 2” musicas
album1^Artista1~Musica1:Artista2U Julio Cezar Neves é
SOBRE O AUTOR

Analista de Suporte de
Listagem 4 ~Musica2
Sistemas desde 1969 e
muslist melhorado album3^Artista5~Musica5:Artista6U trabalha com Unix
~Musica6 desde 1980, quando
$ cat muslist
fez parte da equipe
#!/bin/bash
Conforme expliquei antes, o grep do que desenvolveu o
# Consulta CDs (versao 2) SOX, um sistema
exemplo listou todos os registros de
# operacional similar ao Unix, produzido
musicas exceto o referente a album 2, pela Cobra Computadores.
grep -i “$1” musicas
porque atendia ao argumento do co-

90 Setembro 2004 www.linuxmagazine.com.br


����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER

Curso de Shell Script

Papo de
botequim III
Um chopinho, um aperitivo e o papo continua. Desta vez vamos aprender
alguns comandos de manipulação de cadeias de caracteres, que serão muito

úteis na hora de incrementar nossa “CDteca”. POR JULIO CEZAR NEVES

G arçon! traga dois chopes por


favor que hoje eu vou ter que
falar muito. Primeiro quero
mostrar uns programinhas simples
de usar e muito úteis, como o cut, que
Então, recapitulando, o layout
do arquivo é o seguinte: nome do
álbum^intérprete1~nome da música1:...:
intérpreten~nome da músican, isto é, o
nome do álbum será separado por um
Artista2
Artista4
Artista6
Artista8

é usado para cortar um determinado circunflexo (^) do resto do registro, que Para entender melhor isso, vamos anali-
pedaço de um arquivo. A sintaxe e é formado por diversos grupos compos- sar a primeira linha de musicas:
alguns exemplos de uso podem ser vis- tos pelo intérprete de cada música do
tos no Quadro 1: CD e a respectiva música interpretada. $ head -1 musicas
Como dá para ver, existem quatro Estes grupos são separados entre si por album 1^Artista1~Musica1: U
sintaxes distintas: na primeira (-c 1-5) dois-pontos (:) e o intérprete será sepa- Artista2~Musica2
especifiquei uma faixa, na segunda rado do nome da música por um til (~).
(-c -6) especifiquei todo o texto até Então, para pegar os dados referentes Então observe o que foi feito:
uma posição, na terceira (-c 4-) tudo de a todas as segundas músicas do arquivo
uma determinada posição em diante e musicas, devemos digitar: album 1^Artista1~Musica1: U
na quarta (-c 1,3,5,7,9), só as posições Artista2~Musica2
determinadas. A última possibilidade $ cut -f2 -d: musicas
(-c -3,5,8-) foi só para mostrar que pode- Artista2~Musica2 Desta forma, no primeiro cut o primeiro
mos misturar tudo. Artista4~Musica4 campo do delimitador (-d) dois-pon-
Mas não pense que acabou por aí! Artista6~Musica5 tos (:) é album 1^Artista1~Musica1 e o
Como você deve ter percebido, esta Artista8~Musica8@10_L: segundo, que é o que nos interessa, é
forma de cut é muito útil para lidar com Artista2~Musica2. Vamos então ver o
arquivos com campos de tamanho fi xo, Ou seja, cortamos o segundo campo que aconteceu no segundo cut:
mas atualmente o que mais existe são z(-f de field, campo em inglês) delimi-
arquivos com campos de tamanho vari- tado (-d) por dois-pontos (:). Mas, se Artista2~Musica2
ável, onde cada campo termina com um quisermos somente os intérpretes, deve-
delimitador. Vamos dar uma olhada no mos digitar: Agora, primeiro campo do delimitador
arquivo musicas que começamos a pre- (-d) til (~), que é o que nos interessa,
parar na última vez que viemos aqui no $ cut -f2 -d: musicas | cut U é Artista2 e o segundo é Musica2. Se
botequim. Veja o Quadro 2. -f1 -d~ o raciocínio que fizemos para a pri-

�����������������������������
www.linuxmagazine.com.br Outubro 2004 85
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim

Quadro 1 – O comando cut meira linha for aplicado ao restante do


arquivo, chegaremos à resposta ante-
posta a um exercício prático, valendo
nota, que passei ele me entregou um
riormente dada. Outro comando muito script com todos os comandos sepa-
A sintaxe do cut é: cut -c PosIni-PosFim
interessante é o tr que serve para subs- rados por ponto-e-vírgula (lembre-se
[arquivo], onde PosIni é a posição inicial, e
tituir, comprimir ou remover caracteres. que o ponto-e-vírgula serve para sepa-
PosFim a posição final. Veja os exemplos:
Sua sintaxe segue o seguinte padrão: rar diversos comandos em uma mesma
linha). Vou dar um exemplo simplifi-
$ cat numeros
tr [opções] cadeia1 [cadeia2] cado, e idiota, de um script assim:
1234567890
0987654321
O comando copia o texto da entrada $ cat confuso
1234554321
padrão (stdin), troca as ocorrência dos echo leia Programação Shell U
9876556789
caracteres de cadeia1 pelo seu corres- Linux do Julio Cezar Neves U
pondente na cadeia2 ou troca múltiplas > livro;cat livro;pwd;ls;rm U
$ cut -c1-5 numeros
ocorrências dos caracteres de cadeia1 -f livro2>/dev/null;cd ~
12345
por somente um caracter, ou ainda
09876
caracteres da cadeia1. As principais Eu executei o programa e ele funcionou:
12345
opções do comando são mostradas na
98765
Tabela 1. $ confuso
Primeiro veja um exemplo bem bobo: leia Programação Shell Linux U
$ cut -c-6 numeros
do Julio Cezar Neves
123456
$ echo bobo | tr o a /home/jneves/LM
098765
baba confuso livro musexc musicas
123455
musinc muslist numeros
987655
Isto é, troquei todas as ocorrências da
letra o pela letra a. Suponha que em Mas nota de prova é coisa séria (e nota
$ cut -c4- numeros
determinado ponto do meu script eu de dólar é mais ainda) então, para
4567890
peça ao operador para digitar s ou n entender o que o aluno havia feito, o
7654321
(sim ou não), e guardo sua resposta chamei e em sua frente digitei:
4554321
na variável $Resp. Ora, o conteúdo de
6556789
$Resp pode conter letras maiúsculas $ tr ”;” ”\n” < confuso
ou minúsculas, e desta forma eu teria echo leia Programação Shell U
$ cut -c1,3,5,7,9 numeros
que fazer diversos testes para saber se Linux do Julio Cezar Neves
13579
a resposta dada foi S, s, N ou n. Então o pwd
08642
melhor é fazer: cd ~
13542
ls -l
97568
$ Resp=$(echo $Resp | tr SN sn) rm -f lixo 2>/dev/null

$ cut -c -3,5,8- numeros


e após este comando eu teria certeza O cara ficou muito desapontado, porque
1235890
de que o conteúdo de $Resp seria um em dois ou três segundos eu desfi z a
0986321
s ou um n. Se o meu arquivo ArqEnt gozação que ele perdeu horas para fazer.
1235321
está todo em letras maiúsculas e desejo Mas preste atenção! Se eu estivesse em
9875789
passá-las para minúsculas eu faço: uma máquina Unix, eu teria digitado:

$ tr A-Z a-z < ArqEnt > / tmp/$$ $ tr ”;” ”\012” < confuso
Quadro 2 – O arquivo $ mv -f /tmp/$$ ArqEnt
musicas Agora veja a diferença entre o resultado
Note que neste caso usei a notação A- de um comando date executado hoje e
Z para não escrever ABCD…YZ. Outro outro executado há duas semanas:
$ cat musicas
tipo de notação que pode ser usada são
album 1^Artista1~Musica1:U
as escape sequences (como eu traduzi- Sun Sep 19 14:59:54 2004
Artista2~Musica2
ria? Seqüências de escape? Meio sem Sun Sep 5 10:12:33 2004
album 2^Artista3~Musica3:U
sentido, né? Mas vá lá…) que também
Artista4~Musica4
são reconhecidas por outros comandos Notou o espaço extra após o “Sep” na
album 3^Artista5~Musica5:U
e também na linguagem C, e cujo signi- segunda linha? Para pegar a hora eu
Artista6~Musica5
ficado você verá na Tabela 2: deveria digitar:
album 4^Artista7~Musica7:U
Deixa eu te contar um “causo”: um
Artista8~Musica8
aluno que estava danado comigo resol- $ date | cut -f 4 -d ’ ’
veu complicar minha vida e como res- 14:59:54

�����������������������������
86 Outubro 2004 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER

Mas há duas semanas ocorreria o Bem a opção -d do tr remove do arquivo E veja também o date:
seguinte: todas as ocorrências do caractere espe-
cificado. Desta forma eu removi os $ date
$ date | cut -f 4 -d ’ ’ caracteres indesejados, salvei o texto Mon Sep 20 10:47:19 BRT 2004
5 em um arquivo temporário e posterior-
mente renomeei-o para o nome original. Repare que o mês e o dia estão no
Isto porque existem 2 caracteres em Uma observação: em um sistema Unix mesmo formato em ambos os coman-
branco antes do 5 (dia). Então o ideal eu deveria digitar: dos. Ora, se em algum registro do who
seria transformar os espaços em branco eu não encontrar a data de hoje, é sinal
consecutivos em somente um espaço $ tr -d ’\015’ < ArqDoDOS.U que o usuário está “logado” há mais
para poder tratar os dois resultados do txt > /tmp/$$ de um dia, já que ele não pode ter se
comando date da mesma forma, e isso “logado” amanhã… Então vamos guar-
se faz assim: Uma dica: o problema com os termina- dar o pedaço que importa da data de
dores de linha (CR/LF) só aconteceu hoje para depois procurá-la na saída do
$ date | tr -s ” ” porque a transferência do arquivo foi comando who:
Sun Sep 5 10:12:33 2004 feita no modo binário (ou image), Se
antes da transmissão do arquivo tivesse $ Data=$(date | cut -f 2-3 U
E agora eu posso cortar: sido estipulada a opção ascii do ftp, isto -d’ ’)
não teria ocorrido.
$ date | tr -s ” ” | cut -f 4 U – Olha, depois desta dica tô começando a Eu usei a construção $(...), para prio-
-d ” ” gostar deste tal de shell, mas ainda tem rizar a execução dos comandos antes
10:12:33 muita coisa que não consigo fazer. de atribuir a sua saída à variável Data.
– Pois é, ainda não te falei quase nada Vamos ver se funcionou:
Olha só como o Shell está quebrando o sobre programação em shell, ainda
galho. Veja o conteúdo de um arquivo tem muita coisa para aprender, mas $ echo $Data
baixado de uma máquina Windows: com o que aprendeu, já dá para resol- Sep 20
ver muitos problemas, desde que você
$ cat -ve ArqDoDOS.txt adquira o “modo shell de pensar”. Você Beleza! Agora, o que temos que fazer é
Este arquivo^M$ seria capaz de fazer um script que diga procurar no comando who os registros
foi gerado pelo^M$ quais pessoas estão “logadas” há mais que não possuem esta data.
DOS/Win e foi^M$ de um dia no seu servidor?
baixado por um^M$ – Claro que não! Para isso seria necessá- – Ah! Eu acho que estou entendendo!
ftp mal feito.^M$ rio eu conhecer os comandos condicio- Você falou em procurar e me ocorreu o
nais que você ainda não me explicou comando grep, estou certo?
Dica: a opção -v do cat mostra os carac- como funcionam. - Deixa eu tentar – Certíssimo! Só que eu tenho que usar o
teres de controle invisíveis, com a nota- mudar um pouco a sua lógica e trazê- grep com aquela opção que ele só lista
ção ^L, onde ^ é a tecla Control e L é la para o “modo shell de pensar”, mas os registros nos quais ele não encon-
a respectiva letra. A opção -e mostra o antes é melhor tomarmos um chope. trou a cadeia. Você se lembra que
fi nal da linha como um cifrão ($). Agora que já molhei a palavra, vamos opção é essa?
Isto ocorre porque no DOS o fi m dos resolver o problema que te propus. Veja – Claro, a -v…
registros é indicado por um Carriage como funciona o comando who: – Isso! Tá ficando bom! Vamos ver:
Return (\r – Retorno de Carro, CR) e
um Line Feed (\f – Avanço de Linha, ou $ who $ who | grep -v ”$Data”
LF). No Linux porém o fi nal do regis- jneves pts/ U jneves pts/ U
tro é indicado somente pelo Line Feed. 1 Sep 18 13:40 1 Sep 18 13:40
Vamos limpar este arquivo: rtorres pts/ U
0 Sep 20 07:01 Se eu quisesse um pouco mais de perfu-
$ tr -d ’\r’ < ArqDoDOS.txt > /tmp/$$ rlegaria pts/ U maria eu faria assim:
$ mv -f /tmp/$$ ArqDoDOS.txt 1 Sep 20 08:19
lcarlos pts/ U $ who | grep -v ”$Data” |U
Agora vamos ver o que aconteceu: 3 Sep 20 10:01 cut -f1 -d ’ ’
jneves
$ cat -ve ArqDoDOS.txt Tabela 1 – O comando tr
Este arquivo$ Opção Significado Viu? Não foi necessário usar comando
foi gerado pelo$ condicional, até porque o nosso
-s Comprime n ocorrências de
DOS/Rwin e foi$ cadeia1 em apenas uma
comando condicional, o famoso if, não
baixado por um$ testa condição, mas sim instruções.
-d Remove os caracteres de cadeia1
ftp mal feito.$ Mas antes veja isso:

�����������������������������
www.linuxmagazine.com.br Outubro 2004 87
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim

$ ls musicas Tabela 2 /dev/null


musicas Seqüência Significado Octal then
$ echo $? \t Tabulação \011 echo Usuario \’$1\’ já U
0 \n Nova linha \012 existe
$ ls ArqInexistente <ENTER> else
ls: ArqInexistente: No such U \v Tabulação \013 if useradd $1
file or directory Vertical then
$ echo $? \f Nova \014 echo Usuário \’$1\’ U
1 Página incluído em /etc/passwd
$ who | grep jneves \r Início da \015 else
jneves pts/1 Sep 18 U linha <^M> echo ”Problemas no U
\\ Uma barra \0134
13:40 (10.2.4.144) cadastramento. Você é root?”
invertida
$ echo $? fi
0 fi
$ who | grep juliana já existe
$ echo $? else Vamos testá-lo como um usuário normal :
1 if useradd $1
then $ incusu ZeNinguem
O que é que esse $? faz aí? Algo come- echo Usuário \’$1\’ U ./incusu[6]: useradd: not found
çado por cifrão ($) parece ser uma vari- incluído em /etc/passwd Problemas no cadastramento. U
ável, certo? Sim é uma variável que else Você é root?
contém o código de retorno da última echo ”Problemas no U
instrução executada. Posso te garan- cadastramento. Você é root?” Aquela mensagem de erro não deveria
tir que se esta instrução foi bem suce- fi aparecer! Para evitar isso, devemos
dida, $? terá o valor zero, caso contrário fi redirecionar a saída de erro (stderr) do
seu valor será diferente de zero. O que comando useradd para /dev/null. A ver-
nosso comando condicional (if) faz é Repare que o if está testando direto o são fi nal fica assim:
testar esta variável. Então vamos ver a comando grep e esta é a sua fi nalidade.
sua sintaxe: Caso o if seja bem sucedido, ou seja, o $ cat incusu
usuário (cujo nome está em $1) foi #!/bin/bash
if cmd encontrado em /etc/passwd, os coman- # Versão 3
then dos do bloco do then serão executados if grep ^$1 /etc/passwd > U
cmd1 (neste exemplo, apenas o echo). Caso /dev/null
cmd2 contrário, as instruções do bloco do else then
cmdn serão executadas, quando um novo if echo Usuario \’$1\’ já U
else testa se o comando useradd foi execu- existe
cmd3 tado a contento, criando o registro do else
cmd4 usuário em /etc/passwd, ou exibindo if useradd $1 2> /dev/null
cmdm uma mensagem de erro, caso contrário. then
fi Executar o programa e passe como echo Usuário \’$1\’ U
parâmetro um usuário já cadastrado: incluído em /etc/passwd
Ou seja, caso comando cmd tenha sido else
executado com sucesso, os comandos $ incusu jneves echo ”Problemas no U
do bloco do then (cmd1, cmd2 e cmdn) jneves:x:54002:1001:Julio Neves: U cadastramento. Você é root?”
serão executados, caso contrário, os /home/jneves:/bin/ U fi
comandos do bloco opcional do else bash fi
(cmd3, cmd4 e cmdm) serão executados. Usuario ’jneves’ ja existe
O bloco do if é terminando com um fi. Depois disso, vejamos o comportamento
Vamos ver na prática como isso fun- No exemplo dado, surgiu uma linha do programa, se executado pelo root:
ciona, usando um script que inclui usu- indesejada, ela é a saída do comando
ários no arquivo /etc/passwd: grep. Para evitar que isso aconteça, $ incusu botelho
devemos desviar a saída para /dev/null. Usuário ’botelho’ incluido em U
$ cat incusu O programa fica assim: /etc/passwd
#!/bin/bash
# Versão 1 $ cat incusu E novamente:
if grep ^$1 /etc/passwd #!/bin/bash
then # Versão 2 $ incusu botelho
echo Usuario \’$1\’U if grep ^$1 /etc/passwd > U Usuário ’botelho’ já existe

�����������������������������
88 Outubro 2004 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER

Lembra que eu falei que ao longo dos no início da cadeia e cifrão ($) no fi m, Como você viu, o programa melho-
nossos papos e chopes os nossos pro- servem para testar se o parâmetro (o rou um pouquinho, mas ainda não está
gramas iriam se aprimorando? Então álbum e seus dados) é exatamente igual pronto. À medida que eu te ensinar a
vejamos agora como podemos melhorar a algum registro já existente. Vamos programar em shell, nossa CDteca vai
o nosso programa para incluir músicas executar nosso programa novamente, ficar cada vez melhor.
na "CDTeca": mas desta vez passamos como parâme- – Entendi tudo que você me explicou,
tro um álbum já cadastrado, pra ver o mas ainda não sei como fazer um if
$ cat musinc que acontece: para testar condições, ou seja o uso
#!/bin/bash normal do comando.
# Cadastra CDs (versao 3) $ musinc ”album 4^Artista7~ U – Para isso existe o comando test, que
# Musica7:Artista8~Musica8” testa condições. O comando if testa
if grep ”^$1$” musicas > U Este álbum já está cadastrado o comando test. Como já falei muito,
/dev/null preciso de uns chopes para molhar a
then E agora um não cadastrado: palavra. Vamos parar por aqui e na
echo Este álbum já está U próxima vez te explico direitinho o
cadastrado $ musinc ”album 5^Artista9~ U uso do test e de diversas outras sinta-
else Musica9:Artista10~Musica10” xes do if.
echo $1 >> musicas $ cat musicas – Falou! Acho bom mesmo porque eu
sort musicas -o musicas album 1^Artista1~Musica1: U também já tô ficando zonzo e assim
fi Artista2~Musica2 tenho tempo para praticar esse monte
album 2^Artista3~Musica3: U de coisas que você me falou hoje.
Como você viu, é uma pequena evolu- Artista4~Musica4 Para fi xar o que você aprendeu, tente
ção em relação à versão anterior. Antes album 3^Artista5~Musica5: U fazer um scriptizinho para informar se
de incluir um registro (que na versão Artista6~Musica5 um determinado usuário, cujo nome
anterior poderia ser duplicado), testa- album 4^Artista7~Musica7: U será passado como parâmetro, está
mos se o registro começa (^) e termina Artista8~Musica8 “logado” no sistema ou não.
($) de forma idêntica ao parâmetro album 5^Artista9~Musica9: U – Aê Chico! traz mais dois chopes pra
álbum passado ($1). O circunflexo (^) Artista10~Musica10 mim por favor… ■

�����������������������������
www.linuxmagazine.com.br Outubro 2004 89
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim

Curso de Shell Script

Papo de
botequim IV
O garçon já perdeu a conta das cervejas, e o assunto não acaba. Desta
Dave Hamilton - www.sxc.hu

vez vamos aprender a testar os mais variados tipos de condições,

para podermos controlar a execução de nosso programa de acordo

com a entrada fornecida pelo usuário. POR JULIO CEZAR NEVES

E aí cara, tentou fazer o exercício


que te pedi em nosso último
encontro?
– Claro que sim! Em programação, se
você não treinar não aprende. Você
$ logado jneves
jneves pts/0
12:02 (10.2.4.144)
jneves está logado
Oct 18 U
Ah, agora sim! Lembre-se dessa pega-
dinha: a maior parte dos comandos tem
uma saída padrão e uma saída de erros
(o grep é uma das poucas exceções:
ele não exibe uma mensagem de erro
me pediu um script para informar se Realmente funcionou. Passei meu quando não acha uma cadeia de carac-
um determinado usuário, cujo nome nome de usuário como parâmetro e teres) e devemos redirecioná-las para o
será passado como parâmetro para o ele disse que eu estava logado, porém buraco negro quando necessário.
script, está “logado” (arghh!) ou não. ele imprimiu uma linha extra, que eu Bem, agora vamos mudar de assunto:
Fiz o seguinte: não pedi, que é a saída do comando na última vez que nos encontramos
who. Para evitar que isso aconteça, é aqui no botequim, quando já estávamos
$ cat logado só mandá-la para o buraco negro do de goela seca, você me perguntou como
mundo UNIX, o /dev/null. Vejamos se testam condições. Para isso, usamos
#!/bin/bash então como ficaria: o comando test
# Pesquisa se um usuário está
# logado ou não $ cat logado Testes
#!/bin/bash Todos estamos acostumados a usar o
if who | grep $1 # Pesquisa se uma pessoa está if para testar condições, e estas são
then # logada ou não (versão 2) sempre maior que, menor que, maior
echo $1 está logado if who | grep $1 > /dev/null ou igual a, menor ou igual a, igual a e
else then
echo $1 não está no pedaço echo $1 está logado Tabela 1 – Opções do
fi else test para arquivos
echo $1 não está no pedaço Opção Verdadeiro se
– Calma rapaz! Já vi que você chegou fi -e arq arq existe
cheio de tesão. Primeiro vamos pedir -s arq arq existe e tem tamanho maior que zero
os nossos chopes de praxe e depois Agora vamos aos testes:
vamos ao Shell. Chico, traz dois cho- -f arq arq existe e é um arquivo regular
pes, um sem colarinho! $ logado jneves -d arq arq existe e é um diretório
-r arq arq existe e com direito de leitura
– Aaah! Agora que já molhamos os nos- jneves está logado
-w arq arq existe e com direito de escrita
sos bicos, vamos dar uma olhada nos $ logado chico
-x arq arq existe e com direito de execução
resultados do seu programa: chico não está no pedaço

�����������������������������
84 edição 04 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER

Tabela 2 – Opções do test No exemplo, testei a existência do cadeia de caracteres “01” é realmente
para cadeias de caracteres diretório lmb. Se não existisse (else), ele diferente de “1”. Porém, a coisa muda
seria criado. Já sei, você vai criticar a de figura quando as variáveis são testa-
Opção Verdadeiro se:
minha lógica dizendo que o script não das numericamente, já que o número 1
-z cadeia Tamanho de cadeia é zero
está otimizado. Eu sei, mas queria que é igual ao número 01.
-n cadeia Tamanho de cadeia é maior que zero
cadeia A cadeia cadeia tem tamanho maior que zero você o entendesse assim, para então Para mostrar o uso dos conectores -o
c1 = c2 Cadeia c1 e c2 são idênticas poder usar o “ponto-de-espantação” (!) (ou) e -a (e), veja um exemplo “animal”,
como um negador do test. Veja só: programado direto no prompt do Bash.
Me desculpem os zoólogos, mas eu
diferente de. Para testar condições em if test ! -d lmb não entendo nada de reino, fi lo, classe,
Shell Script usamos o comando test, só then ordem, família, gênero, espécie e outras
que ele é muito mais poderoso do que mkdir lmb coisas do tipo, desta forma o que estou
aquilo com que estamos acostuma- fi chamando de família ou de gênero tem
dos. Primeiramente, veja na Tabela 1 cd lmb grande chance de estar total e comple-
as principais opções (existem muitas tamente incorreto:
outras) para testar arquivos em disco Desta forma o diretório lmb seria
e na Tabela 2 as principais opções para criado somente se ele ainda não exis- $ Familia=felinae
teste de cadeias de caracteres. tisse, e esta negativa deve-se ao ponto $ Genero=gato
de exclamação (!) precedendo a opção $ if test $Familia = canidea U
Tabela 3 – Opções do -d. Ao fi m da execução desse fragmento -a $Genero = lobo -o $Familia = U
test para números de script, com certeza o programa esta- felina -a $Genero = leão
ria dentro do diretório lmb. Vamos ver > then
Opção Verdadeiro se Significado
dois exemplos para entender a diferença > echo Cuidado
n1 -eq n2 n1 e n2 são iguais equal
na comparação entre números e entre > else
n1 -ne n2 n1 e n2 não são iguais not equal
cadeias de caracteres. > echo Pode passar a mão
n1 -gt n2 n1 é maior que n2 greater than
n1 -ge n2 n1 é maior ou igual a n2 greater or equal > fi
n1 -lt n2 n1 é menor que n2 less than cad1=1 Pode passar a mão
n1 -le n2 n1 é menor ou igual a n2 less or equal cad2=01
if test $cad1 = $cad2 Neste exemplo, caso o animal fosse
then da família canídea e (-a) do gênero lobo,
Pensa que acabou? Engano seu! Agora echo As variáveis são iguais. ou (-o) da familia felina e (-a) do gênero
é hora de algo mais familiar, as famosas else leão, seria dado um alerta, caso contrá-
comparações com valores numéricos. echo As variáveis são diferentes. rio a mensagem seria de incentivo.
Veja a Tabela 3, e some às opções já fi Atenção: Os sinais de maior (>) no
apresentadas os operadores da Tabela 4. início das linhas internas ao if são os
Ufa! Como você viu, tem coisa pra Executando o fragmento de programa prompts de continuação (que estão
chuchu, e o nosso if é muito mais pode- acima, teremos como resultado: defi nidos na variável $PS2). Quando o
roso que o dos outros. Vamos ver em shell identifica que um comando con-
uns exemplos como isso tudo funciona. As variáveis são diferentes. tinuará na linha seguinte, automatica-
Testamos a existência de um diretório: mente ele coloca este caractere, até que
Vamos agora alterá-lo um pouco para o comando seja encerrado.
if test -d lmb que a comparação seja numérica: Vamos mudar o exemplo para ver se o
then programa continua funcionando:
cd lmb cad1=1
else cad2=01 $ Familia=felino
mkdir lmb if test $cad1 -eq $cad2 $ Genero=gato
cd lmb then $ if test $Familia = felino -o U
fi echo As variáveis são iguais. $Familia = canideo -a $Genero = U
else onça -o $Genero = lobo
Tabela 4 echo As variáveis são diferentes. > then
fi > echo Cuidado
Operador Finalidade > else
Parênteses () 0
E vamos executá-lo novamente: > echo Pode passar a mão
> fi
Exclamação ! 0
As variáveis são iguais. Cuidado
-a 0
-o 0
Como você viu, nas duas execuções Obviamente a operação resultou em
obtive resultados diferentes, porque a erro, porque a opção -a tem precedência

�����������������������������
www.linuxmagazine.com.br edição 04 85
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim

sobre a -o e, dessa, forma o que foi ava- Da mesma forma, para escolhermos mente a legibilidade, pois o comando if
liado primeiro foi a expressão: CDs que tenham a participação do irá ficar com a sintaxe semelhante à das
Artista1 e do Artista2, não é necessá- outras linguagens; por isso, esse será o
$Familia = canideo -a $Genero = U rio montar um if com o conector -o. O modo como o comando test será usado
onça egrep também resolve isso para nós. daqui para a frente.
Veja como: Se você pensa que acabou, está muito
Que foi avaliada como falsa, retor- enganado. Preste atenção à “Tabela Ver-
nando o seguinte: $ egrep (Artista1|Artista2) U dade” na Tabela 5.
musicas
$Familia = felino -o FALSO -o U Tabela 5 - Tabela Verdade
$Genero = lobo Ou (nesse caso específico) o próprio
Combinação E OU
grep poderia nos quebrar o galho:
VERDADEIRO-VERDADEIRO TRUE TRUE
Que resolvida resulta em:
VERDADEIRO-FALSO FALSE TRUE
$grep Artista[12] musicas FALSO-VERDADEIRO FALSE TRUE
VERDADEIRO -o FALSO -o FALSO FALSO-FALSO FALSE FALSE
No egrep acima, foi usada uma
Como agora todos os conectores são expressão regular, na qual a barra ver-
-o, e para que uma série de expressões tical (|) trabalha como um “ou lógico” e Ou seja, quando o conector é e e a
conectadas entre si por diversos “ou” os parênteses são usados para limitar primeira condição é verdadeira, o resul-
lógicos seja verdadeira, basta que uma a amplitude deste “ou”. Já no grep da tado fi nal pode ser verdadeiro ou falso,
delas o seja. A expressão fi nal resultou linha seguinte, a palavra Artista deve dependendo da segunda condição; já no
como VERDADEIRO e o then foi exe- ser seguida por um dos valores da lista conector ou, caso a primeira condição
cutado de forma errada. Para que isso formada pelos colchetes ([]), isto é, 1 ou 2. seja verdadeira, o resultado sempre será
volte a funcionar façamos o seguinte: verdadeiro. Se a primeira for falsa, o resul-
– Tá legal, eu aceito o argumento, o if tado dependerá da segunda condição.
$ if test \($Familia = felino U do shell é muito mais poderoso que Ora, os caras que desenvolveram o
-o $Familia = canideo\) -a U os outros caretas - mas, cá entre nós, interpretador não são bobos e estão
\($Genero = onça -o $Genero = U essa construção de if test ... é muito sempre tentando otimizar ao máximo
lobo\) esquisita, é pouco legível. os algoritmos. Portanto, no caso do
> then – É, você tem razão, eu também não conector e, a segunda condição não será
> echo Cuidado gosto disso e acho que ninguém gosta. avaliada, caso a primeira seja falsa, já
> else Acho que foi por isso que o shell que o resultado será sempre falso. Já
> echo Pode passar a mão incorporou outra sintaxe, que substi- com o ou, a segunda será executada
> fi tui o comando test. somente caso a primeira seja falsa.
Pode passar a mão Aproveitando-se disso, uma forma
Para isso vamos pegar aquele exem- abreviada de fazer testes foi criada. O
Desta forma, com o uso dos parên- plo para fazer uma troca de diretórios, conector e foi batizado de && e o ou de
teses agrupamos as expressões com o que era assim: ||. Para ver como isso funciona, vamos
conector -o, priorizando a execução e usá-los como teste no nosso velho
resultando em VERDADEIRO -a FALSO. if test ! -d lmb exemplo de troca de diretório, que em
Para que seja VERDADEIRO o resul- then sua última versão estava assim:
tado de duas expressões ligadas pelo mkdir lmb
conector -a, é necessário que ambas fi if [ ! -d lmb ]
sejam verdadeiras, o que não é o caso cd lmb then
do exemplo acima. Assim, o resultado mkdir lmb
fi nal foi FALSO, sendo então o else cor- e utilizando a nova sintaxe, vamos fazê- fi
retamente executado. lo assim: cd lmb
Se quisermos escolher um CD que
tenha faixas de 2 artistas diferentes, nos if [ ! -d lmb ] O código acima também poderia ser
sentimos tentados a usar um if com o then escrito de maneira abreviada:
conector -a, mas é sempre bom lembrar mkdir lmb
que o bash nos oferece muitos recursos fi [ ! -d lmb ] && mkdir lmb
e isso poderia ser feito de forma muito cd lmb cd dir
mais simples com um único comando
grep, da seguinte forma: Ou seja, o comando test pode ser Também podemos retirar a negação (!):
substituído por um par de colchetes ([]),
$ grep Artista1 musicas | grep U separados por espaços em branco dos [ -d lmb ] || mkdir lmb
Artista2 argumentos, o que aumentará enorme- cd dir

�����������������������������
86 edição 04 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER

Tabela 6 diretório para dentro dele. Para execu- sempre estará dentro de lmb, desde que
tar mais de um comando dessa forma, tenha permissão para entrar neste dire-
Caractere Significado
é necessário fazer um grupamento de tório, permissão para criar um subdire-
* Qualquer caractere ocorrendo zero ou
comandos, o que se consegue com o tório dentro de ../lmb, que haja espaço
mais vezes
? Qualquer caractere ocorrendo uma vez
uso de chaves ({}). Veja como seria o em disco suficiente...
[...] Lista de caracteres modo correto: Vejamos um exemplo didático: depen-
| “ou” lógico dendo do valor da variável $opc o script
cd lmb || deverá executar uma das opções a
{ seguir: inclusão, exclusão, alteração ou
No primeiro caso, se o primeiro mkdir lmb encerrar sua execução. Veja como fica-
comando (o test, que está represen- cd lmb ria o código:
tado pelos colchetes) for bem sucedido, }
isto é, se o diretório lmb não existir, o if [ $opc -eq 1 ]
comando mkdir será executado porque Ainda não está legal porque, caso o then
a primeira condição era verdadeira e o diretório não exista, o cd exibirá uma inclusao
conector era e. mensagem de erro. Veja o modo certo: elif [ $opc -eq 2 ]
No exemplo seguinte, testamos se o then
diretório lmb existia (no anterior testa- cd lmb 2> /dev/null || exclusao
mos se ele não existia) e, caso isso fosse { elif [ $opc -eq 3 ]
verdade, o mkdir não seria executado mkdir lmb then
porque o conector era ou. Outra forma cd lmb alteracao
de escrever o programa: } elif [ $opc -eq 4 ]
then
cd lmb || mkdir lmb Como você viu, o comando if nos per- exit
mitiu fazer um cd seguro de diversas else
Nesse caso, se o comando cd fosse maneiras. É sempre bom lembrar que o echo Digite uma opção entre U
mal sucedido, o diretório lmb seria “seguro” a que me refi ro diz respeito ao 1 e 4
criado mas não seria feita a mudança de fato de que ao fi nal da execução você fi

�����������������������������
www.linuxmagazine.com.br edição 04 87
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim

Quadro 1 - Script case $opc in O comando date informa a data com-


bem-educado 1) inclusao ;; pleta do sistema e tem diversas opções
2) exclusao ;; de mascaramento do resultado. Neste
#!/bin/bash
3) alteracao ;; comando, a formatação começa com
# Programa bem educado que
4) exit ;; um sinal de mais (+) e os caracteres de
# dá bom-dia, boa-tarde ou
*) echo Digite uma opção U formatação vêm após um sinal de per-
# boa-noite conforme a hora
entre 1 e 4 centagem (%), assim o %H significa a
Hora=$(date +%H)
esac hora do sistema. Dito isso, veja o exem-
case $Hora in
plo no Quadro 1.
0? | 1[01]) echo Bom Dia
Como você deve ter percebido, eu Peguei pesado, né? Que nada, vamos
;;
usei o asterisco como última opção, isto esmiuçar a resolução:
1[2-7] ) echo Boa Tarde
é, se o asterisco atende a qualquer coisa,
;;
então servirá para qualquer coisa que 0? | 1 [01] – Zero seguido de qualquer
* ) echo Boa Noite
não esteja no intervalo de 1 a 4. Outra coisa (?), ou (|) um seguido de zero
;;
coisa a ser notada é que o duplo ponto-e- ou um ([01]), ou seja, esta linha "casa"
esac
vírgula não é necessário antes do esac. com 01, 02, ... 09, 10 e 11;
exit
Vamos agora fazer um script mais 1 [2-7] – Significa um seguido da lista
radical. Ele te dará bom dia, boa tarde de caracteres entre dois e sete, ou seja,
Neste exemplo você viu o uso do ou boa noite dependendo da hora em esta linha pega 12, 13, ... 17;
comando elif como um substituto ou que for executado, mas primeiramente * – Significa tudo o que não casou com
forma mais curta de else if. Essa é uma veja estes comandos: nenhum dos padrões anteriores.
sintaxe válida e aceita, mas poderíamos
fazer ainda melhor. Para isso usamos o $ date – Cara, até agora eu falei muito e bebi
comando case, cuja sintaxe mostramos Tue Nov 9 19:37:30 BRST 2004 pouco. Agora eu vou te passar um
a seguir: $ date +%H exercício para você fazer em casa e
19 me dar a resposta da próxima vez em
case $var in que nos encontrarmos aqui no bote-
padrao1) cmd1 quim, tá legal?
cmd2 – Beleza!
cmdn ;; – É o seguinte: faça um programa que
padrao2) cmd1 receba como parâmetro o nome de
cmd2 um arquivo e que quando executado
cmdn ;; salve esse arquivo com o nome origi-
padraon) cmd1 nal seguido de um til (~) e abra esse
cmd2 arquivo dentro do vi para ser editado.
cmdn ;; Isso é para ter sempre uma cópia
esac de backup do arquivo caso alguém
faça alterações indevidas. Obvia-
Onde a variável $var é comparada aos mente, você fará as críticas neces-
padrões padrao1, ..., padraon. Caso um sárias, como verificar se foi passado
dos padrões corresponda à variável, o um parâmetro, se o arquivo indicado
bloco de comandos cmd1, ..., cmdn cor- existe... Enfi m, o que te der na telha e
respondente é executado até encontrar você achar que deva constar do script.
um duplo ponto-e-vírgula (;;), quando Deu pra entender?
o fluxo do programa será interrompido – Hum, hum...
e desviado para instrução imediata- – Chico, traz mais um, sem colarinho! ■
mente após o comando esac (que, caso
não tenham notado, é case ao contrário. Julio Cezar Neves é
Ele indica o fi m do bloco de código, da Analista de Suporte
de Sistemas desde
SOBRE O AUTOR

mesma forma que ot comando fi indica


1969 e trabalha com
o fi m de um if). Unix desde 1980,
Na formação dos padrões, são aceitos quando participou
os caracteres mostrados na Tabela 6. do desenvolvimento
Para mostrar como o código fica do SOX, um sistema
operacional similar ao Unix pro-
melhor, vamos repetir o exemplo ante-
duzido pela Cobra computadores.
rior, só que desta vez usaremos o case Pode ser contatado no e-mail julio.
em vez do tradicional bloco de código neves@gmail.com
com if ... elif ... else ... fi.

�����������������������������
88 edição 04 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER

Curso de Shell Script

Papo de
Botequim V
Blocos de código e laços (ou loops, como preferem alguns)
Dave Hamilton - www.sxc.hu

são o tema do mês em mais uma lição de nosso curso de Shell

Script. Garçom, salta uma boa redondinha, que tô a fim de

refrescar o pensamento! POR JULIO CEZAR NEVES


F ala cara! E as idéias estão em
ordem? Já fundiu a cuca ou você
ainda agüenta mais Shell?
Até agora já vimos alguns blocos de
código, como quando te mostrei um
exemplo para fazer um cd para dentro
Güento! Tô gostando muito! Gostei de um diretório:
tanto que até caprichei no exercício
Quadro 1: vira.sh
$ cat vira.sh
#!/bin/bash
#
que você passou. Lembra que você cd lmb 2> /dev/null || # vira - vi resguardando
me pediu para fazer um programa { # arquivo anterior
que recebe como parâmetro o nome mkdir lmb # Verifica se algum parâmetro foi
de um arquivo e que quando execu- cd lmb # passado
tado salva esse arquivo com o nome } if [ “$#” -ne 1 ]
original seguido de um til (~) e o then
abre dentro do vi? O fragmento contido entre as duas echo “Erro -> Uso: $0 U
– Claro que lembro, me mostre e expli- chaves ({}) forma um bloco de código. <arquivo>”
que como você fez. Também nesse exercício que acabamos exit 1
– Beleza, dá uma olhada no quadro 1 de ver, em que salvamos o arquivo antes fi
– É, beleza! Mas me diz uma coisa: por de editá-lo, existem vários blocos de Arq=$1
que você terminou o programa com código compreendidos entre os coman- # Caso o arquivo não exista, não
um exit 0? dos then e fi do if. Um bloco de código # há cópia a ser salva
– Eu descobri que o número após o também pode estar dentro de um case if [ ! -f “$Arq” ]
exit indica o código de retorno do ou entre um do e um done. then
programa (o $?, lembra?) e assim, – Peraí, Julio, que do e done são esses? vi $Arq
como a execução foi bem sucedida, Não me lembro de você ter falado exit 0
ele encerra com o $?=0. Porém, se nisso, e olha que estou prestando fi
você observar, verá que caso o pro- muita atenção... # Se eu não puder alterar o
grama não tenha recebido o nome do – Pois é, ainda não tinha falado porque #arquivo, vou usar o vi para que?
arquivo ou caso o operador não tenha não havia chegado a hora certa. if [ ! -w “$Arq” ]
permissão de gravação nesse arquivo, Todas as instruções de loop ou laço then
o código de retorno ($?) seria dife- executam os comandos do bloco com- echo “Você não tem permissão U
rente do zero. preendidos entre um do e um done. As de escrita em $Arq”
– Grande garoto, aprendeu legal, mas é instruções de loop ou laço são for, while exit 2
bom deixar claro que exit 0, simples- e until , que serão explicadas uma a fi
mente exit ou não colocar exit produ- uma a partir de hoje. # Já que está tudo OK, vou
zem igualmente um código de retorno # salvar a cópia e chamar o vi
($?) igual a zero. Agora vamos falar O comando For cp -f $Arq $Arq~
sobre as instruções de loop ou laço, Se você está habituado a programar, vi $Arq
mas antes vou passar o conceito de certamente já conhece o comando for, exit 0
bloco de código. mas o que você não sabe é que o for,

�����������������������������
www.linuxmagazine.com.br edição 05 89
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim

que é uma instrução intrínseca do Shell Então vamos executá-lo: $ echo “$IFS” | od -h
(isso significa que o código fonte do 0000000 0920 0a0a
comando faz parte do código fonte do $ testefor1 0000004
Shell, ou seja, em bom programês é um ArqDoDOS.txt1:confuso:incusu:
built-in), é muito mais poderoso que os logado:musexc:musicas:musinc: Isto é, mandei a variável (protegida
seus correlatos das outras linguagens. muslist:$ da interpretação do Shell pelas aspas)
Vamos entender a sua sintaxe, pri- para um dump hexadecimal (od -h). O
meiro em português e, depois, como Como você viu, o Shell transformou resultado pode ser interpretado com a
funciona pra valer. Olhe só: o asterisco (que odeia ser chamado tabela abaixo:
de asterístico) em uma lista de arqui-
para var em val1 val2 ... valn vos separados por espaços em branco. Tabela 1: Resultado do od -h
faça Quando o for viu aquela lista, disse:
cmd1 “Opa, listas separadas por espaços é Valor Hexadecimal Significado

cmd2 comigo mesmo!” 09 <TAB>

cmdn O bloco de comandos a ser executado 20 <ESPAÇO>


feito era somente o echo, que com a opção -n 0a <ENTER>
listou a variável $Arq seguida de dois-
Onde a variável var assume cada um pontos (:), sem saltar a linha. O cifrão
dos valores da lista val1 val2 ... valn e, ($) do fi nal da linha da execução é o O último 0a foi proveniente do
para cada um desses valores, executa o prompt, que permaneceu na mesma <ENTER> dado ao fi nal do comando.
bloco de comandos formado por cmd1, linha também em função da opção -n. Para melhorar a explicação, vamos ver
cmd2 e cmdn. Agora que já vimos o Outro exemplo simples (por enquanto): isso de outra forma:
significado da instrução em português,
vejamos a sintaxe correta: $ cat testefor2 $ echo “:$IFS:” | cat -vet
#!/bin/bash : ^I$
for var in val1 val2 ... valn # 2o. Programa didático para :$
do # entender o for
cmd1 for Palavra in Linux Magazine U No comando cat, a opção -e repre-
cmd2 do Brasil senta o <ENTER> como um cifrão ($)
cmdn do e a opção -t representa o <TAB> como
done echo $Palavra um ^I. Usei os dois-pontos (:) para mos-
done trar o início e o fi m do echo. E dessa
Vamos aos exemplos, para entender forma, pudemos notar que os três carac-
direito o funcionamento deste comando. E executando temos: teres estão presentes naquela variável.
Vamos escrever um script para listar Agora veja você: traduzindo, IFS sig-
todos os arquivos do diretório, separa- $ testefor2 nifica separador entre campos. Uma vez
dos por dois-pontos, mas antes veja isso: Linux entendido isso, eu posso afi rmar que o
Magazine comando for não usa apenas listas sepa-
$ echo * do radas por espaços em branco, mas sim
ArqDoDOS.txt1 confuso incusu Brasil pelo conteúdo da variável $IFS, cujo
logado musexc musicas musinc valor padrão são os caracteres que aca-
muslist Como você viu, esse exemplo é bamos de ver. Para comprovarmos isso,
tão bobo e simples como o anterior, vamos continuar mexendo em nossa
Isto é, o Shell viu o asterisco (*), mas serve para mostrar o comporta- CDTeca, escrevendo um script que
expandiu-o com o nome de todos os mento básico do for. Veja só a força do recebe o nome do artista como parâme-
arquivos do diretório e o comando echo comando: ainda estamos na primeira tro e lista as músicas que ele toca. Mas
jogou-os para a tela separados por espa- possibilidade de sintaxe e já estou mos- primeiramente vamos ver como está o
ços em branco. Visto isso, vamos resol- trando novas formas de usá-lo. Lá atrás nosso arquivo musicas:
ver o problema a que nos propusemos: eu havia falado que o for usava listas
separadas por espaços em branco, mas $ cat musicas
$ cat testefor1 isso é uma meia-verdade, só para facili- album 1^Artista1~Musica1: U
#!/bin/bash tar a compreensão. Na verdade, as listas Artista2~Musica2
# 1o. Programa didático para não são obrigatoriamente separadas por album 2^Artista3~Musica3: U
# entender o for espaços. Mas antes de prosseguir, pre- Artista4~Musica4
for Arq in * ciso te mostrar como se comporta uma album 3^Artista5~Musica5: U
do variável do sistema chamada de IFS, ou Artista6~Musica6
echo -n $Arq: Inter Field Separator Veja no exemplo a album 4^Artista7~Musica7: U
done seguir seu conteúdo: Artista1~Musica3

�����������������������������
90 edição 05 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER

album 5^Artista9~Musica9: U o $1 seria Perereca e o resto desse lindo $ listartista Artista1


Artista10~Musica10 nome seria ignorado na pesquisa. Musica1
Para que isso não ocorra, eu deveria Musica3
Em cima desse “leiaute” desenvolve- passar o nome do artista entre aspas
mos o script a seguir: (”) ou trocar $1 por $* (que representa Veja uma segunda sintaxe para o for:
todos os parâmetros passados), que é
$ cat listartista a melhor solução, mas nesse caso eu for var
#!/bin/bash teria que modificar a crítica dos parâ- do
# Dado um artista, mostra as metros e o grep. A nova versão não cmd1
# suas músicas seria se eu passei um parâmetro, mas cmd2
if [ $# -ne 1 ] sim se passei pelo menos um parâ- cmdn
then metro. Quanto ao grep, veja só o que done
echo Você deveria ter U aconteceria após a substituição do $*
passado um parâmetro pelos parâmetros: Ué, sem o in, como ele vai saber que
exit 1 valor assumir? Pois é, né? Esta constru-
fi echo “$ArtMus” | grep perereca U ção, à primeira vista, parece esquisita,
IFS=” & peteleca mas é bastante simples. Neste caso, var
:” assumirá um a um cada parâmetro pas-
for ArtMus in $(cut -f2 -d^ U Isso gera um erro. O correto é: sado para o programa. Como exemplo
musicas) para entender melhor, vamos fazer um
do echo “$ArtMus” | grep -i U script que receba como parâmetro um
echo “$ArtMus” | grep $1 && U “perereca & peteleca” monte de músicas e liste seus autores:
echo $ArtMus | cut -f2 -d~
done Aqui adicionamos a opção -i para $ cat listamusica
que a pesquisa ignorasse maiúsculas e #!/bin/bash
O script, como sempre, começa tes- minúsculas. As aspas foram inseridas # Recebe parte dos nomes de
tando se os parâmetros foram passados para que o nome do artista fosse visto # músicas como parâmetro e
corretamente, em seguida o IFS foi confi- como uma só cadeia de caracteres. # lista os intérpretes. Se o
gurado para <ENTER> e dois-pontos (:) Falta consertar o erro dele ter listado o # nome for composto, deve
(como demonstram as aspas em linhas Artista10. O melhor é dizer ao grep que a # ser passado entre aspas.
diferentes), porque é ele quem separa os cadeia de caracteres está no início (^) de # ex. “Eu não sou cachorro não”
blocos Artistan~Musicam. Desta forma, $ArtMus e que logo após vem um til (~). É # “Churrasquinho de Mãe”
a variável $ArtMus irá receber cada um preciso redirecionar a saída do grep para / #
desses blocos do arquivo (repare que o dev/null para que os blocos não sejam lis- if [ $# -eq 0 ]
for já recebe os registros sem o álbum tados. Veja a nova cara do programa: then
em virtude do cut na sua linha). Caso echo Uso: $0 musica1 U
encontre o parâmetro ($1) no bloco, o $ cat listartista [musica2] ... [musican]
segundo cut listará somente o nome da #!/bin/bash exit 1
música. Vamos executar o programa: # Dado um artista, mostra as fi
# suas musicas IFS=”
$ listartista Artista1 # Versao 2 :”
Artista1~Musica1 if [ $# -eq 0 ] for Musica
Musica1 then do
Artista1~Musica3 echo Voce deveria ter U echo $Musica
Musica3 passado pelo menos um parametro Str=$(grep -i “$Musica” U
Artista10~Musica10 exit 1 musicas) ||
Musica10 fi {
IFS=” echo “ Não U
Êpa! Aconteceram duas coisas inde- :” encontrada”
sejáveis: os blocos também foram lista- for ArtMus in $(cut -f2 -d^ U continue
dos, e a Musica10 idem. Além do mais, musicas) }
o nosso arquivo de músicas está muito do for ArtMus in $(echo “$Str” U
simples: na vida real, tanto a música echo “$ArtMus” | grep -i U | cut -f2 -d^)
quanto o artista têm mais de um nome. “^$*~” > /dev/null && echo U do
Suponha que o artista fosse uma dupla $ArtMus | cut -f2 -d~ echo “ $ArtMus” | U
sertaneja chamada Perereca & Peteleca done grep -i “$Musica” | cut -f1 -d~
(não gosto nem de dar a idéia com receio done
que isso se torne realidade). Nesse caso, O resultado é: done

�����������������������������
www.linuxmagazine.com.br edição 05 91
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim

Da mesma forma que os outros, come- Ou na forma mais completa do seq: Repare que o incremento saiu do
çamos o exercício com uma crítica sobre corpo do for e passou para o bloco de
os parâmetros recebidos, em seguida for i in $(seq 0 3 9) código; repare também que, quando
fizemos um for em que a variável do usei o let, não foi necessário iniciali-
$Musica receberá cada um dos parâme- echo -n “$i “ zar a variável $i. Veja só os comandos a
tros passados, colocando em $Str todos done seguir, digitados diretamente no prompt,
os álbuns que contêm as músicas dese- 0 3 6 9 para demonstrar o que acabo de falar:
jadas. Em seguida, o outro for pega cada
bloco Artista~Musica nos registros que A outra forma de fazer isso é com $ echo $j
estão em $Str e lista cada artista que uma sintaxe muito semelhante ao for da $ let j++
toca aquela música. Vamos executar o linguagem C, como vemos a seguir: $ echo $j
programa para ver se funciona mesmo: 1
for ((var=ini; cond; incr))
$ listamusica musica3 Musica4 U do Ou seja, a variável $j sequer existia e
“Egüinha Pocotó” cmd1 no primeiro let assumiu o valor 0 (zero)
musica3 cmd2 para, após o incremento, ter o valor 1.
Artista3 cmdn Veja só como as coisas ficam simples:
Artista1 done
Musica4 for arq in *
Artista4 Onde var=ini significa que a variá- do
Egüinha Pocotó vel var começará de um valor inicial let i++
Não encontrada ini; cond significa que o loop ou laço for echo “$i -> $Arq”
será executado enquanto var não atingir done
A listagem ficou feinha porque ainda a condição cond e incr significa o incre- 1 -> ArqDoDOS.txt1
não sabemos formatar a saída; mas mento que a variável var sofrerá a cada 2 -> confuso
qualquer dia desses, quando você sou- passada do loop. Vamos aos exemplos: 3 -> incusu
ber posicionar o cursor, trabalhar com 4 -> listamusica
cores etc., faremos esse programa nova- for ((i=1; i<=9; i++)) 5 -> listartista
mente usando todas essas perfumarias. do 6 -> logado
A esta altura dos acontecimentos, echo -n “$i “ 7 -> musexc
você deve estar se perguntando: “E done 8 -> musicas
aquele for tradicional das outras lingua- 1 2 3 4 5 6 7 8 9 9 -> musinc
gens em que ele sai contando a partir 10 -> muslist
de um número, com um determinado A variável i partiu do valor inicial 1, o 11 -> testefor1
incremento, até alcançar uma condi- bloco de código (aqui somente o echo) 12 -> testefor2
ção?”. E é aí que eu te respondo: “Eu será executado enquanto i for menor ou
não te disse que o nosso for é mais por- igual (<=) a 9 e o incremento de i será – Pois é amigo, tenho certeza que você
reta que o dos outros?” Para fazer isso, de 1 a cada passada do loop. já tomou um xarope do comando
existem duas formas. Com a primeira Repare que no for propriamente dito for. Por hoje chega, na próxima vez
sintaxe que vimos, como no exemplo: (e não no bloco de código) não coloquei em que nos encontrarmos falaremos
um cifrão ($) antes do i e a notação sobre outras instruções de loop, mas
for i in $(seq 9) para incrementar (i++) é diferente do eu gostaria que até lá você fi zesse um
do que vimos até agora. O uso de parênte- pequeno script para contar a quanti-
echo -n “$i “ ses duplos (assim como o comando let) dade de palavras de um arquivo texto,
done chama o interpretador aritmético do cujo nome seria recebido como parâ-
1 2 3 4 5 6 7 8 9 Shell, que é mais tolerante. metro. Essa contagem tem que ser
Só para mostrar como o let funciona feita com o comando for, para se habi-
A variável i assumiu os valores intei- e a versatilidade do for, vamos fazer a tuar ao seu uso. Não vale usar o wc -w.
ros entre 1 a 9 gerados pelo comando mesma coisa, mas omitindo a última Aê Chico! Traz a saideira! ■
seq e a opção -n do echo foi usada para parte do escopo do for, passando-a para
não saltar uma linha a cada número lis- o bloco de código: Julio Cezar Neves é Analista de
SOBRE O AUTOR

tado. Ainda usando o for com seq: Suporte de Sistemas desde 1969 e tra-
for ((; i<=9;)) balha com Unix desde 1980, quando
for i in $(seq 4 9) do participou do desenvolvimento do
do let i++ SOX, um sistema operacional similar
echo -n “$i “ echo -n “$i “ ao Unix produzido pela Cobra Com-
putadores. Pode ser contatado no
done done
e-mail julio.neves@gmail.com
4 5 6 7 8 9 1 2 3 4 5 6 7 8 9

�����������������������������
92 edição 05 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim

Curso de Shell Script

Papo de
Botequim VI
Blocos de código e laços (ou loops, como preferem alguns)
Dave Hamilton - www.sxc.hu

são o tema do mês em mais uma lição de nosso curso de Shell

Script. Garçom, salta uma boa redondinha, que tô a fim de

refrescar o pensamento! POR JULIO CEZAR NEVES

F ala, cara! E aí, já tá sabendo tudo


do comando for? Eu te deixei um
exercício para treinar, se não me
engano era para contar a quantidade de
palavras de um arquivo... Você fez?
Ou seja, o programa começa, como
sempre, verificando se a passagem de
parâmetros foi correta; em seguida o
comando for se incumbe de pegar cada
uma das palavras (lembre-se que o $IFS
Uma vez que chegamos neste ponto,
creio ser interessante citar que o Shell
trabalha com o conceito de “Expansão
Aritmética” (Arithmetic Expansion),
que é acionada por uma construção da
– Claro! Tô empolgadão com essa lin- padrão é branco, TAB e ENTER, que é forma $((expressão)) ou let expressão.
guagem! Eu fi z da forma que você exatamente o que desejamos para sepa- No último loop for usei a expansão
pediu, olha só... rar as palavras), incrementando a vari- aritmética das duas formas, mas não
– Êpa! Peraí que eu tô sequinho pra ável $Cont. Vamos relembrar como é o podemos seguir adiante sem saber que
tomar um chope. Aê Chico, traz dois arquivo ArqDoDOS.txt. a expressão pode ser uma das listadas
por favor. Um sem colarinho! na tabela 1.
– Como eu ia dizendo, olha como eu fi z. $ cat ArqDoDOS.txt Mas você pensa que o papo de loop
É muito fácil... Este arquivo (ou laço) se encerra no comando for?
foi gerado pelo Ledo engano, amigo, vamos a partir de
$ cat contpal.sh DOS/Rwin e foi agora ver mais dois comandos.
#!/bin/bash baixado por um
# Script meramente pedagógico ftp mal feito. O comando while
# cuja função é contar a Todos os programadores conhecem este
# quantidade de palavras de Agora vamos testar o programa pas- comando, porque é comum a todas as
# um arquivo. Supõe-se que as sando esse arquivo como parâmetro: linguagens. Nelas, o que normalmente
# palavras estão separadas ocorre é que um bloco de comandos é
# entre si por espaços, <TAB> $ contpal.sh ArqDoDOS.txt executado, enquanto (enquanto, em
# ou <ENTER>. O arquivo ArqDoDOS.txt tem 14 inglês, é “while”) uma determinada
if [ $# -ne 1 ] palavras. condição for verdadeira.
then
echo uso: $0 /caminho/do/ U Funcionou legal! Tabela 1: Expressões no Shell
arquivo Se você se lembra, Expressão Resultado
exit 2 em nossa última id++ id-- pós-incremento e pós-decremento de variáveis
fi aula mostramos o ++id --id pré-incremento e pré-decremento de variáveis
Cont=0 loop for a seguir: ** exponenciação
for Palavra in $(cat $1) */% multiplicação, divisão, resto da divisão (módulo)
do for ((; i<=9;)) +- adição, subtração

Cont=$((Cont+1)) do <= >= < > comparação


== != igualdade, desigualdade
done let i++
&& E lógico
echo O arquivo $1 tem $Cont U echo -n "$i "
|| OU lógico
palavras. done

�����������������������������
86 edição 06 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER

Pois bem, isso é o que acontece nas $ logaute.sh $cat monbg.sh


linguagens caretas! Em programação xefe pts/0 Jan 4 08:46 U #!/bin/bash
Shell, o bloco de comandos é executado (10.2.4.144) # Executa e monitora um
enquanto um comando for verdadeiro. xefe pts/0 Jan 4 08:46 U # processo em background
E é claro, se quiser testar uma condi- (10.2.4.144) $1 & # Coloca em backgroud
ção, use o comando while junto com o ... while ps | grep -q $!
comando test, exatamente como você xefe pts/0 Jan 4 08:46 U do
aprendeu a fazer no if, lembra? Então a (10.2.4.144) sleep 5
sintaxe do comando fica assim: done
Isto é, a cada 30 segundos a saída do echo Fim do Processo $1
while comando comando grep seria enviada para a tela,
do o que não é legal, já que poluiria a tela Esse script é bastante similar ao ante-
cmd1 do meu micro e a mensagem tão espe- rior, mas tem uns macetes a mais, veja
cmd2 rada poderia passar despercebida. Para só: ele tem que ser executado em back-
... evitar isso, já sabemos que a saída do ground para não prender o prompt mas
cmdn pipeline tem que ser redirecionada para o $! será o do programa passado como
done o dispositivo /dev/null. parâmetro, já que ele foi colocado em
background após o monbg.sh propria-
e dessa forma, o bloco formado pelas $ cat logaute.sh mente dito. Repare também na opção -q
instruções cmd1, cmd2,... e cmdn é exe- #!/bin/bash (quiet) do grep, que serve para fazê-lo
cutado enquanto a execução da instru- # Espero que a Xuxa não tenha “trabalhar em silêncio”. O mesmo resul-
ção comando for bem sucedida. # copyright de xefe e xato :) tado poderia ser obtido com a linha:
Suponha a seguinte cena: tinha while who | grep xefe > /dev/null while ps | grep $! > /dev/null, como nos
uma tremenda gata me esperando e do exemplos que vimos até agora.
eu estava preso no trabalho sem poder sleep 30 Vamos melhorar o nosso velho
sair porque o meu chefe, que é um pé done musinc, nosso programa para incluir
no saco (aliás chefe-chato é uma redun- echo O xato se mandou, não U registros no arquivo musicas, mas
dância, né?), ainda estava na sala dele, hesite, dê exit e vá a luta antes preciso te ensinar a pegar
que fica bem na minha passagem para a um dado da tela, e já vou avisando:
rua. Ele começou a ficar cabreiro depois Agora quero montar um script que só vou dar uma pequena dica do
da quinta vez que passei pela sua porta receba o nome (e eventuais parâme- comando read (que é quem pega o
e olhei para ver se já havia ido embora. tros) de um programa que será execu- dado da tela), que seja o suficiente
Então voltei para a minha mesa e fi z, no tado em background e que me informe para resolver este nosso problema.
servidor, um script assim: do seu término. Mas, para você enten- Em uma outra rodada de chope vou
der este exemplo, primeiro tenho de te ensinar tudo sobre o assunto,
$ cat logaute.sh mostrar uma nova variável do sistema. inclusive como formatar tela, mas
#!/bin/bash Veja estes comandos executados direta- hoje estamos falando sobre loops. A
# Espero que a Xuxa não tenha mente no prompt: sintaxe do comando read que nos
# copyright de xefe e xato :) interessa por hoje é a seguinte:
while who | grep xefe $ sleep 10&
do [1] 16317 $ read -p "prompt de leitura" var
sleep 30 $ echo $!
done 16317 Onde “prompt de leitura” é o texto
echo O xato se mandou, não U [1]+ Done sleep 10 que você quer que apareça escrito na
hesite, dê exit e vá à luta $ echo $! tela. Quando o operador teclar tal dado,
16317 ele será armazenado na variável var.
Neste scriptzinho, o comando while Por exemplo:
testa o pipeline composto pelos coman- Isto é, criei um processo em back-
dos who e grep, que será verdadeiro ground que dorme por 10 segundos, $ read -p "Título do Álbum: " Tit
enquanto o grep localizar a palavra somente para mostrar que a variável $!
xefe na saída do comando who. Desta guarda o PID (Process ID) do último pro- Bem, uma vez entendido isso, vamos
forma, o script dormirá por 30 segundos cesso em background. Mas observe a à especificação do nosso problema:
enquanto o chefe estiver logado (Argh!). listagem e repare, após a linha do Done, faremos um programa que inicialmente
Assim que ele se desconectar do servi- que a variável reteve o valor mesmo lerá o nome do álbum e em seguida fará
dor, o fluxo do script sairá do loop e te após o término desse processo. um loop de leitura, pegando o nome da
mostrará a tão ansiada mensagem de Bem, sabendo isso, já fica mais fácil música e o artista. Esse loop termina
liberdade. Mas quando executei o script, monitorar qualquer processo em back- quando for informada uma música com
adivinha o que aconteceu? ground. Veja só como: nome vazio, isto é, quando o operador

�����������������������������
www.linuxmagazine.com.br edição 06 87
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim

Dica until comando $cat chegada.sh


Leitura de arquivo significa ler um a um do #!/bin/bash
todos os registros, o que é sempre uma cmd1 until who | grep julio
operação lenta. Fique atento para não usar cmd2 do
o while quando for desnecessário. O Shell ... sleep 30
tem ferramentas como o sed e a família cmdn done
grep, que vasculham arquivos de forma done echo $(date "+ Em %d/%m às U
otimizada sem que seja necessário o uso do %H:%Mh") > relapso.log
while para fazê-lo registro a registro.
e dessa forma o bloco de comandos for-
mado pelas instruções cmd1, cmd2,... e Olha que safado! O cara estava mon-
der um simples <ENTER>. Para facili- cmdn é executado até que a execução da tando um log com os meus horários de
tar a vida do operador, vamos oferecer instrução comando seja bem sucedida. chegada, e ainda por cima chamou o
como default o mesmo nome do artista Como eu te disse, while e until funcio- arquivo de relapso.log! O que será que
da música anterior (já que é normal que nam de forma antagônica, e isso é muito ele quis dizer com isso?
o álbum seja todo do mesmo artista) até fácil de demonstrar: em uma guerra, Nesse script, o pipeline who | grep
que ele deseje alterá-lo. Veja na listagem sempre que se inventa uma arma, o julio, será bem sucedido somente
1 como ficou o programa. inimigo busca uma solução para neu- quando julio for encontrado na saída
Nosso exemplo começa com a lei- tralizá-la. Foi baseado nesse principio do comando who, isto é, quando eu
tura do título do álbum. Caso ele não belicoso que meu chefe desenvolveu, no me “logar” no servidor. Até que isso
seja informado, terminamos a execu- mesmo servidor em que eu executava o aconteça, o comando sleep, que forma
ção do programa. Em seguida um grep logaute.sh, um script para controlar o o bloco de instruções do until, colocará
procura, no início (^) de cada registro meu horário de chegada. o programa em espera por 30 segun-
de músicas, o título informado seguido Um dia tivemos um problema na rede. dos. Quando esse loop encerrar-se, será
do separador (^) (que está precedido de Ele me pediu para dar uma olhada no enviada uma mensagem para o arquivo
uma contrabarra [\] para protegê-lo da micro dele e me deixou sozinho na sala. relapso.log. Supondo que no dia 20/01
interpretação do Shell). Resolvi bisbilhotar os arquivos – guerra eu me “loguei” às 11:23 horas, a mensa-
Para ler os nomes dos artistas e as é guerra – e veja só o que descobri: gem seria a seguinte:
músicas do álbum, foi montado um loop
while simples, cujo único destaque é o Listagem 1
fato de ele armazenar o nome do intér-
$ cat musinc.sh
prete da música anterior na variável
#!/bin/bash
$oArt, que só terá o seu conteúdo alte-
# Cadastra CDs (versao 4)
rado quando algum dado for informado
#
para a variável $Art, isto é, quando não
clear
for teclado um simples ENTER para
read -p "Título do Álbum: " Tit
manter o artista anterior.
[ "$Tit" ] || exit 1 # Fim da execução se título vazio
O que foi visto até agora sobre o while
if grep "^$Tit\^" musicas > /dev/null
foi muito pouco. Esse comando é muito
then
utilizado, principalmente para leitura
echo "Este álbum já está cadastrado"
de arquivos, porém ainda nos falta
exit 1
bagagem para prosseguir. Depois que

aprendermos mais sobre isso, veremos
Reg="$Tit^"
essa instrução mais a fundo.
Cont=1

O comando until oArt=


while true
Este comando funciona de forma
do
idêntica ao while, porém ao contrário.
echo "Dados da trilha $Cont:"
Disse tudo mas não disse nada, né? É
read -p "Música: " Mus
o seguinte: ambos testam comandos;
[ "$Mus" ] || break # Sai se vazio
ambos possuem a mesma sintaxe e
read -p "Artista: $oArt // " Art
ambos atuam em loop; porém, o while
[ "$Art" ] && oArt="$Art" # Se vazio Art anterior
executa o bloco de instruções do loop
Reg="$Reg$oArt~$Mus:" # Montando registro
enquanto um comando for bem suce-
Cont=$((Cont + 1))
dido; já o until executa o bloco do loop
# A linha anterior tb poderia ser ((Cont++))
até que o comando seja bem sucedido.
done
Parece pouca coisa, mas a diferença é
echo "$Reg" >> musicas
fundamental. A sintaxe do comando é
sort musicas -o musicas
praticamente a mesma do while. Veja:

�����������������������������
88 edição 06 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER

Em 20/01 às 11:23h
Atalhos no
loop �� ��

Voltando à nossa CDteca, quando Nem sempre um �������� ��������


vamos cadastrar músicas seria ideal ciclo de programa,
que pudéssemos cadastrar diversos compreendido
CDs de uma vez só. Na última versão entre um do e um ����� ����� ����� ��������
do programa isso não ocorre: a cada CD done, sai pela porta
cadastrado o programa termina. Veja na da frente. Em algu-
�������� ��������
listagem 2 como melhorá-lo. mas oportunidades,
Nesta versão, um loop maior foi adi- temos que colocar
���� ����
cionado antes da leitura do título, que um comando que
só terminará quando a variável $Para aborte de forma
deixar de ser vazia. Caso o título do c ont rolada esse �������������������� �����������������������
álbum não seja informado, a variá- loop. De maneira Figura 1: A estrutura dos comandos break e continue, usados para contro-
vel $Para receberá um valor (coloquei inversa, algumas lar o fluxo de execução em loops.
1, mas poderia ter colocado qualquer vezes desejamos
coisa) para sair desse loop, terminando que o f lu xo de pectivamente os comandos break (que
o programa. No resto, o script é idênticoexecução do programa volte antes de já vimos rapidamente nos exemplos do
à versão anterior. chegar ao done. Para isso, temos res- comando while) e continue, que funcio-
nam da forma mostrada na figura 1.
Listagem 2 O que eu não havia dito anterior-
mente é que nas suas sintaxes genéricas
$ cat musinc.sh
eles aparecem da seguinte forma:
#!/bin/bash
# Cadastra CDs (versao 5)
break [qtd loop]
#
Para=
e também:
until [ "$Para" ]
do
continue [qtd loop]
clear
read -p "Título do Álbum: " Tit
Onde qtd loop representa a quanti-
if [ ! "$Tit" ] # Se titulo vazio...
dade dos loops mais internos sobre os
then
quais os comandos irão atuar. Seu valor
Para=1 # Liguei flag de saída
por default é 1.
else
Duvido que você nunca tenha apa-
if grep "^$Tit\^" musicas > /dev/null
gado um arquivo e logo após deu um
then
tabefe na testa se xingando porque
echo "Este álbum já está cadastrado"
não devia tê-lo removido. Pois é, na
exit 1
décima vez que fi z esta besteira, criei

um script para simular uma lixeira,
Reg="$Tit^"
isto é, quando mando remover um (ou
Cont=1
vários) arquivo(s), o programa “fi nge”
oArt=
que deletou, mas no duro o que ele fez
while [ "$Tit" ]
foi mandá-lo(s) para o diretório /tmp/
do
LoginName_do_usuario. Chamei esse
echo Dados da trilha $Cont:
programa de erreeme e no arquivo /etc/
read -p "Música: " Mus
profile coloquei a seguinte linha, que
[ "$Mus" ] || break # Sai se vazio
cria um “apelido” para ele:
read -p "Artista: $oArt // " Art
[ "$Art" ] && oArt="$Art" # Se vazio Art anterior
alias rm=erreeme
Reg="$Reg$oArt~$Mus:" # Montando registro
Cont=$((Cont + 1))
Veja o programa na listagem 3. Como
# A linha anterior tb poderia ser ((Cont++))
você pode ver, a maior parte do script
done
é formada por pequenas críticas aos
echo "$Reg" >> musicas
parâmetros informados, mas como o
sort musicas -o musicas
script pode ter recebido diversos arqui-

vos a remover, a cada arquivo que não
done
se encaixa dentro do especificado há

�����������������������������
www.linuxmagazine.com.br edição 06 89
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim

Listagem 3: erreeme.sh
$ cat erreeme.sh then
#!/bin/bash echo "$Arq nao existe."
# Erro=3
# Salvando cópia de um arquivo antes de removê-lo continue # Volta para o comando for

# Tem de ter um ou mais arquivos a remover
if [ $# -eq 0 ] # Cmd. dirname informa nome do dir de $Arq
then DirOrig=`dirname $Arq`
echo "Erro -> Uso: erreeme arq [arq] ... [arq]" # Verifica permissão de gravacao no diretório
echo "O uso de metacaracteres e’ permitido. Ex.U if [ ! -w $DirOrig ]
erreeme arq*" then
exit 1 echo "Sem permissão no diretorio de $Arq"
fi Erro=4
continue # Volta para o comando for
# Variável do sistema que contém o nome do usuário. fi
MeuDir="/tmp/$LOGNAME"
# Se não existir o meu diretório sob o /tmp... # Se estou "esvaziando a lixeira"...
if [ ! -d $MeuDir ] if [ "$DirOrig" = "$MeuDir" ]
then then
mkdir $MeuDir # Vou criá-lo echo "$Arq ficara sem copia de seguranca"
fi rm -i $Arq # Pergunta antes de remover
# Será que o usuário removeu?
# Se não posso gravar no diretório... [ -f $Arq ] || echo "$Arquivo removido"
if [ ! -w $MeuDir ] continue
then fi
echo "Impossivel salvar arquivos em $MeuDir. U
Mude as permissões..." # Guardo no fim do arquivo o seu diretório originalU
exit 2 para usá-lo em um script de undelete
fi cd $DirOrig
pwd >> $Arq
# Variável que indica o cod. de retorno do programa mv $Arq $MeuDir # Salvo e removo
Erro=0 echo "$Arq removido"
# Um for sem o in recebe os parametros passados done
for Arq
do # Passo eventual número do erro para o código
# Se este arquivo não existir... # de retorno
if [ ! -f $Arq ] exit $Erro

um continue, para que a seqüência volte faça-o em casa e me traga para dis- um email para julio.neves@gmail.
para o loop do for de forma a receber cutirmos no nosso próximo encontro com. Agora chega de papo que eu já
outros arquivos. aqui no boteco. estou de goela seca de tanto falar. Me
Quando você está no Windows (com – Poxa, mas nesse eu acho que vou dan- acompanha no próximo chope ou já
perdão da má palavra) e tenta remover çar, pois não sei nem como começar... vai sair correndo para fazer o script
aquele monte de lixo com nomes esqui- – Cara, este programa é como tudo que passei?
sitos como HD04TG.TMP, se der erro o que se faz em Shell: extrema- – Deixa eu pensar um pouco...
em um dos arquivos os outros não são mente fácil. É para ser feito em, no – Chico, traz mais um chope enquanto
removidos, não é? Então, o continue foi máximo, 10 linhas. Não se esqueça ele pensa!
usado para evitar que uma improprie- de que o arquivo está salvo em /tmp/
dade dessas ocorra, isto é, mesmo que $LOGNAME e que sua última linha é Julio Cezar Neves é Analista de
SOBRE O AUTOR

dê erro na remoção de um arquivo, o o diretório em que ele residia antes Suporte de Sistemas desde 1969 e tra-
programa continuará removendo os de ser “removido”. Também não se balha com Unix desde 1980, quando
outros que foram passados. esqueça de criticar se foi passado o participou do desenvolvimento do
– Eu acho que a esta altura você deve nome do arquivo a ser removido. SOX, um sistema operacional similar
estar curioso para ver o programa – É eu vou tentar, mas sei não... ao Unix produzido pela Cobra Com-
putadores. Pode ser contatado no
que restaura o arquivo removido, não – Tenha fé, irmão, eu tô te falando que
e-mail julio.neves@gmail.com
é? Pois então aí vai vai um desafio: é mole! Qualquer dúvida é só passar

�����������������������������
90 edição 06 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
Linux User Papo de botequim

Papo de Botequim
Curso de Shell Script

Parte VII

Dave Hamilton - www.sxc.hu


De pouco adianta ter acesso à informação se ela não puder ser apresentada
de forma atraente e que facilite a compreensão. O comando tput pode ser
usado por shell scripts para posicionar caracteres e criar todos os tipos de
efeito com o texto mostrado na tela. Garçom, solta uma geladinha!
por Julio Cezar Neves

C
umequié, rapaz! Derreteu os pen- – Peraí, deixa eu ver se entendi o que você – É, tô vendo que o bichinho do shell te
samentos para fazer o scriptzinho fez: você coloca na variável Dir a última pegou. Vamos ver como ler dados, mas
que eu te pedi? linha do arquivo a ser restaurado, em antes vou te mostrar um comando que
– É, eu realmente tive de colocar muita nosso caso /tmp/$LOGNAME/$1 (onde te dá todas as ferramentas para formatar
pensação na tela preta, mas acho que $LOGNAME é o nome do usuário logado, uma tela de entrada de dados.
fi nalmente consegui! Bem, pelo menos e $1 é o primeiro parâmetro que você
nos testes que fi z a coisa funcionou, passou ao script), já que foi lá que arma- O comando tput
mas você tem sempre que botar chifres zenamos o nome e caminho originais do O principal uso desse comando é o posi-
em cabeça de cachorro! arquivo antes de movê-lo para o diretório cionamento do cursor na tela. Alguns
– Não é bem assim. É que programar em (defi nido na variável Dir). O comando parâmetros podem não funcionar se o
Shell Script é muito fácil, mas o que é grep -v apaga essa linha, restaurando modelo de terminal defi nido pela vari-
realmente importante são as dicas e o arquivo ao estado original, e o manda ável de ambiente $TERM não suportá-los.
macetes que não são triviais. As cor- de volta pra onde ele veio. A última linha A tabela 1 apresenta apenas os principais
reções que faço são justamente para o apaga da “lixeira”. Sensacional! Impe- parâmetros e os efeitos resultantes, mas
mostrá-los. Mas vamos pedir dois cho- cável! Nenhum erro! Viu? Você já está existem muito mais deles. Para saber tudo
pes enquanto dou uma olhadela no teu pegando as manhas do shell! sobre o tput, veja a referência [1].
script lá na listagem 1. Aê Chico, traz – Então vamos lá, chega de lesco-lesco Vamos fazer um programa bem besta
dois chopes! E não se esqueça que um e blá-blá-blá, sobre o quê nós vamos e fácil para ilustrar melhor o uso desse
deles é sem colarinho! falar hoje? comando. É uma versão do famigerado
“Alô Mundo”, só que dessa vez a frase
Listagem 1 – restaura.sh será escrita no centro da tela e em vídeo
reverso. Depois disso, o cursor voltará para
01 #!/bin/bash
a posição original. Veja a listagem 2.
02 #
Como o programa já está todo comen-
03 # Restaura arquivos deletados via erreeme
tado, acho que a única linha que precisa de
04 #
explicação é a 8, onde criamos a variável
05
Coluna. O estranho ali é aquele número
06 if [ $# -eq 0 ]
9, que na verdade indica o tamanho da
07 then
cadeia de caracteres que vou escrever na
08 echo "Uso: $0 <Nome do arquivo a ser restaurado>"
tela. Dessa forma, este programa somente
09 exit 1
conseguiria centralizar cadeias de 9 carac-
10 fi
teres, mas veja isto:
11 # Pega nome do arquivo/diretório original na última linha
12 Dir='tail -1 /tmp/$LOGNAME/$1'
$ var=Papo
13 # O grep -v exclui a última linha e recria o arquivo com o diretório
$ echo ${#var}
14 # e nome originalmente usados
4
15 grep -v $Dir /tmp/$LOGNAME/$1 > $Dir/$1
$ var="Papo de Botequim"
16 # Remove o arquivo que já estava moribundo
$ echo ${#var}
17 rm /tmp/$LOGNAME/$1
16

86 abril 2005 edição 07


www.linuxmagazine.com.br
Linux User Papo de botequim

Tabela 1: Parâmetros do tput e a linha 12 (echo $1) passaria a ser:

Parâmetro Efeito echo $*


cup lin col CUrsor Position – Posiciona o cursor na linha lin e coluna col. A origem (0,0) fica no
canto superior esquerdo da tela. Lendo dados da tela
bold Coloca a tela em modo negrito Bem, a partir de agora vamos aprender
rev Coloca a tela em modo de vídeo reverso tudo sobre leitura. Só não posso ensinar
a ler cartas e búzios porque se soubesse
smso Idêntico ao anterior
estaria rico, num pub Londrino tomando
smul Sublinha os caracteres um scotch e não em um boteco tomando
blink Deixa os caracteres piscando chope. Mas vamos em frente.
sgr0 Restaura a tela a seu modo normal Da última vez em que nos encontramos
reset Limpa o terminal e restaura suas definições de acordo com terminfo, ou seja, o ter- eu dei uma palhinha sobre o comando
minal volta ao comportamento padrão definido pela variável de ambiente $TERM read. Antes de entrarmos em detalhes,
veja só isso:
lines Informa a quantidade de linhas que compõem a tela
cols Informa a quantidade de colunas que compõem a tela $ read var1 var2 var3
el Erase Line – Apaga a linha a partir da posição do cursor Papo de Botequim
ed Erase Display – Apaga a tela a partir da posição do cursor $ echo $var1
Papo
il n Insert Lines – Insere n linhas a partir da posição do cursor
$ echo $var2
dl n Delete Lines – Remove n linhas a partir da posição do cursor
de
dch n Delete CHaracters – Apaga n caracteres a partir da posição do cursor $ echo $var3
sc Save Cursor position – Salva a posição do cursor Botequim
rc Restore Cursor position – Coloca o cursor na posição marcada pelo último sc $ read var1 var2
Papo de Botequim
$ echo $var1
Ahhh, melhorou! Então agora sabemos essa construção devolve o número de Papo
que a construção ${#variavel} devolve caracteres do primeiro parâmetro pas- $ echo $var2
a quantidade de caracteres da variável. sado para o programa. Se o parâmetro de Botequim
Assim sendo, vamos otimizar o nosso tivesse espaços em branco, seria preciso
programa para que ele escreva em vídeo colocá-lo entre aspas, senão o $1 leva- Como você viu, o read recebe uma
reverso, no centro da tela (e indepen- ria em conta somente o pedaço antes lista de parâmetros separada por espa-
dente do número de caracteres) a cadeia do primeiro espaço. Para evitar este ços em branco e coloca cada item dessa
de caracteres passada como parâmetro e aborrecimento, é só substituir o $1 por lista em uma variável. Se a quantidade
depois retorne o cursor à posição em que $*, que como sabemos é o conjunto de de variáveis for menor que a quantidade
estava antes da execução do script. Veja todos os parâmetros. Então a linha 8 de itens, a última variável recebe o res-
o resultado na listagem 3. ficaria assim: tante deles. Eu disse lista separada por
Este script é igual ao anterior, só que espaços em branco, mas agora que você
trocamos o valor fixo na variável Coluna # Centralizando a mensagem na tela já conhece tudo sobre o $IFS (Inter Field
(9) por ${#1}, onde esse 1 é $1, ou seja, Coluna=`$(((Colunas - ${#*}) / 2))` Separator – Separador entre campos), que

Listagem 2: alo.sh Listagem 3: alo.sh melhorado


01 #!/bin/bash 01 #!/bin/bash
02 # Script bobo para testar 02 # Script bobo para testar
03 # o comando tput (versao 1) 03 # o comando tput (versão 2.0)
04 04
05 Colunas=`tput cols` # Salva a quantidade de colunas na tela 05 Colunas=`tput cols` # Salva a quantidade de colunas na tela
06 Linhas=`tput lines` # Salva a quantidade linhas na tela 06 Linhas=`tput lines` # Salva a quantidade de linhas na tela
07 Linha=$((Linhas / 2)) # Qual é a linha central da tela? 07 Linha=$((Linhas / 2)) # Qual é a linha central da tela?
08 Coluna=$(((Colunas - 9) / 2)) # Centraliza a mensagem na tela 08 Coluna=$(((Colunas - ${#1}) / 2)) # Centraliza a mensagem na tela
09 tput sc # Salva a posição do cursor 09 tput sc # Salva a posicao do cursor
10 tput cup $Linha $Coluna # Posiciona o cursor antes de escrever 10 tput cup $Linha $Coluna # Posiciona o cursor antes de escrever
11 tput rev # Vídeo reverso 11 tput rev # Video reverso
12 echo Alô Mundo 12 echo $1
13 tput sgr0 # Restaura o vídeo ao normal 13 tput sgr0 # Restaura o vídeo ao normal
14 tput rc # Restaura o cursor à posição original 14 tput rc # Devolve o cursor à posição original

88 abril 2005 edição 07


www.linuxmagazine.com.br
Papo de botequim Linux User

eu te apresentei quando falávamos do para que o \n fosse entendido como $ read -t2 -p "Digite seu nome completo: U
comando for, será que ainda acredita uma quebra de linha (new line) e não " Nom || echo 'Eita moleza!'
nisso? Vamos testar: como um literal. Sob o Bash exis- Digite seu nome completo: Eita moleza!
tem diversas opções do read que ser- $ echo $Nom
$ oIFS="$IFS" vem para facilitar a sua vida. Veja
$ IFS=: a tabela 2. O exemplo acima foi uma brincadeira,
$ read var1 var2 var3 E agora direto aos exemplos curtos pois eu só tinha 2 segundos para digitar o
Papo de Botequim para demonstrar estas opções. Para ler meu nome completo e mal tive tempo de
$ echo $var1 um campo “Matrícula”: teclar um J (aquele colado no Eita), mas
Papo de Botequim ele serviu para mostrar duas coisas:
$ echo $var2 # -n não salta linha P 1) O comando após o par de barras verti-
$ echo $var3 $ echo -n "Matricula: "; read Mat cais (o ou – or – lógico, lembra-se?) será
$ read var1 var2 var3 Matricula: 12345 executado caso a digitação não tenha
Papo:de:Botequim $ echo $Mat sido concluída no tempo estipulado;
$ echo $var1 12345 P 2) A variável Nom permaneceu vazia. Ela
Papo só receberá um valor quando o ENTER
$ echo $var2 Podemos simplificar as coisas usando for teclado.
de a opção -p:
$ echo $var3 $ read -sp “Senha: “
Botequim $ read -p "Matricula: " Mat Senha: $ echo $REPLY
$ IFS="$oIFS" Matricula: 12345 segredo :)
$ echo $Mat
Viu? eu estava furado! O read lê uma 12345 Aproveitei um erro no exemplo ante-
lista, assim como o for, separada pelos rior para mostrar um macete. Quando
caracteres da variável $IFS. Veja como E podemos ler apenas uma quantidade escrevi a primeira linha, esqueci de colo-
isso pode facilitar a sua vida: pré-determinada de caracteres: car o nome da variável que iria receber a
senha e só notei isso quando ia escrevê-
$ grep julio /etc/passwd $ read -n5 -p"CEP: " Num ; read -n3 U la. Felizmente a variável $REPLY do Bash
julio:x:500:544:Julio C. Neves - 7070:U -p- Compl contém a última seqüência de caracteres
/home/julio:/bin/bash CEP: 12345-678$ digitada – e me aproveitei disso para não
$ oIFS="$IFS" # Salva o IFS antigo. $ echo $Num perder a viagem. Teste você mesmo o que
$ IFS=: 12345 acabei de fazer.
$ grep julio /etc/passwd | read lname U $ echo $Compl O exemplo que dei, na verdade, era para
lixo uid gid coment home shell 678 mostrar que a opção -s impede que o que
$ echo -e "$lname\n$uid\n$gid\n$comentU está sendo digitado seja mostrado na tela.
\n$home\n$shell" No exemplo acima executamos duas Como no exemplo anterior, a falta do new
julio vezes o comando read: um para a pri- line fez com que o prompt de comando
500 meira parte do CEP e outra para o seu ($) permanecesse na mesma linha.
544 complemento, deste modo formatando Agora que sabemos ler da tela, vejamos
Julio C. Neves - 7070 a entrada de dados. O cifrão ($) logo como se lêem os dados dos arquivos.
/home/julio após o último algarismo digitado é
/bin/bash necessário porque o read não inclui Lendo arquivos
$ IFS="$oIFS" # Restaura o IFS por padrão um caractere new line Como eu já havia lhe dito, e você deve se
implícito, como o echo. lembrar, o while testa um comando e exe-
Como você viu, a saída do grep foi Para ler só durante um determinado cuta um bloco de instruções enquanto esse
redirecionada para o comando read, limite de tempo (também conhecido comando for bem sucedido. Ora, quando
que leu todos os campos de uma só como time out): você está lendo um arquivo para o qual
tacada. A opção -e do echo foi usada você tem permissão de leitura, o read só
será mal sucedido quando alcançar o EOF
Tabela 2: Opções do read (End Of File – Fim do Arquivo). Portanto,
podemos ler um arquivo de duas maneiras.
Opção Ação A primeira é redirecionando a entrada do
-p prompt Escreve “prompt” na tela antes de fazer a leitura arquivo para o bloco while, assim:
-n num Lê até num caracteres
while read Linha
-t seg Espera seg segundos para que a leitura seja concluída do
-s Não exibe na tela os caracteres digitados. echo $Linha
done < arquivo

abril 2005 edição 07 89


www.linuxmagazine.com.br
Linux User Papo de botequim

A segunda é redirecionando a saída Como você viu, o script lista suas pró- que vou mostrar com um exemplo prático.
de um cat para o while, da seguinte prias linhas com um sinal de menos (-) Suponha que você queira listar um arquivo
maneira: antes e outro depois de cada uma e, no e quer que a cada dez registros essa listagem
final, exibe o conteúdo da variável $Ultimo. pare para que o operador possa ler o con-
cat arquivo | Repare, no entanto, que o conteúdo dessa teúdo da tela, e que ela só continue depois
while read Linha variável permanece vazio. Ué, será que de o operador pressionar qualquer tecla.
do a variável não foi atualizada? Foi, e isso Para não gastar papel (da Linux Magazine),
echo $Linha pode ser comprovado porque a linha echo vou fazer essa listagem na horizontal. Meu
done "-$Ultimo-" lista corretamente as linhas. arquivo (numeros) tem 30 registros com
Então por que isso aconteceu? números seqüenciais. Veja:
Cada um dos processos tem suas van- Como eu disse, o bloco de instruções
tagens e desvantagens. O primeiro é mais redirecionado pelo pipe (|) é executado $ seq 30 > numeros
rápido e não necessita de um subshell em um subshell e, lá, as variáveis são $ cat 10porpag.sh
para assisti-lo mas, em contrapartida, o atualizadas. Quando esse subshell ter- #!/bin/bash
redirecionamento fica pouco visível em mina, as atualizações das variáveis vão # Programa de teste para escrever
um bloco de instruções grande, o que para as profundezas do inferno junto com # 10 linhas e parar para ler
por vezes prejudica a visualização do ele. Repare que vou fazer uma pequena # Versão 1
código. O segundo processo traz a van- mudança no script, passando o arquivo while read Num
tagem de que, como o nome do arquivo por redirecionamento de entrada (<), e do
está antes do while, a visualização do as coisas passarão a funcionar na mais let ContLin++ # Contando...
código é mais fácil. Entretanto, o Pipe perfeita ordem: # -n para não saltar linha
(|) chama um subshell para interpretá-lo, echo -n "$Num "
tornando o processo mais lento e pesado. $ cat redirread.sh ((ContLin % 10)) > /dev/null || read
Para ilustrar o que foi dito, veja os exem- #!/bin/bash done < numeros
plos a seguir: # redirread.sh
# Exemplo de read passando o arquivo Na tentativa de fazer um programa
$ cat readpipe.sh # por um pipe. genérico criamos a variável $ContLin (na
#!/bin/bash Ultimo="(vazio)" vida real, os registros não são somente
# readpipe.sh # Passa o script ($0) para o while números seqüenciais) e, quando testamos
# Exemplo de read passando um arquivo while read Linha se o resto da divisão era zero, mandamos
# por um pipe. do a saída para /dev/null, pra que ela não
Ultimo="(vazio)" Ultimo="$Linha" apareça na tela. Mas quando fui executar
# Passa o script ($0) para o while echo "-$Ultimo-" o programa descobri a seguinte zebra:
cat $0 | while read Linha done < $0
do echo "Acabou, Último=:$Ultimo:" $ 10porpag.sh
Ultimo="$Linha" 1 2 3 4 5 6 7 8 9 10 12 13 14 15 16 17 U
echo "-$Ultimo-" Veja como ele roda perfeitamente: 18 19 20 21 23 24 25 26 27 28 29 30
done
echo "Acabou, Último=:$Ultimo:" $ redirread.sh Repare que faltou o número 11 e a lista-
-#!/bin/bash- gem não parou no read. Toda a entrada do
Vamos ver o resultado de sua execução: -# redirread.sh- loop estava redirecionada para o arquivo
-# Exemplo de read passando o arquivo numeros e a leitura foi feita em cima desse
$ readpipe.sh -# por um pipe.- arquivo, perdendo o número 11. Vamos
-#!/bin/bash- -- mostrar como deveria ficar o código para
-# readpipe.sh- -Ultimo="(vazio)"- que ele passe a funcionar a contento:
-# Exemplo de read passando um arquivo -while read Linha-
-# por um pipe.- -do- $ cat 10porpag.sh
-- -Ultimo="$Linha"- #!/bin/bash
-Ultimo="(vazio)"- -echo "-$Ultimo-"- # Programa de teste para escrever
-# Passa o script ($0) para o while- -# Passa o script ($0) para o while- # 10 linhas e parar para ler - Versão 2
-cat $0 | - -done < $0- while read Num
-while read Linha- -echo "Acabou, Último=:$Ultimo:"- do
-do- Acabou, Último=:echo "Acabou,U let ContLin++ # Contando...
-Ultimo="$Linha"- Último=:$Ultimo:": # -n para não saltar linha
-echo "-$Ultimo-"- echo -n "$Num "
-done- Bem, amigos da Rede Shell, para finali- ((ContLin % 10)) > /dev/null || read U
-echo "Acabou, Último=:$Ultimo:"- zar a aula sobre o comando read só falta < /dev/tty
Acabou, Último=:(vazio): mais um pequeno e importante macete done < numeros

90 abril 2005 edição 07


www.linuxmagazine.com.br
Papo de botequim Linux User

Repare que agora a entrada do read foi # 10 linhas e parar para ler – Bem meu amigo, por hoje é só porque
redirecionada para /dev/tty, que nada # Versão 3 acho que você já está de saco cheio…
mais é senão o terminal corrente, expli- clear – Num tô não, pode continuar…
citando desta forma que aquela leitura while read Num – Se você não estiver eu estou… Mas já
seria feita do teclado e não do arquivo do que você está tão empolgado com o shell,
numeros. É bom realçar que isso não # Contando... vou te deixar um serviço bastante sim-
acontece somente quando usamos o redi- ((ContLin++)) ples para você melhorar a sua cdteca:
recionamento de entrada; se tivéssemos echo "$Num" Monte toda a tela com um único echo
usado o redirecionamento via pipe (|), o ((ContLin % (`tput lines` - 3))) || e depois posicione o cursor à frente de
mesmo teria ocorrido. Veja agora a exe- { cada campo para receber o valor que
cução do script: # para ler qualquer caractere será digitado pelo operador.
read -n1 -p"Tecle Algo " < /dev/tty Não se esqueçam que, em caso de
$ 10porpag.sh # limpa a tela após a leitura qualquer dúvida ou falta de companhia
1 2 3 4 5 6 7 8 9 10 clear para um chope é só mandar um e-mail
11 12 13 14 15 16 17 18 19 20 } para julio.neves@gmail.com. Vou aproveitar
21 22 23 24 25 26 27 28 29 30 done < numeros também para fazer uma propaganda:
digam aos amigos que quem estiver a
Isso está quase bom, mas ainda falta A mudança substancial feita neste exem- fim de fazer um curso “porreta” de pro-
um pouco para ficar excelente. Vamos plo é com relação à quebra de página, já gramação em shell deve mandar um e-
melhorar o exemplo para que você o que ela é feita a cada quantidade-de-linhas- mail para julio.neves@tecnohall.com.br para
reproduza e teste (mas, antes de testar, da-tela (tput lines) menos (-) três, isto informar-se. Até mais!
aumente o número de registros em nume- é, se a tela tem 25 linhas, o programa
ros ou reduza o tamanho da tela, para listará 22 registros e parará para leitura. Informações
que haja quebra de linha). No comando read também foi feita uma [1] Página oficial do Tput: http://www.cs.utah.edu/
alteração, inserido o parâmetro -n1 para dept/old/texinfo/tput/tput.html#SEC4
$ cat 10porpag.sh ler somente um caractere qualquer, não
[2] Página oficial do Bash: http://www.gnu.org/
#!/bin/bash necessariamente um ENTER, e a opção software/bash/bash.html
# Programa de teste para escrever -p para exibir uma mensagem.

abril 2005 edição 07 91


www.linuxmagazine.com.br
Linux User Papo de botequim

Papo de Botequim
Curso de Shell Script

Parte VIII

Dave Hamilton - www.sxc.hu


Chegou a hora de fazer como Jack e dividir os programas
em pedacinhos. Com funções e chamadas externas
os scripts ficam menores, a manutenção mais fácil
e ainda por cima reaproveitamos código.
por Júlio Cezar Neves

E
aê, cara, tudo bem? posicionais ($1, $2, …, $n). Todas as regras que se aplicam
– – Tudo beleza! Eu queria te mostrar o que fi z mas já sei
que você vai querer molhar o bico primeiro, né?
à passagem de parâmetros para programas também valem para
funções, mas é muito importante realçar que os parâmetros
– Só pra te contrariar, hoje não quero. Vai, mostra logo aí passados para um programa não se confundem com aqueles
o que você fez. que são passados para suas funções. Isso significa, por exem-
– Poxa, o exercício que você passou é muito grande. Dá uma plo, que o $1 de um script é diferente do $1 de uma de suas
olhada na listagem 1 e vê como eu resolvi: funções internas.
– É, o programa tá legal, tá todo estruturadinho, mas gosta- Repare que as variáveis $Msg, $TamMsg e $Col são de uso
ria de fazer alguns poucos comentários: só para relembrar, restrito dessa rotina e, por isso, foram criadas como variáveis
as seguintes construções: [ ! $Album ] && e [ $Musica ] locais. A razão é simplesmente a economia de memória, já que
|| representam a mesma coisa, isto é: no caso da primeira, ao sair da rotina elas serão devidamente detonadas, coisa que
testamos se a variável $Album não (!) tem nada dentro, então não aconteceria se eu não tivesse usado esse artifício.
(&&)… Na segunda, testamos se $Musica tem algum dado, A linha de código que cria a variável local Msg concatena ao
senão (||)… texto recebido ($1) um parêntese, a resposta padrão ($2) em
Se você reclamou do tamanho do programa, é porque ainda caixa alta, uma barra, a outra resposta ($3) em caixa baixa
não te dei algumas dicas. Repare que a maior parte do script e fi naliza fechando o parêntese. Uso essa convenção para, ao
é para mostrar mensagens centraliza-
das na penúltima linha da tela. Repare Listagem 1: musinc5.sh
ainda que algumas mensagens pedem
01 $ cat musinc5.sh
um S ou um N como resposta e outras
02 #!/bin/bash
são só de advertência. Isso é um caso
03 # Cadastra CDs (versao 5)
típico que pede o uso de funções, que
04 #
seriam escritas somente uma vez e
05 clear
executadas em diversos pontos do
06 LinhaMesg=$((`tput lines` - 3)) # Linha onde serão mostradas as msgs para o operador
script. Vou montar duas funções para
07 TotCols=$(tput cols) # Qtd colunas da tela para enquadrar msgs
resolver esses casos e vamos incor-
08 echo “
porá-las ao seu programa para ver o
Inclusão de Músicas
resultado fi nal.
======== == =======
– Chico! Agora traz dois chopes, um
Título do Álbum:
sem colarinho, para me dar inspira-
| Este campo foi
ção. E você, de olho na listagem 2.
Faixa: < criado somente para
Como podemos ver, uma função é
| orientar o preenchimento
defi nida quando digitamos nome_da_
Nome da Música:
função () e todo o seu corpo está
Intérprete:” # Tela montada com um único echo
entre chaves ({}). Já conversamos aqui
09 while true
no boteco sobre passagem de parâ-
10 do
metros e as funções os recebem da
11 tput cup 5 38; tput el # Posiciona e limpa linha ➟
mesma forma, isto é, são parâmetros

86 maio 2005 edição 08


www.linuxmagazine.com.br
papo de botequim Linux User

12 read Album mesmo tempo, mostrar as opções dis-


13 [ ! “$Album” ] && # Operador deu <ENTER> poníveis e realçar a resposta oferecida
14 { como padrão.
15 Msg=”Deseja Terminar? (S/n)” Quase no fim da rotina, a resposta
16 TamMsg=${#Msg} recebida ($SN) é convertida para caixa
17 Col=$(((TotCols - TamMsg) / 2)) # Centraliza msg na linha baixa (minúsculas) de forma que no
18 tput cup $LinhaMesg $Col corpo do programa não precisemos
19 echo “$Msg” fazer esse teste. Veja na listagem 3
20 tput cup $LinhaMesg $((Col + TamMsg + 1)) como ficaria a função para exibir uma
21 read -n1 SN mensagem na tela.
22 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela Essa é uma outra forma de definir
23 [ $SN = “N” -o $SN = “n” ] && continue # $SN é igual a N ou (-o) n? uma função: não a chamamos, como
24 clear; exit # Fim da execução no exemplo anterior, usando uma
25 } construção com a sintaxe nome_da_
26 grep “^$Album\^” musicas > /dev/null && função (), mas sim como function
27 { nome_da_função. Em nada mais ela
28 Msg=”Este álbum já está cadastrado” difere da anterior, exceto que, como
29 TamMsg=${#Msg} consta dos comentários, usamos a
30 Col=$(((TotCols - TamMsg) / 2)) # Centraliza msg na linha variável $* que, como já sabemos,
31 tput cup $LinhaMesg $Col representa o conjunto de todos os
32 echo “$Msg” parâmetros passados ao script, para
33 read -n1 que o programador não precise usar
34 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela aspas envolvendo a mensagem que
35 continue # Volta para ler outro álbum deseja passar à função.
36 } Para terminar com esse blá-blá-blá,
37 Reg=”$Album^” # $Reg receberá os dados para gravação vamos ver na listagem 4 as alterações
38 oArtista= # Variável que guarda artista anterior no programa quando usamos o con-
39 while true ceito de funções:
40 do Repare que a estrutura do script
41 ((Faixa++)) segue a ordem Variáveis Globais, Fun-
42 tput cup 7 38 ções e Corpo do Programa. Esta estrutu-
43 echo $Faixa ração se deve ao fato de Shell Script ser
44 tput cup 9 38 # Posiciona para ler música uma linguagem interpretada, em que
45 read Musica
o programa é lido da esquerda para
46 [ “$Musica” ] || # Se o operador tiver dado <ENTER>...
a direita e de cima para baixo. Para
47 {
ser vista pelo script e suas funções,
48 Msg=”Fim de Álbum? (S/n)”
uma variável deve ser declarada (ou
49 TamMsg=${#Msg}
inicializada, como preferem alguns)
50 Col=$(((TotCols - TamMsg) / 2)) # Centraliza msg na linha
antes de qualquer outra coisa. Por sua
51 tput cup $LinhaMesg $Col
vez, as funções devem ser declaradas
52 echo “$Msg”
antes do corpo do programa propria-
53 tput cup $LinhaMesg $((Col + TamMsg + 1)
mente dito. A explicação é simples: o
54 read -n1 SN
interpretador de comandos do shell
55 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
deve saber do que se trata a função
56 [ “$SN” = N -o “$SN” = n ] && continue # $SN é igual a N ou (-o) n?
antes que ela seja chamada no pro-
57 break # Sai do loop para gravar
grama principal.
58 }
Uma coisa bacana na criação de fun-
59 tput cup 11 38 # Posiciona para ler Artista
ções é fazê-las tão genéricas quanto
60 [ “$oArtista” ] && echo -n “($oArtista) “ # Artista anterior é default
possível, de forma que possam ser
61 read Artista
reutilizadas em outros scripts e apli-
62 [ “$Artista” ] && oArtista=”$Artista”
cativos sem a necessidade de reinven-
63 Reg=”$Reg$oArtista~$Musica:” # Montando registro
tarmos a roda. As duas funções que
64 tput cup 9 38; tput el # Apaga Música da tela
acabamos de ver são bons exemplos,
65 tput cup 11 38; tput el # Apaga Artista da tela
pois é difícil um script de entrada de
66 done
dados que não use uma rotina como
67 echo “$Reg” >> musicas # Grava registro no fim do arquivo
a MandaMsg ou que não interaja com
38 sort musicas -0 musicas # Classifica o arquivo
o operador por meio de algo seme-
69 done
lhante à Pergunta.

maio 2005 edição 08 87


www.linuxmagazine.com.br
Linux User Papo de botequim

Conselho de amigo: crie um arquivo e anexe a ele cada fun- diretório /home. Só que assim que a execução do script termi-
ção nova que você criar. Ao final de algum tempo você terá nou, o sub-shell foi para o beleléu e, com ele, todo o ambiente
uma bela biblioteca de funções que lhe poupará muito tempo criado. Agora preste atenção no exemplo abaixo e veja como a
de programação. coisa muda de figura:

O comando source $ source script_bobo


Veja se você nota algo de diferente na saída do ls a seguir: jneves juliana paula silvie
$ pwd
$ ls -la .bash_profile /home
-rw-r--r-- 1 Julio unknown 4511 Mar 18 17:45 .bash_profile $ cd -
/home/jneves
Não olhe a resposta não, volte a prestar atenção! Bem, já que $ . script_bobo
você está mesmo sem saco de pensar e prefere ler a resposta, jneves juliana paula silvie
vou te dar uma dica: acho que você já sabe que o .bash_pro- $ pwd
file é um dos scripts que são automaticamente executados /home
quando você se “loga” (ARRGGHH! Odeio
esse termo!) no sistema. Agora olhe nova- Listagem 2: Função Pergunta
mente para a saída do comando ls e me
01 Pergunta ()
diga o que há de diferente nela.
02 {
Como eu disse, o .bash_profile é exe-
03 # A função recebe 3 parâmetros na seguinte ordem:
cutado durante o logon, mas repare que ele
não tem nenhuma permissão de execução. 04 # $1 - Mensagem a ser mostrada na tela
Isso acontece porque se você o executasse 05 # $2 - Valor a ser aceito com resposta padrão
como qualquer outro script careta, no fim 06 # $3 - O outro valor aceito
de sua execução todo o ambiente por ele 07 # Supondo que $1=Aceita?, $2=s e $3=n, a linha a
gerado morreria junto com o shell sob o 08 # seguir colocaria em Msg o valor “Aceita? (S/n)”
qual ele foi executado (você se lembra de 09 local Msg=”$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)”
que todos os scripts são executados em 10 local TamMsg=${#Msg}
sub-shells, né?). Pois é para coisas assim 11 local Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha
que existe o comando source, também 12 tput cup $LinhaMesg $Col
conhecido por “.” (ponto). Este comando 13 echo “$Msg”
faz com que o script que lhe for passado 14 tput cup $LinhaMesg $((Col + TamMsg + 1))
como parâmetro não seja executado em 15 read -n1 SN
um sub-shell. Mas é melhor um exemplo 16 [ ! $SN ] && SN=$2 # Se vazia coloca default em SN
que uma explicação em 453 palavras. Veja 17 echo $SN | tr A-Z a-z # A saída de SN será em minúscula
o scriptzinho a seguir: 18 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
19 return # Sai da função
$ cat script_bobo 20 }
cd ..
ls
Listagem 3: Função MandaMsg
Ele simplesmente deveria ir para o dire-
01 function MandaMsg
tório acima do diretório atual. Vamos
02 {
executar uns comandos envolvendo o
03 # A função recebe somente um parâmetro
script_bobo e analisar os resultados:
04 # com a mensagem que se deseja exibir.
05 # para não obrigar o programador a passar
$ pwd
06 # a msg entre aspas, usaremos $* (todos
/home/jneves
07 # os parâmetros, lembra?) e não $1.
$ script_bobo
08 local Msg=”$*”
jneves juliana paula silvie
09 local TamMsg=${#Msg}
$ pwd
10 local Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha
/home/jneves
11 tput cup $LinhaMesg $Col
12 echo “$Msg”
Se eu mandei ele subir um diretório,
13 read -n1
por que não subiu? Opa, peraí que subiu
14 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
sim! O sub-shell que foi criado para exe-
15 return # Sai da função
cutar o script tanto subiu que listou os
16 }
diretórios dos quatro usuários abaixo do

88 maio 2005 edição 08


www.linuxmagazine.com.br
papo de botequim Linux User

Listagem 4: musinc6.sh 52 echo “


Inclusão de Músicas
01 $ cat musinc6
======== == =======
02 #!/bin/bash
Título do Álbum:
03 # Cadastra CDs (versao 6)
Faixa:
04 #
Nome da Música:
05 # Área de variáveis globais
Intérprete:”
06 # Linha onde as mensagens serão exibidas
53 while true
07 LinhaMesg=$((`tput lines` - 3))
54 do
08 # Quantidade de colunas na tela (para enquadrar as mensagens)
55 tput cup 5 38; tput el # Posiciona e limpa linha
09 TotCols=$(tput cols)
56 read Album
10 # Área de funções
57 [ ! “$Album” ] && # Operador deu <ENTER>
11 Pergunta ()
58 {
12 {
59 Pergunta “Deseja Terminar” s n
13 # A função recebe 3 parâmetros na seguinte ordem:
60 # Agora só testo caixa baixa
14 # $1 - Mensagem a ser dada na tela
61 [ $SN = “n” ] && continue
15 # $2 - Valor a ser aceito com resposta default
62 clear; exit # Fim da execução
16 # $3 - O outro valor aceito
63 }
17 # Supondo que $1=Aceita?, $2=s e $3=n, a linha
64 grep -iq “^$Album\^” musicas 2> /dev/null &&
18 # abaixo colocaria em Msg o valor “Aceita? (S/n)”
65 {
19 local Msg=”$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)”
66 MandaMsg Este álbum já está cadastrado
20 local TamMsg=${#Msg}
67 continue # Volta para ler outro álbum
21 # Centraliza a mensagem na linha
68 }
22 local Col=$(((TotCols - TamMsg) / 2))
69 Reg=”$Album^” # $Reg receberá os dados de gravação
23 tput cup $LinhaMesg $Col
70 oArtista= # Guardará artista anterior
24 echo “$Msg”
71 while true
25 tput cup $LinhaMesg $((Col + TamMsg + 1))
72 do
26 read -n1 SN
73 ((Faixa++))
27 [ ! $SN ] && SN=$2 # Se vazia, coloca default em SN
74 tput cup 7 38
28 echo $SN | tr A-Z a-z # A saída de SN será em minúsculas
75 echo $Faixa
29 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
76 tput cup 9 38 # Posiciona para ler música
30 return # Sai da função
77 read Musica
31 }
78 [ “$Musica” ] || # Se o operador teclou <ENTER>...
32 function MandaMsg
79 {
33 {
80 Pergunta “Fim de Álbum?” s n
34 # A função recebe somente um parâmetro
81 # Agora só testo a caixa baixa
35 # com a mensagem que se deseja exibir;
82 [ “$SN” = n ] && continue
36 # para não obrigar o programador a passar
83 break # Sai do loop para gravar dados
37 # a msg entre aspas, usaremos $* (todos
84 }
38 # os parâmetro, lembra?) e não $1.
85 tput cup 11 38 # Posiciona para ler Artista
39 local Msg=”$*”
86 # O artista anterior é o padrão
40 local TamMsg=${#Msg}
87 [ “$oArtista” ] && echo -n “($oArtista) “
41 # Centraliza mensagem na linha
88 read Artista
42 local Col=$(((TotCols - TamMsg) / 2))
89 [ “$Artista” ] && oArtista=”$Artista”
43 tput cup $LinhaMesg $Col
90 Reg=”$Reg$oArtista~$Musica:” # Montando registro
44 echo “$Msg”
91 tput cup 9 38; tput el # Apaga Música da tela
45 read -n1
92 tput cup 11 38; tput el # Apaga Artista da tela
46 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
93 done
47 return # Sai da função
94 # Grava registro no fim do arquivo
48 }
95 echo “$Reg” >> musicas
49 # O corpo do programa propriamente dito começa aqui
96 # Classifica o arquivo
50 clear
97 sort musicas -o musicas
51 # A tela a seguir é montada com um único comando echo
98 done

maio 2005 edição 08 89


www.linuxmagazine.com.br
Linux User Papo de botequim

Listagem 5: musinc7.sh Ahh! Agora sim! Quando passado


como parâmetro do comando source,
01 $ cat musinc7.sh
o script foi executado no shell cor-
02 #!/bin/bash
03 # Cadastra CDs (versao 7)
rente, deixando nele todo o ambiente
04 # criado. Agora vamos rebobinar a fita
05 # Área de variáveis globais até o início da explicação sobre este
06 LinhaMesg=$((`tput lines` - 3)) # Linha onde serão mostradas as msgs para o operador comando. Lá falamos do .bash_pro-
07 TotCols=$(tput cols) # Qtd colunas da tela para enquadrar msgs file e, a esta altura, você já deve saber
08 # O corpo do programa propriamente dito começa aqui que sua incumbência é, logo após o
09 clear login, preparar o ambiente de trabalho
10 echo “ para o usuário. Agora entendemos que
Inclusão de Músicas é por isso mesmo que ele é executado
======== == ======= usando esse artifício.
Título do Álbum: E agora você deve estar se per-
| Este campo foi guntando se é só para isso que esse
Faixa: < criado somente para comando serve. Eu lhe digo que sim,
| orientar o preenchimento mas isso nos traz um monte de vanta-
Nome da Música:
gens – e uma das mais usadas é tratar
Intérprete:” # Tela montada com um único echo
funções como rotinas externas. Veja
11 while true
na listagem 5 uma outra forma de fazer
12 do
o nosso programa para incluir CDs no
13 tput cup 5 38; tput el # Posiciona e limpa linha
14 read Album
arquivo musicas.
15 [ ! “$Album” ] && # Operador deu <ENTER> Agora o programa deu uma boa enco-
16 { lhida e as chamadas de função foram
17 source pergunta.func “Deseja Terminar” s n trocadas por arquivos externos chama-
18 [ $SN = “n” ] && continue # Agora só testo a caixa baixa dos pergunta.func e mandamsg.func,
19 clear; exit # Fim da execução que assim podem ser chamados por
20 } qualquer outro programa, dessa forma
21 grep -iq “^$Album\^” musicas 2> /dev/null && reutilizando o seu código.
22 { Por motivos meramente didáticos, as
23 . mandamsg.func Este álbum já está cadastrado chamadas a pergunta.func e manda-
24 continue # Volta para ler outro álbum msg.func estão sendo feitas por source
25 } e por . (ponto) indiscriminadamente,
26 Reg=”$Album^” # $Reg receberá os dados de gravação embora eu prefira o source que, por
27 oArtista= # Guardará artista anterior
ser mais visível, melhora a legibili-
28 while true
dade do código e facilita sua posterior
29 do
manutenção. Veja na listagem 6 como
30 ((Faixa++))
ficaram esses dois arquivos.
31 tput cup 7 38
Em ambos os arquivos, fiz somente
32 echo $Faixa
33 tput cup 9 38 # Posiciona para ler música duas mudanças, que veremos nas
34 read Musica observações a seguir. Porém, tenho
35 [ “$Musica” ] || # Se o operador tiver dado <ENTER>... mais três observações a fazer:
36 { 1. As variáveis não estão sendo mais
37 . pergunta.func “Fim de Álbum?” s n declaradas como locais, porque essa
38 [ “$SN” = n ] && continue # Agora só testo a caixa baixa é uma diretiva que só pode ser usada
39 break # Sai do loop para gravar dados no corpo de funções e, portanto, essas
40 } variáveis permanecem no ambiente do
41 tput cup 11 38 # Posiciona para ler Artista shell, poluindo-o;
42 [ “$oArtista” ] && echo -n “($oArtista) “ # Artista anterior é default 2. O comando return não está mais
43 read Artista presente, mas poderia estar sem alte-
44 [ “$Artista” ] && oArtista=”$Artista” rar em nada a lógica do script, uma
45 Reg=”$Reg$oArtista~$Musica:” # Montando registro vez que só serviria para indicar um
46 tput cup 9 38; tput el # Apaga Música da tela
eventual erro por meio de um código
47 tput cup 11 38; tput el # Apaga Artista da tela
de retorno previamente estabelecido
48 done
(por exemplo return 1, return 2, …),
49 echo “$Reg” >> musicas # Grava registro no fim do arquivo
sendo que o return e return 0 são
50 sort musicas -o musicas # Classifica o arquivo
51 done
idênticos e significam que a rotina foi
executada sem erros;

90 maio 2005 edição 08


www.linuxmagazine.com.br
papo de botequim Linux User

3. O comando que estamos acostumados Listagem 6: pergunta.func e mandamsg.func


a usar para gerar um código de retorno
01 $ cat pergunta.func
é o exit, mas a saída de uma rotina
02 # A função recebe 3 parâmetros na seguinte ordem:
externa não pode ser feita dessa forma
03 # $1 - Mensagem a ser dada na tela
porque, como ela está sendo executada
04 # $2 - Valor a ser aceito com resposta default
no mesmo shell do script que o cha-
05 # $3 - O outro valor aceito
mou, o exit simplesmente encerraria
06 # Supondo que $1=Aceita?, $2=s e $3=n, a linha
esse shell, terminando a execução de
07 # abaixo colocaria em Msg o valor “Aceita? (S/n)”
todo o script;
08 Msg=”$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)”
4. De onde veio a variável LinhaMesg? Ela
09 TamMsg=${#Msg}
veio do script musinc7.sh, porque havia
10 Col=$(((TotCols - TamMsg) / 2)) # Centraliza msg na linha
sido declarada antes da chamada das
11 tput cup $LinhaMesg $Col
rotinas (nunca esqueça que o shell que
12 echo “$Msg”
está interpretando o script e essas roti-
13 tput cup $LinhaMesg $((Col + TamMsg + 1))
nas é o mesmo);
14 read -n1 SN
5. Se você decidir usar rotinas externas
15 [ ! $SN ] && SN=$2 # Se vazia coloca default em SN
não se envergonhe, exagere nos comen-
16 echo $SN | tr A-Z a-z # A saída de SN será em minúscula
tários, principalmente sobre a passagem
17 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
dos parâmetros, para facilitar a manu-
18 $ cat mandamsg.func
tenção e o uso dessa rotina por outros
19 # A função recebe somente um parâmetro
programas no futuro.
20 # com a mensagem que se deseja exibir;
– Bem, agora você já tem mais um
21 # para não obrigar o programador a passar
monte de novidades para melhorar
22 # a msg entre aspas, usaremos $* (todos
os scripts que fizemos. Você se lem-
23 # os parâmetro, lembra?) e não $1.
bra do programa listartista.sh no qual
24 Msg=”$*”
você passava o nome de um artista
25 TamMsg=${#Msg}
como parâmetro e ele devolvia as suas
26 Col=$(((TotCols - TamMsg) / 2)) # Centraliza msg na linha
músicas? Ele era como o mostrado aqui
27 tput cup $LinhaMesg $Col
embaixo na listagem 7.
28 echo “$Msg”
– Claro que me lembro!
29 read -n1
– Para firmar os conceitos que te pas-
30 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
sei, faça-o com a tela formatada e a
execução em loop, de forma que ele
só termine quando receber um Enter no lugar do nome Não se esqueça: em caso de dúvida ou falta de compa-
do artista. Suponha que a tela tenha 25 linhas; a cada 22 nhia para um chope, é só mandar um e-mail para o endereço
músicas listadas o programa deverá dar uma parada para julio.neves@gmail.com que terei prazer em lhe ajudar. Vou aproveitar
que o operador possa lê-las. Eventuais mensagens de erro também para mandar minha propaganda: diga aos amigos que
devem ser passadas usando a rotina mandamsg.func que quem estiver a fim de fazer um curso porreta de programação
acabamos de desenvolver. Chico, manda mais dois!! O meu em Shell deve mandar um e-mail para julio.neves@tecnohall.com.br
é com pouca pressão… para informar-se. Até mais! ■

Listagem 7: listartista.sh Informações


01 $ cat listartista.sh [1] Bash, página oficial:
02 #!/bin/bash http://www.gnu.org/software/bash/bash.html
03 # Dado um artista, mostra as suas musicas
[2] Manual de referência do Bash:
04 # versao 2 http://www.gnu.org/software/bash/
05 if [ $# -eq 0 ] manual/bashref.html
06 then
07 echo Voce deveria ter passado pelo menos um parametro
08 exit 1
Julio Cezar Neves é Analista de Suporte de
Sobre o autor

09 fi
Sistemas desde 1969 e trabalha com Unix
10 IFS=”
11 :” desde 1980, quando participou do desen-
12 for ArtMus in $(cut -f2 -d^ musicas) volvimento do SOX, um sistema operacio-
13 do nal similar ao Unix produzido pela Cobra
14 echo “$ArtMus” | grep -i “^$*~” > /dev/null && echo $ArtMus | cut -f2 -d~ Computadores. Pode ser contatado no
15 done e-mail julio.neves@gmail.com

maio 2005 edição 08 91


www.linuxmagazine.com.br
Linux User Papo de Botequim

Papo de Botequim
Curso de Shell Script

Parte IX

Dave Hamilton - www.sxc.hu


Hoje vamos aprender mais sobre formatação de cadeias de caracteres,
conhecer as principais variáveis do Shell e nos aventurar no (ainda)
desconhecido território da expansão de parâmetros. E dá-lhe chope!
por Júlio Cezar Neves

T
á bom, já sei que você vai querer chope antes de começar, Bom, a resposta é "mais ou menos". Com estes comandos
mas tô tão a fi m de te mostrar o que fi z que já vou pedir a você escreve 90% do que precisa, porém se precisar escrever
rodada enquanto isso. Aê Chico, manda dois! O dele é sem algo formatado eles lhe darão muito trabalho. Para formatar a
colarinho pra não deixar cheiro ruim nesse bigodão… saída veremos agora uma instrução muito mais interessante,
Enquanto o chope não chega, deixa eu te lembrar o que a printf. Sua sintaxe é a seguinte:
você me pediu na edição passada: era para refazer
o programa listartista com a tela formatada e exe- Listagem 1: mandamsg.func e pergunta.func
cução em loop, de forma que ele só termine quando mandamsg.func
receber um [ENTER] sozinho como nome do artista. 01 # A função recebe somente um parâmetro
Eventuais mensagens de erro e perguntas feitas ao 02 # com a mensagem que se deseja exibir.
usuário deveriam ser mostradas na antepenúltima 03 # Para não obrigar o programador a passar
linha da tela, utilizando para isso as rotinas externas 04 # a msg entre aspas, usaremos $* (todos
mandamsg.func e pergunta.func que desenvolvemos 05 # os parâmetro, lembra?) e não $1.
06 Msg="$*"
durante nosso papo na edição passada.
07 TamMsg=${#Msg}
Primeiramente eu dei uma encolhida nas rotinas
08 Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha
mandamsg.func e pergunta.func, que ficaram como 09 tput cup $LinhaMesg $Col
na listagem 1. E na listagem 2 você tem o “grandão”, 10 read -n1 -p "$Msg "
nossa versão refeita do listaartista.
– Puxa, você chegou com a corda toda! Gostei da pergunta.func
forma como você resolveu o problema e estruturou 01 # A função recebe 3 parâmetros na seguinte ordem:
02 # $1 - Mensagem a ser mostrada na tela
o programa. Foi mais trabalhoso, mas a apresenta-
03 # $2 - Valor a ser aceito com resposta padrão
ção ficou muito legal e você explorou bastante as
04 # $3 - O outro valor aceito
opções do comando tput. Vamos testar o resultado
05 # Supondo que $1=Aceita?, $2=s e $3=n, a linha
com um álbum do Emerson, Lake & Palmer que 06 # abaixo colocaria em Msg o valor "Aceita? (S/n)"
tenho cadastrado. 07 Msg="$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)"
08 TamMsg=${#Msg}
Envenenando a escrita 09 Col=$(((TotCols - TamMsg) / 2)) # Centraliza msg na linha
Ufa! Agora você já sabe tudo sobre leitura de dados, 10 tput cup $LinhaMesg $Col
mas quanto à escrita ainda está apenas engatinhando. 11 read -n1 -p "$Msg " SN
12 [ ! $SN ] && SN=$2 # Se vazia coloca default em SN
Já sei que você vai me perguntar: “Ora, não é com o
13 SN=$(echo $SN | tr A-Z a-z) # A saída de SN será em minúscula
comando echo e com os redirecionamentos de saída
14 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
que se escreve dados?”.

84 junho 2005 edição 09


www.linuxmagazine.com.br
Papo de Botequim Linux User

Listagem 2: listaartista
printf formato [argumento...] $ cat listartista3.sh
01 #!/bin/bash
Onde formato é uma cadeia 02 # Dado um artista, mostra as suas musicas
03 # versao 3
de caracteres que contém três
04 LinhaMesg=$((`tput lines` - 3)) # Linha onde as msgs serão mostradas
tipos de objeto: caracteres 05 TotCols=$(tput cols) # Qtd de colunas na tela para enquadrar msgs
simples, caracteres para es- 06 clear
pecificação de formato (ou de 07 echo "
+–––––––––––––––––-----------------------------------+
controle) e seqüência de esca-
| Lista Todas as Músicas de um Determinado Artista |
pe no padrão da linguagem | ===== ===== == ======= == == =========== ======= |
C. argumento é a cadeia de | |
caracteres a ser impressa sob | Informe o Artista: |
+––––––––––––––––----------------------------------–-+"
o controle de formato.
08 while true
Cada um dos caracteres uti- 09 do
lizados é precedido pelo ca- 10 tput cup 5 51; tput ech 31 # ech=Erase chars (31 para não apagar barra vertical)
racter % e, logo a seguir, vem 11 read Nome
a especificação de formato de 12 if [ ! "$Nome" ] # $Nome esta vazio?
13 then
acordo com a tabela 1.
14 . pergunta.func "Deseja Sair?" s n
As seqüências de escape 15 [ $SN = n ] && continue
padrão da linguagem C são 16 break
sempre precedidas pelo ca- 17 fi
18 fgrep -iq "^$Nome~" musicas || # fgrep não interpreta ^ como expressão regular
ractere contra-barra (\). As
19 {
reconhecidas pelo comando 20 . mandamsg.func "Não existe música desse artista"
printf são as da tabela 2. 21 continue
Não acaba por aí não! Tem 22 }
23 tput cup 7 29; echo '| |'
muito mais coisa sobre essa
24 LinAtual=8
instrução, mas como esse é 25 IFS="
um assunto muito cheio de 26 :"
detalhes e, portanto, chato 27 for ArtMus in $(cut -f2 -d^ musicas) # Exclui nome do album
28 do
para explicar e pior ainda para
29 if echo "$ArtMus" | grep -iq "^$Nome~"
ler ou estudar, vamos passar 30 then
direto aos exemplos com co- 31 tput cup $LinAtual 29
mentários. Veja só: 32 echo -n '| "
33 echo $ArtMus | cut -f2 -d~
34 tput cup $LinAtual 82
$ printf "%c" "1 caracter"
35 echo '|'
1$ 36 let LinAtual++
37 if [ $LinAtual -eq $LinhaMesg ]
Errado! Só listou 1 caractere 38 then
39 . mandamsg.func "Tecle Algo para Continuar..."
e não saltou linha ao final
40 tput cup 7 0; tput ed # Apaga a tela a partir da linha 7
41 tput cup 7 29; echo '| |'
$ printf "%c\n" "1 caracter" 42 LinAtual=8
1 43 fi
44 fi
45 done
Saltou linha mas ainda não 46 tput cup $LinAtual 29; echo '| |'
listou a cadeia inteira 47 tput cup $((++LinAtual)) 29
48 read -n1 -p "+–––--Tecle Algo para Nova Consulta––––+"
49 tput cup 7 0; tput ed # Apaga a tela a partir da linha 7
$ printf "%c caractere\n" 1
50 done
1 caractere ➟

junho 2005 edição 09 85


www.linuxmagazine.com.br
Linux User Papo de Botequim

Tabela 1: Formatos de caractere (%) significa o tamanho que a cadeia terá O bc devolveu duas casas decimais e
após a execução do comando. Vamos ver o printf colocou o zero à direita. O co-
Caractere A expressão será impressa como:
c Caractere simples a seguir mais alguns exemplos. Os co- mando a seguir:
d Número no sistema decimal mandos abaixo:
e Notação científica exponencial $ printf "%o\n" 10
f Número com ponto decimal (float) $ printf "%d\n" 32 12
g O menor entre os formatos %e e %f com 32
omissão dos zeros não significativos $ printf "%10d\n" 32 Converteu o valor 10 para base octal.
o Número no sistema octal 32 Para melhorar experimente:
s Cadeia de caracteres
x Número no sistema hexadecimal
preenchem a string com espaços em $ printf "%03o\n" 27
% Imprime um %. Não há nenhum
tipo de conversão branco à esquerda (oito espaços mais dois 033
caracteres, 10 dígitos), não com zeros. Já
no comando abaixo: Assim a conversão fica com mais jeito
Opa, essa é a forma correta! O %c rece- de octal, né?. O que este aqui faz?
beu o valor 1, como queríamos: $ printf "%04d\n" 32
0032 $ printf "%s\n" Peteleca
$ a=2 Peteleca
$ printf "%c caracteres\n" $a O 04 após % significa “formate a string $ printf "%15s\n" Peteleca
2 caracteres em quatro dígitos, com zeros à esquerda Peteleca
se necessário”. No comando:
O %c recebeu o valor da variável $a. Imprime Peteleca com 15 caracteres.
$ printf "%e\n" $(echo "scale=2 ; 100/6" | bc) A cadeia de caracteres é preenchida com
$ printf "%10c caracteres\n" $a 1.666000e+01 espaços em branco à esquerda. Já no co-
2 caracteres mando:
$ printf "%10c\n" $a caracteres O padrão do %e é seis casas decimais.
2 Já no comando: $ printf "%-15sNeves\n" Peteleca
c Peteleca Neves
$ printf "%.2e\n" `echo "scale=2 ; 100/6" | bc`
Repare que, nos dois últimos exemplos, 1.67e+01 O menos (-) colocou espaços em branco
em virtude do uso do %c, só foi listado à direita de Peteleca até completar os 15
um caractere de cada cadeia de caracteres O .2 especificou duas casas decimais. caracteres pedidos. E o comando abaixo,
passada como parâmetro. O valor 10 à Observe agora: o que faz?
frente do c não significa 10 caracteres. Um
número seguindo o sinal de percentagem $ printf "%f\n" 32.3 $ printf "%.3s\n" Peteleca
32.300000 Pet
Tabela 2: Seqüências de escape
O padrão do %f é seis casas decimais. O .3 manda truncar a cadeia de ca-
Seqüência Efeito
E no comando: racteres após as três primeiras letras. E
a Soa o beep
b Volta uma posição (backspace) o comando a seguir:
f Salta para a próxima página $ printf "%.2f\n" 32.3
lógica ( form feed) 32.30 $ printf "%10.3sa\n" Peteleca
n Salta para o início da linha se- Peta Pet
guinte (line feed) O .2 especificou duas casas decimais.
r Volta para o início da linha cor-
Agora observe: Imprime a cadeia com 10 caracteres,
rente (carriage return)
truncada após os três primeiros, conca-
t Avança para a próxima marca de
tabulação $ printf "%.3f\n" `echo "scale=2 ; 100/6" | bc` tenada com o caractere a (após o s). E
33.330 esse comando a seguir, o que faz?

86 junho 2005 edição 09


www.linuxmagazine.com.br
Papo de Botequim Linux User

$ printf “EXEMPLO %x\n” 45232 trabalho, principalmente em instalações (1 minuto). A cada intervalo o Shell fará
EXEMPLO b0b0 com estrutura de diretórios em múltiplos a verificação antes de exibir o próximo
níveis. Veja o exemplo a seguir: prompt primário ($PS1). Se essa variável
Ele transformou o número 45232 para estiver sem valor ou com um valor menor
hexadecimal (b0b0), mas os zeros não $ echo $CDPATH ou igual a zero, a busca por novas men-
combinam com o resto. Experimente: .:..:~:/usr/local sagens não será efetuada.
$ pwd PATH » Caminhos que serão pesquisa-
$ printf “EXEMPLO %X\n” 45232 /home/jneves/LM dos para tentar localizar um arquivo espe-
EXEMPLO B0B0 $ cd bin cificado. Como cada script é um arquivo,
$ pwd caso use o diretório corrente (.) na sua
Assim disfarçou melhor! (repare no X /usr/local/bin variável $PATH, você não necessitará usar
maiúsculo). Pra terminar, que tal o co- o comando ./scrp para que o script scrp
mando abaixo: Como /usr/local estava na minha seja executado. Basta digitar scrp. Este
variável $CDPATH e não existia o diretório é o modo que prefiro.
$ printf “%X %XL%X\n” 49354 192 10 bin em nenhum dos seus antecessores (., PIPESTATUS » É uma variável do tipo
C0CA C0LA .. e ~), o comando cd foi executado tendo vetor (array) que contém uma lista de
como destino /usr/local/bin. valores de códigos de retorno do último
Este aí não é marketing e é bastante HISTSIZE » Limita o número de ins- pipeline executado, isto é, um array que
completo, veja só como funciona: truções que cabem dentro do arquivo de abriga cada um dos $? de cada instrução
O primeiro %X converteu 49354 em he- histórico de comandos (normalmente do último pipeline. Para entender melhor,
xadecimal, resultando em C0CA (leia-se .bash_history, mas na verdade é o que veja o exemplo a seguir:
“cê”, “zero”, “cê” e “a”). Em seguida veio está indicado na variável $HISTFILE). Seu
um espaço em branco seguido por outro valor padrão é 500. $ who
%XL. O %X converteu o 192 dando como HOSTNAME » O nome do host corrente jneves pts/0 Apr 11 16:26 (10.2.4.144)
resultado C0 que com o L fez C0L. E final- (que também pode ser obtido com o co- jneves pts/1 Apr 12 12:04 (10.2.4.144)
mente o último parâmetro %X transformou mando uname -n). $ who | grep ^botelho
o número 10 na letra A. LANG » Usada para determinar o idioma $ echo ${PIPESTATUS[*]}
Conforme vocês podem notar, a instru- falado no país (mais especificamente ca- 0 1
ção é bastante completa e complexa. Ain- tegoria do locale). Veja um exemplo:
da bem que o echo resolve quase tudo... Neste exemplo mostramos que o usu-
Acertei em cheio quando resolvi expli- $ date ário botelho não estava “logado”, em
car o printf através de exemplos, pois Thu Apr 14 11:54:13 BRT 2005 seguida executamos um pipeline que
não saberia como enumerar tantas regri- $ LANG=pt_BR date procurava por ele. Usa-se a notação [*]
nhas sem tornar a leitura enfadonha. Qui Abr 14 11:55:14 BRT 2005 em um array para listar todos os seus
elementos; dessa forma, vimos que a pri-
Principais variáveis do Shell LINENO » O número da linha do script meira instrução (who) foi bem-sucedida
O Bash possui diversas variáveis que ou função que está sendo executada. (código de retorno 0) e a seguinte (grep)
servem para dar informações sobre Seu uso principal é mostrar mensagens não (código de retorno 1).
o ambiente ou alterá-lo. São muitas e de erro juntamente com as variáveis $0 PROMPT_COMMAND » Se esta variável re-
não pretendo mostrar todas elas, mas (nome do programa) e $FUNCNAME (nome ceber o nome de um comando, toda vez
uma pequena parte pode lhe ajudar na da função em execução). que você teclar um [ENTER] sozinho no
elaboração de scripts. Veja a seguir as LOGNAME » Esta variável armazena o prompt principal ($PS1), esse comando
principais delas: nome de login do usuário . será executado. É muito útil quando você
CDPATH » Contém os caminhos que MAILCHECK » Especifica, em segundos, a precisa repetindo constantemente uma
serão pesquisados para tentar localizar freqüência com que o Shell verifica a pre- determinada instrução.
um diretório especificado. Apesar dessa sença de correspondência nos arquivos PS1 » É o prompt principal. No “Papo
variável ser pouco conhecida, seu uso indicados pela variáveis $MAILPATH ou de Botequim” usamos os padrões $ para
deve ser incentivado por poupar muito $MAIL. O tempo padrão é de 60 segundos usuário comum e # para root, mas é mui-

junho 2005 edição 09 87


www.linuxmagazine.com.br
Linux User Papo de Botequim

to freqüente que ele esteja personalizado. Expansão de parâmetros $ cadeia="Papo de Botequim"


Uma curiosidade é que existe até concurso Bem, muito do que vimos até agora são $ echo ${cadeia#*' '}
de quem programa o $PS1 mais criativo. comandos externos ao Shell. Eles quebram de Botequim
PS2 » Também chamado “prompt de o maior galho, facilitam a visualização, $ echo "Conversa "${cadeia#*' '}
continuação”, é aquele sinal de maior manutenção e depuração do código, mas Conversa de Botequim
(>) que aparece após um [ENTER] sem não são tão eficientes quanto os intrínse-
o comando ter sido encerrado. cos (built-ins). Quando o nosso problema No exemplo anterior foi suprimido à
PWD » Possui o caminho completo for performance, devemos dar preferência esquerda tudo o que “casa” com a menor
($PATH) do diretório corrente. Tem o ao uso dos intrínsecos. A partir de agora ocorrência da expressão *’ ‘, ou seja,
mesmo efeito do comando pwd. vou te mostrar algumas técnicas para o todos os caracteres até o primeiro espaço
RANDOM » Cada vez que esta variável é seu programa pisar no acelerador. em branco. Esses exemplos também po-
acessada, devolve um inteiro aleatório en- Na tabela 3 e nos exemplos a seguir, deriam ser escritos sem proteger o espaço
tre 0 e 32767. Para gerar um inteiro entre veremos uma série de construções cha- da interpretação do Shell (mas prefiro
0 e 100, por exemplo, digitamos: madas expansão (ou substituição) de protegê-lo para facilitar a legibilidade
parâmetros (Parameter Expansion), que do código). Veja só:
$ echo $((RANDOM%101)) substituem instruções como o cut, o expr,
73 o tr, o sed e outras de forma mais ágil. $ echo ${cadeia#* }
Vamos ver alguns exemplos: se em uma de Botequim
Ou seja, pegamos o resto da divisão do pergunta o S é oferecido como valor de- $ echo "Conversa "${cadeia#* }
número randômico gerado por 101 porque fault (padrão) e a saída vai para a variável Conversa de Botequim
o resto da divisão de qualquer número SN, após ler o valor podemos fazer:
por 101 varia entre 0 e 100. Repare que na construção de expr é
REPLY » Use esta variável para recuperar SN=$(SN:-S} permitido o uso de metacaracteres.
o último campo lido, caso ele não tenha Utilizando o mesmo valor da variável
nenhuma variável associada. Exemplo: Para saber o tamanho de uma cadeia: cadeia, observe como faríamos para ter
somente Botequim :
$ read -p "Digite S ou N: " $ cadeia=0123
Digite S ou N: N $ echo ${#cadeia} $ echo ${cadeia##*' '}
$ echo $REPLY 4 Botequim
N $ echo "Vamos 'Chopear' no "${cadeia##*' '}
Para extrair dados de cadeia, da posi- Vamos 'Chopear' no Botequim
SECONDS » Esta variável informa, em se- ção um até o final fazemos:
gundos, há quanto tempo o Shell corrente Desta vez suprimimos à esquerda de
está “de pé”. Use-a para demonstrar a $ cadeia=abcdef cadeia a maior ocorrência da expressão
estabilidade do Linux e esnobar usuários $ echo ${cadeia:1} expr. Assim como no caso anterior, o uso
daquela coisa com janelinhas coloridas bcdef de metacaracteres é permitido.
que chamam de sistema operacional, mas Outro exemplo mais útil: para que não
que necessita de “reboots” freqüentes. Repare que a origem é zero e não um. apareça o caminho (path) completo do seu
TMOUT » Se esta variável contiver um Vamos extrair 3 caracteres a partir da 2ª programa ($0) em uma mensagem de erro,
valor maior do que zero, esse valor posição da mesma variável cadeia: inicie o seu texto da seguinte forma:
será considerado o timeout padrão do
comando read. No prompt, esse valor $ echo ${cadeia:2:3} echo Uso: ${0##*/} texto da mensagem de erro
é interpretado como o tempo de espera cde
por uma ação antes de expirar a sessão. Neste exemplo seria suprimido à es-
Supondo que a variável contenha o valor Repare que novamente a origem da con- querda tudo até a última barra (/) do
30, o Shell encerrará a sessão do usuário tagem é zero e não um. Para suprimir caminho, restando somente o nome do
(ou seja, fará logout) após 30 segundos tudo à esquerda da primeira ocorrência programa. O caractere % é simétrico ao
sem nenhuma ação no prompt. de uma cadeia, faça: #, veja o exemplo:

88 junho 2005 edição 09


www.linuxmagazine.com.br
Papo de Botequim Linux User

Tabela 3: Tipos de expansão de parâmetros Há várias formas de trocar uma subca-


deia no início ou no fim de uma variável.
Expansão de parâmetros Resultado esperado
Para trocar no início fazemos:
${var:-padrao} Se var é vazia, o resultado da expressão é padrão
${#cadeia} Tamanho de $cadeia
${cadeia:posicao} Extrai uma subcadeia de $cadeia a partir de posição. Origem zero $ echo $Passaro
${cadeia:posicao:tamanho} Extrai uma subcadeia de $cadeia a partir de posição com tamanho quero quero
igual a tamanho. Origem zero $ echo “Como diz o sulista - “${PassaroU
${cadeia#expr} Corta a menor ocorrência de $cadeia à esquerda da expressão expr /#quero/não}
${cadeia##expr} Corta a maior ocorrência de $cadeia à esquerda da expressão expr Como diz o sulista - não quero
${cadeia%expr} Corta a menor ocorrência de $cadeia à direita da expressão expr
${cadeia%%expr} Corta a maior ocorrência de $cadeia à direita da expressão expr
Para trocar no final fazemos:
${cadeia/subcad1/subcad2} Troca a primeira ocorrência de subcad1 por subcad2
${cadeia//subcad1/subcad2} Troca todas as ocorrências de subcad1 por subcad2
${cadeia/#subcad1/subcad2} Se subcad1 combina com o início de cadeia, então é trocado por subcad2 $ echo “Como diz o nordestino - U

${cadeia/%subcad1/subcad2} Se subcad1 combina com o fim de cadeia, então é trocado por subcad2 “${Passaro/%quero/não}
Como diz o nordestino - quero não

$ echo $cadeia $ echo ${cadeia/*po/Conversa} – Agora já chega, o papo hoje foi chato
Papo de Botequim Conversa de Botequim porque teve muita decoreba, mas o que
$ echo ${cadeia%' '*} $ echo ${cadeia/????/Conversa} mais importa é você ter entendido o
Papo de Conversa de Botequim que te falei. Quando precisar, consulte
$ echo ${cadeia%%' '*} estes guardanapos onde rabisquei as
Papo Trocando todas as ocorrências de uma dicas e depois guarde-os para consultas
subcadeia por outra. O comando: futuras. Mas voltando à vaca fria: tá
Para trocar primeira ocorrência de uma na hora de tomar outro e ver o jogo do
subcadeia em uma cadeia por outra: $ echo ${cadeia//o/a} Mengão. Pra próxima vez vou te dar
Papa de Batequim moleza e só vou cobrar o seguinte: pe-
$ echo $cadeia gue a rotina pergunta.func (da qual
Papo de Botequim Ordena a troca de todos as letras o por falamos no início do nosso bate-papo
$ echo ${cadeia/de/no} a. Outro exemplo mais útil é para contar de hoje, veja a listagem 1) e otimize-a
Papo no Botequim a quantidade de arquivos existentes no para que a variável $SN receba o valor
$ echo ${cadeia/de /} diretório corrente. Observe o exemplo: padrão por expansão de parâmetros,
Papo Botequim como vimos.
$ ls | wc -l E não se esqueça: em caso de dúvidas
Preste atenção quando for usar metaca- 30 ou falta de companhia para um (ou mais)
racteres! Eles são gulosos e sempre com- chope é só mandar um e-mail para julio.
binarão com a maior possibilidade; No O wc põe um monte de espaços em neves@gmail.com. E diga para os amigos
exemplo a seguir eu queria trocar Papo de branco antes do resultado. Para tirá-los: que quem estiver a fim de fazer um curso
Botequim por Conversa de Botequim: porreta de programação em Shell deve
# QtdArqs recebe a saída do comando mandar um e-mail para julio.neves@tecnohall.
$ echo $cadeia $ QtdArqs=$(ls | wc -l) com.br para informar-se. Valeu! ■
Papo de Botequim $ echo ${QtdArqs/ * /}
$ echo ${cadeia/*o/Conversa} 30 Julio Cezar Neves é Analista de Suporte de
Sobre o autor

Conversatequim Sistemas desde 1969 e trabalha com Unix


Nesse exemplo, eu sabia que a saída era desde 1980, quando participou do desen-
A idéia era pegar tudo até o primeiro composta de brancos e números, por isso volvimento do SOX, um sistema operacio-
o, mas acabou sendo trocado tudo até o montei essa expressão para trocar todos os nal similar ao Unix produzido pela Cobra
último o. Isto poderia ser resolvido de espaços por nada. Note que antes e após o Computadores. Pode ser contatado no
diversas maneiras. Eis algumas: asterisco (*) há espaços em branco. e-mail julio.neves@gmail.com

junho 2005 edição 09 89


www.linuxmagazine.com.br
Linux User Papo de Botequim

Papo de Botequim
Curso de Shell Script

Parte X

Dave Hamilton - www.sxc.hu


Em mais um capítulo de nossa saga através do mundo do
Shell Script, vamos aprender a avaliar expressões, capturar
sinais e receber parâmetros através da linha de comando.
por Júlio Cezar Neves

E
aê amigo, te dei a maior moleza na a legibilidade do código está “horrorível”, O comando eval
última aula né? Um exerciciozinho mas o desempenho, isto é, a velocidade Vou te dar um problema que eu duvido
muito simples… de execução, está ótimo. Como funções que você resolva:
– É, mas nos testes que eu fi z, e de são coisas muito pessoais, já que cada
acordo com o que você ensinou sobre um usa as suas e quase não há neces- $ var1=3
substituição de parâmetros, achei que sidade de manutenção, eu sempre opto $ var2=var1
deveria fazer algumas alterações nas fun- pelo desempenho.
ções que desenvolvemos para torná-las de Hoje vamos sair daquela chatura que Te dei essas duas variáveis e quero que
uso geral, como você disse que todas as foi o nosso último papo e voltar à lógica, você me diga como eu posso, me referindo
funções deveriam ser. Quer ver? saindo da decoreba. Mas volto a te lem- apenas à variável a var2, listar o valor de
– Claro, né, mané, se te pedi para fazer brar: tudo que eu te mostrei da última vez var1 (que, no nosso caso, é 3).
é porque estou a fi m de te ver aprender, aqui no Boteco do Chico é válido e quebra – Ah, isso é mole, mole! É só digitar
mas peraí, dá um tempo. Chico! Manda um galhão. Guarde aqueles guardanapos esse comando aqui:
dois, um sem colarinho! Vai, mostra aí que rabiscamos porque, mais cedo ou
o que você fez. mais tarde, eles lhe vão ser muito úteis. echo $`echo $var2`
– Bem, além do que você pediu, eu
reparei que o programa que chamava a Listagem 1: função pergunta.func
função teria de ter previamente defi nidas 01 # A função recebe 3 parâmetros na seguinte ordem:
a linha em que seria mostrada a mensa- 02 # $1 - Mensagem a ser mostrada na tela
gem e a quantidade de colunas. O que 03 # $2 - Valor a ser aceito com resposta padrão
fi z foi incluir duas linhas – nas quais 04 # $3 - O outro valor aceito
empreguei substituição de parâmetros 05 # Supondo que $1=Aceita?, $2=s e $3=n, a linha
– para que, caso uma dessas variáveis não 06 # abaixo colocaria em Msg o valor "Aceita? (S/n)"
07 TotCols=${TotCols:-$(tput cols)} # Se não estava definido, agora está
fosse informada, ela recebesse um valor
08 LinhaMesg=${LinhaMesg:-$(($(tput lines)-3))} # Idem
atribuído pela própria função. A linha
09 Msg="$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)"
de mensagem é três linhas antes do fi m 10 TamMsg=${#Msg}
da tela e o total de colunas é obtido pelo 11 Col=$(((TotCols - TamMsg) / 2)) # Para centralizar Msg na linha
comando tput cols. Dê uma olhada na 12 tput cup $LinhaMesg $Col
listagem 1 e veja como ficou: 13 read -n1 -p "$Msg " SN
– Gostei, você já se antecipou ao que eu 14 SN=${SN:-$2} # Se vazia coloca o padrão em SN
ia pedir. Só pra gente encerrar esse papo 15 SN=$(echo $SN | tr A-Z a-z) # A saída de SN será em minúsculas
16 tput cup $LinhaMesg $Col; tput el # Apaga Msg da tela
de substituição de parâmetros, repare que

86 julho 2005 edição 10


www.linuxmagazine.com.br
Papo de Botequim Linux User

Repare que eu coloquei o echo $var2 entre crases (`), porque $ var2=ls
dessa forma ele terá prioridade de execução e resultará em var1. $ $var2
E echo $var1 produzirá 3… 10porpag1.sh alo2.sh incusu logado
– Ah, é? Então execute para ver se está correto. 10porpag2.sh ArqDoDOS.txt1 listamusica logaute.sh
10porpag3.sh confuso listartista mandamsg.func
$ echo $`echo $var2` alo1.sh contpal.sh listartista3 monbg.sh
$var1
Agora vamos colocar em var2 o seguinte: ls $var1; e em
– Ué! Que foi que aconteceu? O meu raciocínio me parecia var1 vamos colocar l*, vejamos o resultado:
bastante lógico…
– O seu raciocínio realmente foi lógico, o problema é que você $ var2='ls $var1'
esqueceu de uma das primeiras coisas de que te falei aqui no $ var1='l*'
Boteco e que vou repetir. O Shell usa a seguinte ordem para $ $var2
resolver uma linha de comando: ls: $var1: No such file or directory
P Resolve os redirecionamentos; $ eval $var2
P Substitui as variáveis pelos seus valores; listamusica listartista listartista3 logado logaute.sh
P Resolve e substitui os meta caracteres;
P Passa a linha já toda esmiuçada para execução. Novamente, no tempo de substituição das variáveis, $var1
Dessa forma, quando o interpretador chegou na fase de re- ainda não havia se apresentado ao Shell para ser resolvida.
solução de variáveis, que como eu disse é anterior à execução, Assim, só nos resta executar o comando eval para dar as duas
a única variável existente era var2 e por isso a tua solução passadas necessárias.
produziu como saída $var1. O comando echo identificou isso Uma vez um colega da excelente lista de discussão groups.yahoo.com/
como uma cadeia de caracteres e não como uma variável. group/shell-script colocou uma dúvida: queria fazer um menu
Problemas desse tipo são relativamente freqüentes e seriam que numerasse e listasse todos os arquivos com extensão .sh e,
insolúveis caso não existisse a instrução eval, cuja sintaxe é quando o operador escolhesse uma opção, o programa corres-
eval cmd, onde cmd é uma linha de comando qualquer, que pondente fosse executado. Veja minha proposta na listagem 2:
você poderia inclusive executar direto no prompt do terminal.
Quando você põe o eval na frente, no entanto, o que ocorre é Listagem 2: fazmenu.sh
que o Shell trata cmd como um parâmetro do eval e, em seguida, 01 #!/bin/bash
o eval executa a linha recebida, submetendo-a ao Shell. Ou 02 #
seja, na prática cmd é analisado duas vezes. Dessa forma, se 03 # Lista que enumera os programas com extensão .sh no
executássemos o comando que você propôs colocando o eval 04 # diretório corrente e executa o escolhido pelo operador
05 #
na frente, teríamos a saída esperada. Veja:
06 clear; i=1
07 printf "%11s\t%s\n\n" Opção Programa
$ eval echo $`echo $var2` 08 CASE='case $opt in'
3 09 for arq in *.sh
10 do
Esse exemplo também poderia ter sido feito de outra maneira. 11 printf "\t%03d\t%s\n" $i $arq
12 CASE="$CASE
Dá só uma olhada:
13 "$(printf "%03d)\t %s;;" $i $arq)
14 i=$((i+1))
$ eval echo \$$var2 15 done
3 16 CASE="$CASE
17 *) . erro;;
Na primeira passada a contrabarra (\) seria retirada e $var2 18 esac"
19 read -n3 -p "Informe a opção desejada: " opt
seria resolvido produzindo var1. Na segunda passada teria so-
20 echo
brado echo $var1, que produziria o resultado esperado. Agora 21 eval "$CASE"
vou colocar um comando dentro de var2 e executar:

julho 2005 edição 10 87


www.linuxmagazine.com.br
Linux User Papo de Botequim

Parece complicado porque usei muitos dos por) processos em execução. Vamos, "limpar a área" ao seu término. Se seu
printf para formatação da tela, mas na de agora em diante, dar uma olhadinha encerramento ocorrer de forma prevista,
verdade é bastante simples: o primei- nos sinais enviados aos processos e mais ou seja, se tiver um término normal, é
ro printf foi colocado para imprimir à frente vamos dar uma passada rápida muito fácil fazer essa limpeza; porém,
o cabeçalho e logo em seguida come- pelos sinais gerados pelos processos. se o seu programa tiver um fim brusco,
cei a montar dinamicamente a variável Para mandar um sinal a um processo, muita coisa ruim pode ocorrer:
$CASE, na qual ao final será feito um eval usamos normalmente o comando kill, P É possível que em um determinado es-
para execução do programa escolhido. cuja sintaxe é: paço de tempo, o seu computador esteja
Repare no entanto que dentro do loop cheio de arquivos de trabalho inúteis
do for existem dois printf: o primeiro $ kill -sig PID P Seu processador poderá ficar atolado
serve para formatar a tela e o segundo de processos zombies e defuncts gera-
para montar o case (se antes do coman- Onde PID é o identificador do proces- dos por processos filhos que perderam
do read você colocar uma linha echo so (Process Identification ou Process ID). os pais e estão “órfãos”;
"$CASE", verá que o comando case mon- Além do comando kill, algumas seqüên- P É necessário liberar sockets abertos para
tado dentro da variável está todo inden- cias de teclas também podem gerar sinais. não deixar os clientes congelados;
tado. Frescura, né?:). Na saída do for, foi A tabela 1 mostra os sinais mais impor- P Seus bancos de dados poderão ficar
adicionada uma linha à variável $CASE tantes para monitorarmos: corrompidos porque sistemas gerencia-
para, no caso de uma escolha inválida, Além desses, existe o famigerado si- dores de bancos de dados necessitam
ser executada uma função externa para nal -9 ou SIGKILL que, para o processo de um tempo para gravar seus buffers
exibir mensagens de erro. Vamos execu- que o está recebendo, equivale a meter em disco (commit).
tar o script para ver a saída gerada: o dedo no botão de desligar do compu- Enfim, existem mil razões para não usar
tador – o que é altamente indesejável, um kill com o sinal -9 e para monitorar o
$ fazmenu.sh já que muitos programas necessitam encerramento anormal de programas.
Opcao Programa
Listagem 3: Nova versão do fazmenu.sh
001 10porpag1.sh
01 #!/bin/bash
002 10porpag2.sh
02 #
003 10porpag3.sh 03 # Lista enumerando os programas com extensão .sh no
004 alo1.sh 04 # diretório corrente; executa o escolhido pelo operador
005 alo2.sh 05 #
006 contpal.sh 06 clear; i=1
007 fazmenu.sh 07 printf "%11s\t%s\n\n" Opção Programa
008 logaute.sh 08 CASE='case $opt in'
009 monbg.sh 09 for arq in *.sh
010 readpipe.sh 10 do
011 redirread.sh 11 printf "\t%03d\t%s\n" $i $arq
Informe a opção desejada: 12 CASE="$CASE
13 "$(printf "%03d)\t %s;;" $i $arq)
Seria interessante incluir uma opção 14 i=$((i+1))
para terminar o programa e, para isso, 15 done
seria necessária a inclusão de uma linha 16 printf "\t%d\t%s\n\n" 999 "Fim do programa" # Linha incluída
após o loop de montagem da tela e a alte- 17 CASE="$CASE

ração da linha na qual fazemos a atribui- 18 999) exit;; # Linha alterada

ção final do valor da variável $CASE. Veja 19 *) ./erro;;


20 esac"
na listagem 3 como ele ficaria:
21 read -n3 -p "Informe a opção desejada: " opt
Existe no Linux uma coisa chamada
22 echo
sinal (signal). Existem diversos sinais
23 eval "$CASE"
que podem ser mandados para (ou gera-

88 julho 2005 edição 10


www.linuxmagazine.com.br
Papo de Botequim Linux User

O comando trap Caso a transferência seja interrompida Tabela 1: Principais sinais


Para fazer a monitoração de sinais existe por um kill ou um [CTRL]+[C], certa-
Código Nome Gerado por:
o comando trap, cuja sintaxe pode ser mente deixará lixo no disco. É exatamen-
0 EXIT Fim normal do programa
uma das mostradas a seguir: te essa a forma mais comum de uso do 1 SIGHUP Quando o programa
comando trap. Como isso é trecho de recebe um kill -HUP
trap "cmd1; cmd2; cmdn" S1 S2 … SN um script devemos, logo no início dele, 2 SIGINT Interrupção pelo teclado.
trap 'cmd1; cmd2; cmdn' S1 S2 … SN digitar o comando: ([CTRL]+[C])
3 SIGQUIT Interrupção pelo teclado
([CTRL]+[\])
Onde os comandos cmd1, cmd2, cmdn trap "rm -f /tmp/$$ ; exit" 0 1 2 3 15
15 SIGTERM Quando o programa
serão executados caso o programa receba recebe um kill ou
os sinais S1, S2 … SN. As aspas (") ou Dessa forma, caso houvesse uma inter- kill -TERM
as apóstrofes (') só são necessárias caso rupção brusca (sinais 1, 2 , 3 ou 15) antes
o trap possua mais de um comando cmd do programa encerrar (no exit dentro do
associado. Cada uma delas pode ser tam- comando trap), ou um fim normal (sinal 1º Caso: O comando ftp encontra-se
bém uma função interna, uma externa 0), o arquivo /tmp/$$ seria removido. em script1. Nesse caso, o argumento do
ou outro script. Caso não houvesse a instrução exit comando trap deveria vir entre aspas (")
Para entender o uso de aspas (") e na linha de comando do trap, ao final porque, caso ocorresse uma interrupção
apóstrofes (') vamos recorrer a um da execução dessa linha o fluxo do pro- ([CTRL]+[C] ou [CTRL]+[\]) no script2,
exemplo que trata um fragmento de grama retornaria ao ponto em que estava a linha só seria interpretada nesse mo-
um script que faz uma transferência de quando recebeu o sinal que originou a mento e o PID do script2 seria diferente
arquivos via FTP para uma máquina execução desse trap. do encontrado em /tmp/$$ (não esqueça
remota ($RemoComp), na qual o usuário Note também que o Shell pesquisa a que $$ é a variável que contém o PID do
é $Fulano, sua senha é $Segredo e o linha de comando uma vez quando o trap processo ativo);
arquivo a ser enviado é $Arq. Suponha é interpretado (e é por isso que é usual 2º Caso: O comando ftp encontra-se
ainda que essas quatro variáveis foram colocá-lo no início do programa) e nova- em script2. Nesse caso, o argumento
recebidas por uma rotina anterior de mente quando um dos sinais listados é do comando trap deveria estar entre
leitura e que esse script seja muito usado recebido. Então, no último exemplo, o va-apóstrofes ('), pois caso a interrupção
por diversas pessoas. Vejamos o trecho lor de $$ será substituído no momento em se desse durante a execução de script1,
de código a seguir: que o comando trap é lido pela primeira o arquivo não teria sido criado; caso ela
vez, já que as aspas (") não protegem o ocorresse durante a execução de script2,
ftp -ivn $RemoComp << FimFTP >> /tmp/$$ U cifrão ($) da interpretação do Shell. o valor de $$ seria o PID desse processo,
2>> /tmp/$$ Se você quisesse fazer a substituição que coincidiria com o de /tmp/$$.
user $Fulano $Segredo somente ao receber o sinal, o comando O comando trap, quando executado
binary deveria ser colocado entre apóstrofes (').
sem argumentos, lista os sinais que estão
get $Arq Assim, na primeira interpretação do trap,sendo monitorados no ambiente, bem
FimFTP o Shell não veria o cifrão ($), as apóstro-
como a linha de comando que será exe-
fes (') seriam removidas e, finalmente, o cutada quando tais sinais forem recebidos.
Repare que tanto as saídas dos diálo- Se a linha de comandos do trap for nula
Shell poderia substituir o valor da variá-
gos do FTP como os erros encontrados vel. Nesse caso, a linha ficaria assim: (vazia), isso significa que os sinais espe-
estão sendo redirecionados para /tmp/$$, cificados devem ser ignorados quando
o que é uma construção bastante comum trap 'rm -f /tmp/$$ ; exit' 0 1 2 3 15 recebidos. Por exemplo, o comando trap
para arquivos temporários usados em "" 2 especifica que o sinal de interrup-
scripts com mais de um usuário, porque Suponha dois casos: você tem dois ção ([CTRL]+[C]) deve ser ignorado. No
$$ é a variável que contém o número do scripts que chamaremos de script1, cuja último exemplo, note que o primeiro ar-
processo (PID), que é único. Com esse primeira linha será um trap, e script2, gumento deve ser especificado para que o
tipo de construção evita-se que dois ou colocado em execução por script1. Por sinal seja ignorado e não é equivalente a
mais usuários disputem a posse e os di- serem dois processos diferentes, terão escrever trap 2, cuja finalidade é retornar
reitos sobre um arquivo. dois PIDs distintos. o sinal 2 ao seu estado padrão. ➟

julho 2005 edição 10 89


www.linuxmagazine.com.br
Linux User Papo de Botequim

Se você ignorar um sinal, todos os sub- Para terminar esse assunto, abra um cadeiadeopcoes deve ser abc. Se você
shells irão ignorá-lo. Portanto, se você console gráfico e escreva no prompt de desejar que uma opção seja seguida por
especificar qual ação deve ser tomada comando o seguinte: um argumento, ponha um sinal de dois
quando receber um sinal, todos os sub- pontos (:) depois da letra, como em a:bc.
shells irão tomar a mesma ação quando $ trap "echo Mudou o tamanho da janela" 28 Isso diz ao getopts que a opção -a tem a
receberem esse sinal. Ou seja, os sinais forma -a argumento. Normalmente um
são automaticamente exportados. Para o Em seguida, pegue o mouse e arraste-o ou mais espaços em branco separam o
sinal mostrado (sinal 2), isso significa que de forma a variar o tamanho da janela parâmetro da opção; no entanto, getopts
os sub-shells serão encerrados. Suponha corrente. Surpreso? É o Shell orientado também manipula parâmetros que vêm
que você execute o comando trap "" 2 e a eventos… Mais unzinho, porque não colados à opção como em -aargumento.
então execute um sub-shell, que tornará a consigo resistir. Escreva isto: cadeiadeopcoes não pode conter um si-
executar outro script como um sub-shell. nal de interrogação (?).
Se for gerado um sinal de interrupção, $ trap "echo já era" 17 O nome constante da linha de sintaxe
este não terá efeito nem sobre o Shell acima define uma variável que receberá,
principal nem sobre os sub-shell por ele Em seguida digite: a cada vez que o comando getopts for
chamados, já que todos eles ignorarão executado, o próximo dos parâmetros
o sinal. $ sleep 3 & posicionais e o colocará na variável nome.
Em korn shell (ksh) não existe a opção getopts coloca uma interrogação (?) na
-s do comando read para ler uma senha. Você acabou de criar um sub-shell que variável definida em nome se achar uma
O que costumamos fazer é usar usar o irá dormir durante três segundos em opção não definida em cadeiadeopcoes
comando stty com a opção -echo, que background. Ao fim desse tempo, você ou se não achar o argumento esperado
inibe a escrita na tela até que se encon- receberá a mensagem “já era”, porque o para uma determinada opção.
tre um stty echo para restaurar essa sinal 17 é emitido a cada vez em que um Como já sabemos, cada opção passada
escrita. Então, se estivéssemos usando o sub-shell termina a sua execução. Para por uma linha de comandos tem um ín-
interpretador ksh, a leitura da senha teria devolver esses sinais ao seu comporta- dice numérico; assim, a primeira opção
que ser feita da seguinte forma: mento padrão, digite: trap 17 28. estará contida em $1, a segunda em $2 e
Muito legal esse comando, né? Se você assim por diante. Quando o getopts obtém
echo -n "Senha: " descobrir algum material bacana sobre uma opção, ele armazena o índice do
stty -echo uso de sinais, por favor me informe por próximo parâmetro a ser processado na
read Senha email, porque é muito rara a literatura variável OPTIND.
stty echo sobre o assunto. Quando uma opção tem um argumento
associado (indicado pelo : na cadeiade-
O problema com esse tipo de constru- Comando getopts opcoes), getopts armazena o argumento
ção é que, caso o operador não soubes- O comando getopts recupera as opções e na variável OPTARG. Se uma opção não
se a senha, ele provavelmente teclaria seus argumentos de uma lista de parâ- possuir argumento ou se o argumento
[CTRL]+[C] ou um [CTRL]+[\] durante metros de acordo com a sintaxe POSIX.2, esperado não for encontrado, a variável
a instrução read para descontinuar o pro- isto é, letras (ou números) após um sinal OPTARG será "apagada" (com unset). O co-
grama e, caso agisse dessa forma, o seu de menos (-) seguidas ou não de um mando encerra sua execução quando:
terminal estaria sem echo. Para evitar que argumento; no caso de somente letras P Encontra um parâmetro que não come-
isso aconteça, o melhor a fazer é: (ou números), elas podem ser agrupa- ça com um hífen (-).
das. Você deve usar esse comando para P O parâmetro especial -- indica o fim
echo -n "Senha: " "fatiar" opções e argumentos passados das opções.
trap "stty echo para o seu script. P Quando encontra um erro (por exemplo,
exit" 2 3 A sintaxe é getopts cadeiadeopcoes uma opção não reconhecida).
stty -echo nome. A cadeiadeopcoes deve explicitar O exemplo da listagem 4 é meramente
read Senha uma cadeia de caracteres com todas as didático, servindo para mostrar, em um
stty echo opções reconhecidas pelo script; assim, pequeno fragmento de código, o uso ple-
trap 2 3 se ele reconhece as opções -a -b e –c, no do comando.

90 julho 2005 edição 10


www.linuxmagazine.com.br
Papo de Botequim Linux User

Para entender melhor, vamos executar o script: OPTARG eh 'impressora'


Dispensando os primeiros $OPTIND-1 = 3 argumentos
$ getoptst.sh -h -Pimpressora arq1 arq2 O que sobrou da linha de comandos foi 'arq1 arq2'
getopts fez a variavel OPT_LETRA igual a 'h'
OPTARG eh '' Repare, no exemplo a seguir, que se passarmos uma opção inválida a
getopts fez a variavel OPT_LETRA igual a 'P' variável $OPT_LETRA receberá um ponto de interrogação (?) e a $OPTARG
OPTARG eh 'impressora' será "apagada" (unset).
Dispensando os primeiros $OPTIND-1 = 2 argumentos
O que sobrou da linha de comandos foi 'arq1 arq2' $ getoptst.sh -f -Pimpressora arq1 arq2 # A opção –f não é valida
./getoptst.sh: illegal option -- f
Dessa forma, sem ter muito trabalho, separei getopts fez a variavel OPT_LETRA igual a '?'
todas as opções com seus respectivos argumentos, OPTARG eh ''
deixando somente os parâmetros que foram passa- getopts fez a variavel OPT_LETRA igual a 'P'
dos pelo operador para posterior tratamento. Repare OPTARG eh 'impressora'
que, se tivéssemos escrito a linha de comando com Dispensando os primeiros $OPTIND-1 = 2 argumentos
o argumento (impressora) separado da opção (-P), O que sobrou da linha de comandos foi 'arq1 arq2'
o resultado seria exatamente o mesmo, exceto pelo
OPTIND, já que nesse caso ele identifica um conjun- – Me diz uma coisa: você não poderia ter usado um condicional com
to de três opções (ou argumentos) e, no anterior, case para evitar o getopts?
somente dois. Veja só: – Poderia sim, mas para quê? Os comandos estão aí para serem usados…
O exemplo foi didático, mas imagine um programa que aceitasse muitas
$ getoptst.sh -h -P impressora arq1 arq2 opções e cujos parâmetros poderiam ou não estar colados às opções, sen-
getopts fez a variavel OPT_LETRA igual a 'h' do que as opções também poderiam ou não estar coladas: ia ser um case
OPTARG eh '' infernal! Com getopts, é só seguir os passos acima.
getopts fez a variavel OPT_LETRA igual a 'P' – É… Vendo dessa forma, acho que você tem razão. É porque eu já estou
meio cansado com tanta informação nova na minha
Listagem 4: getoptst.sh cabeça. Vamos tomar a saideira ou você ainda quer
01 $ cat getoptst.sh explicar alguma particularidade do Shell?
02 #!/bin/sh – Nem um nem outro, eu também já cansei mas
03 hoje não vou tomar a saideira porque estou indo
04 # Execute assim: dar aula na UniRIO, que é a primeira universidade
05 # federal que está preparando seus alunos do curso
06 # getoptst.sh -h -Pimpressora arq1 arq2 de graduação em Informática para o uso de Soft-
07 #
ware Livre. Mas antes vou te deixar um problema
08 # e note que as informações de todas as opções são exibidas
para te encucar: quando você varia o tamanho de
09 #
10 # A cadeia 'P:h' diz que a opção -P é uma opção complexa uma janela do terminal, no centro dela não aparece
11 # e requer um argumento e que h é uma opção simples que não requer dinamicamente, em vídeo reverso, a quantidade de
12 # argumentos. linhas e colunas? Então! Eu quero que você repro-
13 duza isso usando a linguagem Shell. Chico, traz
14 while getopts 'P:h' OPT_LETRA rapidinho a minha conta! Vou contar até um e se
15 do você não trouxer eu me mando!
16 echo "getopts fez a variavel OPT_LETRA igual a '$OPT_LETRA'"
Não se esqueça, qualquer dúvida ou falta de compa-
17 echo " OPTARG eh '$OPTARG'"
nhia para um chope é só mandar um email para julio.
18 done
19 used_up=`expr $OPTIND – 1`
neves@gmail.com. Vou aproveitar também para mandar
20 echo "Dispensando os primeiros \$OPTIND-1 = $used_up argumentos" o meu jabá: diga para os amigos que quem estiver a
21 shift $used_up fim de fazer um curso porreta de programação em
22 echo "O que sobrou da linha de comandos foi '$*'" Shell que mande um e-mail para julio.neves@tecnohall.
com.br para informar-se. Valeu! ■

julho 2005 edição 10 91


www.linuxmagazine.com.br
Linux User Papo de Botequim

Papo de botequim
Curso de Shell Script

Dave Hamilton - www.sxc.hu


Parte final
A conversa está boa, mas uma hora eles tem que sair do bar. Na última
parte do nosso papo, falamos sobre pipes e sincronização entre processos.
por Júlio Cezar Neves

E
aí rapaz, tudo bom? – Não estou nem aí para a aparência, o que eu queria é que
– Beleza. Você se lembra de que da última vez você me você exercitasse o que aprendemos. Me dá a listagem 1 pra eu
pediu para fazer um programa que imprimisse dinami- ver o que você fez.
camente, no centro da tela, a quantidade de linhas e colunas – Perfeito! Que se dane a aparência, depois vou te ensinar
de um terminal sempre que o tamanho da janela variasse? uns macetes para melhorá-la. O que vale é que o programa está
Pois é, eu até que fi z, mas mesmo depois de quebrar muito a funcionando e está bem enxuto.
cabeça a aparência não ficou igual. – Pôxa, e eu que perdi o maior tempo tentando descobrir
como aumentar a fonte…
Listagem 1: tamtela.sh – Deixe isso para lá! Hoje vamos ver umas coisas
01 #!/bin/bash bastante interessantes e úteis.
02 #
03 # Coloca no centro da tela, em video reverso, Dando nomes aos canos
04 # a quantidade de colunas e linhas Um outro tipo de pipe é o named pipe, que também
05 # quando o tamanho da tela eh alterado. é chamado de FIFO. FIFO é um acrônimo de First In
06 # First Out que se refere à propriedade em que a ordem
07 trap Muda 28 # 28 = sinal gerado pela mudanca no tamanho
dos bytes entrando no pipe é a mesma que a da saída.
08 # da tela e Muda eh a funcao que fara isso.
O name em named pipe é, na verdade, o nome de um
09
10 Bold=$(tput bold) # Modo de enfase arquivo. Os arquivos tipo named pipes são exibidos
11 Rev=$(tput rev) # Modo de video reverso pelo comando ls como qualquer outro, com poucas
12 Norm=$(tput sgr0) # Restaura a tela ao padrao default diferenças, veja:
13
14 Muda () $ ls -l pipe1
15 { prw-r-r-- 1 julio dipao 0 Jan 22 23:11 pipe1|
16 clear
17 Cols=$(tput cols)
o p na coluna mais à esquerda indica que fifo1
18 Lins=$(tput lines)
19 tput cup $(($Lins / 2)) $(((Cols - 7) / 2)) # Centro da tela
é um named pipe. O resto dos bits de controle de
20 echo $Bold$Rev$Cols X $Lins$Norm permissões, quem pode ler ou gravar o pipe, funcio-
21 } nam como um arquivo normal. Nos sistemas mais
22 modernos uma barra vertical (|), ou pipe, no fi m do
23 clear nome do arquivo é outra dica e, nos sistemas LINUX,
24 read -n1 -p "Mude o tamanho da tela ou tecle algo para terminar " onde o ls pode exibir cores, o nome do arquivo é
escrito em vermelho por padrão.

86 agosto 2005 edição 11


www.linuxmagazine.com.br
Papo de Botequim Linux User

Nos sistemas mais antigos, os named pipes são criados pelo Sincronização de processos
utilitário mknod, normalmente situado no diretório /etc. Nos Suponha que você dispare paralelamente dois programas (pro-
sistemas mais modernos, a mesma tarefa é feita pelo mkfifo, cessos), chamados programa1 e programa2, cujos diagramas de
que recebe um ou mais nomes como argumento e cria pipes blocos de suas rotinas são como mostrado na tabela 1. Os dois
com esses nomes. Por exemplo, para criar um named pipe com processos são disparados em paralelo e, no bloco 1 do programa1,
o nome pipe1, digite: as três classificações são disparadas da seguinte maneira:

$ mkfifo pipe1 for Arq in BigFile1 BigFile2 BigFile3


do
Como sempre, a melhor forma de mostrar como algo funciona if sort $Arq
é dando exemplos. Suponha que nós tenhamos criado o named then
pipe mostrado anteriormente. Vamos agora trabalhar com duas Manda=va
sessões ou dois consoles virtuais. Em um deles digite: else
Manda=pare
$ ls -l > pipe1 break
fi
e em outro faça: done
echo $Manda > pipe1
$ cat < pipe1 [ $Manda = pare ] &&
{
Voilà! A saída do comando executado no primeiro console echo Erro durante a classificação dos arquivos
foi exibida no segundo. Note que a ordem em que os comandos exit 1
ocorreram não importa. }
Se você prestou atenção, reparou que o primeiro comando …
executado parecia ter "pendurado". Isto acontece porque a outra
ponta do pipe ainda não estava conectada, e então o sistema Assim sendo, o comando if testa cada classificação que está
operacional suspendeu o primeiro processo até que o segundo sendo efetuada. Caso ocorra qualquer problema, as classificações
"abrisse" o pipe. Para que um processo que usa pipe não fique seguintes serão abortadas, uma mensagem contendo a string
em modo de espera, é necessário que em uma ponta do pipe pare é enviada pelo pipe1 e programa1 é descontinuado com
haja um processo "falante" e na outra um "ouvinte". No exemplo código de saída sinalizando um encerramento anormal.
anterior, o ls era o "falante" e o cat era o "ouvinte". Enquanto o programa1 executava o seu primeiro bloco (as
Um uso muito útil dos named pipes é permitir que programas classificações), o programa2 executava o seu bloco 1, proces-
sem nenhuma relação possam se comunicar entre si. Os named sando as suas rotinas de abertura e menu paralelamente ao
pipes também são usados para sincronizar processos, já que programa1, ganhando dessa forma um bom tempo. O fragmento
em um determinado ponto você pode colocar um processo para de código do programa2 a seguir mostra a transição do seu
"ouvir" ou para "falar" em um determinado named pipe e ele daí bloco 1 para o bloco 2:
só sairá se outro processo "falar" ou "ouvir" aquele pipe.
Você já deve ter notado que essa ferramenta é ótima para OK=`cat pipe1`
sincronizar processos e fazer bloqueio em arquivos de forma a if [ $OK = va ]
evitar perda/corrupção de dados devido a atualizações simul- then
tâneas (a famosa concorrência). Vamos ver alguns exemplos …
para ilustrar estes casos. Rotina de impressão

Tabela 1
Programa1 Programa2
Bloco 1 Rotina de classificação de três grandes arquivos Rotina de abertura e geração de menus
Bloco 2 Acertos finais e encerramento Impressão dos dados classificados pelo programa 1

agosto 2005 edição 11 87


www.linuxmagazine.com.br
Linux User Papo de Botequim

… Então, o que fazer? Para resolver o problema de concor-


else rência, vamos utilizar um named pipe. Criamos o script na
exit 1 listagem 2 que será o daemon que receberá todos os pedidos
fi para incrementar o contador. Note que ele vai ser usado por
qualquer página no nosso site que precise de um contador.
Após a execução de seu primeiro bloco, o programa2 passará Como apenas este script altera os arquivos, não existe o pro-
a "ouvir" o pipe1, ficando parado até que as classificações do blema de concorrência.
Programa1 terminem, testando a seguir a mensagem passada Este script será um daemon, isto é, rodará em segundo plano.
pelo pipe1 para decidir se os arquivos estão íntegros para serem Quando uma página sofrer um acesso, ela escreverá a sua URL
impressos ou se o programa deverá ser descontinuado. Dessa no pipe. Para testar, execute este comando:
forma é possível disparar programas de forma assíncrona e
sincronizá-los quando necessário, ganhando bastante tempo echo "teste_pagina.html" > /tmp/pipe_contador
de processamento.
Para evitar erros, em cada página a que quisermos adicionar
Bloqueio de arquivos o contador acrescentamos a seguinte linha:
Suponha que você tenha escrito um CGI (Common Gateway Inter-
face) em Shell Script para contar quantos hits uma determinada <!--#exec cmd="echo $REQUEST_URI > /tmp/pipe_contador"-->
URL recebe e a rotina de contagem está da seguinte maneira:
Note que a variável $REQUEST_URI contém o nome do arquivo
Hits="$(cat page.hits 2> /dev/null)" || Hits=0 que o browser requisitou. Esse exemplo é fruto de uma troca
echo $((Hits=Hits++)) > page.hits de idéias com o amigo e mestre em Shell Thobias Salazar Tre-
visan, que escreveu o script e colocou-o em seu excelente site
Dessa forma, se a página receber dois ou mais acessos simul- (www.thobias.org). Aconselho a todos os que querem aprender
tâneos, um ou mais poderá ser perdido, bastando que o segundo Shell a dar uma passada lá.
acesso seja feito após a leitura do arquivo page.hits e antes Você pensa que o assunto named pipes está esgotado? Enga-
da sua gravação, isto é, após o primeiro acesso ter executado a nou-se. Vou mostrar um uso diferente a partir de agora.
primeira linha do script e antes de executar a segunda.
Substituição de processos
Listagem 2: contahits.sh Vou mostrar que o Shell também usa os named
01 #!/bin/bash pipes de uma maneira bastante singular, que é a
02 substituição de processos (process substitution). Uma
03 PIPE="/tmp/pipe_contador" # arquivo named pipe substituição de processos ocorre quando você põe
04 # dir onde serao colocados os arquivos contadores de cada pagina um < ou um > grudado na frente do parêntese da
05 DIR="/var/www/contador" esquerda. Digitar o comando:
06
07 [ -p "$PIPE" ] || mkfifo "$PIPE"
$ cat <(ls -l)
08
09 while :
10 do Resultará no comando ls -l executado em um
11 for URL in $(cat < $PIPE) sub-shell, como normal, porém redirecionará a saída
12 do para um named pipe temporário, que o Shell cria,
13 FILE="$DIR/$(echo $URL | sed 's,.*/,,')" nomeia e depois remove. Então o cat terá um nome
14 # quando rodar como daemon comente a proxima linha de arquivo válido para ler (que será este named pipe
15 echo "arquivo = $FILE" e cujo dispositivo lógico associado é /dev/fd/63).
15
O resultado é a mesma saída que a gerada pelo
17 n="$(cat $FILE 2> /dev/null)" || n=0
ls -l, porém dando um ou mais passos que o usual.
18 echo $((n=n+1)) > "$FILE"
19 done
Pra que simplificar?
20 done Como poderemos nos certificar disso? Fácil… Veja
o comando a seguir:

88 agosto 2005 edição 11


www.linuxmagazine.com.br
Papo de Botequim Linux User

$ ls -l >(cat) #!/bin/bash
l-wx–– 1 jneves jneves 64 Aug 27 12:26 /dev/fd/63 -> pipe:[7050] LIST="" # Criada no shell principal
ls | while read FILE # Inicio do subshell
É… Realmente é um named pipe. Você deve estar pensando do
que isto é uma maluquice de nerd, né? Então suponha que você LIST="$FILE $LIST" # Alterada dentro do subshell
tenha dois diretórios, chamados dir e dir.bkp, e deseja saber done # Fim do subshell
se os dois são iguais. Basta comparar o conteúdo dos diretórios echo $LIST
com o comando cmp:
No início deste exemplo eu disse que ele era meramente
$ cmp <(cat dir/*) <(cat dir.bkp/*) || echo backup furado didático porque existem formas melhores de fazer a mesma
tarefa. Veja só estas duas:
ou, melhor ainda:
$ ls | ln
$ cmp <(cat dir/*) <(cat dir.bkp/*) >/dev/null || echo backup furado
ou então, usando a própria substituição de processos:
Este é um exemplo meramente didático, mas são tantos os
comandos que produzem mais de uma linha de saída que ele $ cat -n <(ls)
serve como guia para outros. Eu quero gerar uma listagem dos
meus arquivos, numerando-os, e ao final mostrar o total de Um último exemplo: você deseja comparar arq1 e arq2 usando
arquivos no diretório corrente: o comando comm, mas esse comando necessita que os arquivos
estejam classificados. Então a melhor forma de proceder é:
while read arq
do $ comm <(sort arq1) <(sort arq2)
((i++)) # assim nao eh necessario inicializar i
echo "$i: $arq" Essa forma evita que você faça as seguintes operações:
done < <(ls)
echo "No diretorio corrente (`pwd`) existem $i arquivos" $ sort arq1 > /tmp/sort1
$ sort arq2 > /tmp/sort2
Tá legal, eu sei que existem outras formas de executar a $ comm /tmp/sort1 /tmp/sort2
mesma tarefa. Usando o comando while, a forma mais comum $ rm -f /tmp/sort1 /tmp/sort2
de resolver esse problema seria:
Pessoal, o nosso papo de botequim chegou ao fim. Curti muito
ls | while read arq e recebi diversos elogios pelo trabalho desenvolvido ao longo
do de doze meses e, o melhor de tudo, fiz muitas amizades e tomei
((i++)) # assim nao eh necessario inicializar i muitos chopes de graça com os leitores que encontrei pelos
echo "$i: $arq" congressos e palestras que ando fazendo pelo nosso querido
done Brasil. Me despeço de todos mandando um grande abraço aos
echo "No diretorio corrente (`pwd`) existem $i arquivos" barbudos e beijos às meninas e agradecendo os mais de 100
emails que recebi, todos elogiosos e devidamente respondidos.
Ao executar o script, tudo parece estar bem, porém no coman- À saúde de todos nós: Tim, Tim.
do echo após o done, você verá que o valor de $i foi perdido. - Chico, fecha a minha conta porque vou pra casa! ■
Isso deve-se ao fato desta variável estar sendo incrementada em
Julio Cezar Neves é Analista de Suporte de Sistemas desde 1969 e
Sobre o autor

um sub-shell criado pelo pipe (|) e que terminou no comando


done, levando com ele todas as variáveis criadas no seu interior trabalha com Unix desde 1980, quando participou do desenvolvi-
e as alterações lá feitas por variáveis criadas externamente. mento do SOX, um sistema operacional similar ao Unix produzido
Somente para te mostrar que uma variável criada fora do pela Cobra Computadores. É autor do livro Programação Shell
Linux, publicado pela editora Brasport. Pode ser contatado no
sub-shell e alterada em seu interior perde as alterações feitas
e-mail julio.neves@gmail.com
quando o sub-shell se encerra, execute o script a seguir:

agosto 2005 edição 11 89


www.linuxmagazine.com.br