Você está na página 1de 55

LINUX USER Papo de Botequim

Curso de Shell Script

Papo de Botequim
Voc no agenta mais aquele seu porque, em ingls, Shell significa con-
cha, carapaa, isto , fica entre o u-
amigo usurio de Linux enchendo o surio e o sistema operacional, de
forma que tudo que interage com
seu saco com aquela histria de que o o sistema operacional, tem que
passar pelo seu crivo.
sistema fantstico e o Shell uma

ferramenta maravilhosa? A partir


O ambiente Shell
Bom j que para chegar ao
desta edio vai ficar mais fcil en- ncleo do Linux, no seu ker-
nel que o que interessa a
tender o porqu deste entusiasmo... todo aplicativo, necessria a
filtragem do Shell, vamos enten-
POR JULIO CEZAR NEVES der como ele funciona de forma a
tirar o mximo proveito das in-
meras facilidades que ele nos oferece.

D
ilogo entreouvido em uma mesa O Linux, por definio, um sistema
de um botequim, entre um multiusurio no podemos nunca nos
usurio de Linux e um empur- esquecer disto e para permitir o acesso
rador de mouse: O ambiente Linux de determinados usurios e barrar a en-
Quem o Bash? Para voc entender o que e como fun- trada de outros, existe um arquivo cha-
o filho caula da famlia Shell. ciona o Shell, primeiro vou te mostrar mado /etc/passwd, que alm de fornecer
P cara! Ests a fim de me deixar como funciona o ambiente em camadas dados para esta funo de leo-de-ch-
maluco? Eu tinha uma dvida e voc do Linux. D uma olhada no grfico cara do Linux, tambm prov informa-
me deixa com duas! mostrado na Figura 1. es para o incio de uma sesso (ou
No, maluco voc j h muito tem- Neste grfico 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 fsicos 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 no tem domnio nenhum sobre camada do kernel que o cerne do vai receber ao iniciar sua sesso.
o que esta acontecendo no seu com- Linux, seu ncleo, e quem pe o hard- Lembra que eu te falei de Shell, fa-
putador. Mas deixa isso pr l, pois ware para funcionar, fazendo seu geren- mlia, irmo? Pois , vamos comear a
vou te explicar o que Shell e os com- ciamento e controle. Os programas e entender isto: o Shell a conceituao
ponentes de sua famlia 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 no optei pelo que foram desenvolvidos. Fechando tudo genrico para tratar os filhos desta idia
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 tambm patibilidade total com o sh vem trazendo Universidade de Berkley, o Shell mais uti-
foi desenvolvido o Unix), foi durante muitos muitos usurios e programadores de Shell lizado em ambientes BSD. Foi ele quem intro-
anos o Shell padro do sistema operacional para este ambiente. duziu o histrico de comandos. A
Unix. tambm chamado de Standard Shell Boune Again Shell (bash): Desenvolvido ini- estruturao de seus comandos bem simi-
por ter sido durante vrios 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 nmero 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 prprio.
tribuies Linux. mundo, seja por que ele o Shell padro do Alm destes Shells existem outros, mas irei
Korn Shell (ksh): Desenvolvido por David Linux, seja por sua grande diversidade de falar somente sobre os trs primeiros, tratan-
Korn, tambm 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 caractersticos do C Shell. as especificidades de cada um.

82 Agosto 2004 www.linuxmagazine.com.br


Papo de Botequim LINUX USER

tncia do sistema operacional Unix, redirecionamento, que pode ser de


foram aparecendo. Atualmente existem
Com que Shell eu vou? entrada (stdin), de sada (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 pgina anterior). seguir. Mas antes precisamos falar de...
Shell que o usurio vai usar ao se logar, isto
deve ser interpretado ao p-da-letra. Se este
Como funciona o Shell campo do seu registro contm o termo prog, Substituio de Variveis
O Shell o primeiro programa que voc ao acessar o sistema o usurio executar o Neste ponto, o Shell verifica se as even-
ganha ao iniciar sua sesso (se quiser- programa prog. Ao trmino da execuo, a tuais variveis (parmetros comeados
mos assassinar a lngua portuguesa sesso do usurio se encerra automatica- por $), encontradas no escopo do
mente. Imagine quanto se pode incremen-
podemos tambm dizer ao se logar) no comando, esto definidas e as substitui
tar a segurana com este simples artifcio.
Linux. ele quem vai resolver um monte por seus valores atuais.
de coisas de forma a no onerar o kernel
com tarefas repetitivas, poupando-o para volvidos (inclusive o prprio programa), Substituio de Meta-
tratar assuntos mais nobres. Como cada e retorna um erro caso o usurio que Caracteres
usurio possui o seu prprio Shell inter- chamou o programa no 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 substitudo por
examina as suas sintaxes, passando-os $ ls linux seus possveis valores.
esmiuados para execuo. linux Supondo que o nico item no seu
pa! Esse negcio de interpretar co- diretrio corrente cujo nome comea
mando no tem nada a ver com inter- Neste exemplo o Shell identificou o ls co- com a letra n seja um diretrio chamado
pretador no, n? mo um programa e o linux como um pa- nomegrandeprachuchu, se voc fizer:
Tem sim: na verdade o Shell um in- rmetro passado para o programa ls.
terpretador que traz consigo uma po- $ cd n*
derosa linguagem com comandos de Atribuio
alto nvel, que permite construo de Se o Shell encontra dois campos separa- como at aqui quem est manipulando a
loops, de tomadas de deciso e de ar- dos por um sinal de igual (=) sem espa- linha de comando ainda o Shell e o
mazenamento de valores em variveis, os em branco entre eles, ele identifica programa cd ainda no foi executado, o
como vou te mostrar. esta seqncia como uma atribuio. Shell expande o n* para nomegrandepra-
Vou explicar as principais tarefas que o chuchu (a nica possibilidade vlida) e
Shell cumpre, na sua ordem de exe- $ valor=1000 executa o comando cd com sucesso.
cuo. Preste ateno, porque esta
ordem fundamental para o entendi- Neste caso, por no haver espaos 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 atribuio e Completadas todas as tarefas anteriores,
Anlise da linha de comando colocou 1000 na varivel valor. o Shell monta a linha de comando, j
Neste exame o Shell identifica os carac- com todas as substituies feitas e
teres especiais (reservados) que tm sig- Resoluo de chama o kernel para execut-la em um
nificado para a interpretao da linha e Redirecionamentos novo Shell (Shell filho), que ganha um
logo em seguida verifica se a linha pas- Aps identificar os componentes da li- nmero de processo (PID ou Process
sada um comando ou uma atribuio nha que voc digitou, o Shell parte para IDentification) e fica inativo, tirando
de valores, que so os tens que vou a resoluo de redirecionamentos. uma soneca durante a execuo 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 Atribuio
espaos em branco: a primeira Ncleo ou Kernel Jamais faa:
parte o nome do programa,
cuja existncia ser verificada; Hardware $ valor = 1000
bash: valor: not found
em seguida, nesta ordem, vm
Neste caso, o Bash achou a palavra valor iso-
as opes/parmetros, redire-
lada por espaos e julgou que voc estivesse
cionamentos e variveis.
mandando executar um programa chama-
Quando o programa identifi-
do valor, para o qual estaria passando dois
cado existe, o Shell verifica as parmetros: = e 1000.
permisses 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 Padro)


Para tirar aquela sensao que voc tem $ echo * e como tambm no citei a sada, o que
quando v um script Shell, que mais eu teclar ir para a tela (Sada Padro),
parece uma sopa de letrinhas ou um con- Viu a diferena? criando desta forma como eu havia
junto de hierglifos, vou lhe mostrar os Aspas (): exatamente iguais ao aps- 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 cifro ($), uma
frando a Pedra de Roseta. crase (`), ou uma barra invertida (\), Redirecionamentop da Sada
estes caracteres sero interpretados Padro
Caracteres para remoo do pelo Shell. Para especificarmos a sada de um pro-
significado. No precisa se estressar, eu no te dei grama usamos o smbolo > ou o
isso mesmo, quando no desejamos exemplos do uso das aspas por que >>, seguido do nome do arquivo pa-
que o Shell interprete um caractere voc ainda no conhece o cifro ($) ra o qual se deseja mandar a sada.
especfico, devemos escond-lo dele. nem a crase (`). Daqui para frente - Vamos transformar o programa ante-
Isso pode ser feito de trs maneiras difer- veremos com muita constncia o uso rior em um editor de textos:
entes, cada uma com sua peculiaridade: destes caracteres especiais; o mais
Apstrofo (): quando o Shell v uma importante entender seu significado. $ cat > Arq
cadeia de caracteres entre apstrofos,
ele retira os apstrofos da cadeia e no Caracteres de O cat continua sem ter a entrada especi-
interpreta seu contedo. redirecionamento ficada, portanto est aguardando que os
A maioria dos comandos tem uma entra- dados sejam teclados, porm a sua sada
$ ls linuxm* da, uma sada e pode gerar erros. Esta est sendo desviada para o arquivo Arq.
linuxmagazine entrada chamada Entrada Padro ou Assim sendo, tudo que esta sendo tecla-
$ ls 'linuxm*' stdin e seu dispositivo padro o teclado do esta indo para dentro de Arq, de for-
bash: linuxm* no such file U do terminal. Analogamente, a sada do ma que fizemos o editor de textos mais
or directory comando chamada Sada Padro ou curto e ruim do planeta.
stdout e seu dispositivo padro a tela Se eu fizer novamente:
No primeiro caso o Shell expandiu o do terminal. Para a tela tambm so
asterisco e descobriu o arquivo linux- enviadas normalmente as mensagens de $ cat > Arq
magazine para listar. No segundo, os erro oriundas dos comandos, chamada
apstrofos inibiram a interpretao do neste caso de Sada de Erro Padro ou Os dados contidos em Arq sero perdi-
Shell e veio a resposta que no 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 informaes no final do arquivo eu
dntico aos apstrofos exceto que a isto digite (tecle Enter ao final de cada deveria ter feito:
barra invertida inibe a interpretao linha comandos do usurio so ilus-
somente do caractere que a segue. trados em negrito): $ cat >> Arq
Suponha que voc, acidentalmente,
tenha criado um arquivo chamado * $ cat Redirecionamento da Sada
(asterisco) o que alguns sabores de E-e-eu sou gago. Vai encarar? de Erro Padro
Unix permitem e deseja remov-lo. E-e-eu sou gago. Vai encarar? Assim como por padro o Shell recebe os
Se voc fizesse: dados do teclado e envia a sada para a
O cat um comando que lista o con- tela, os erros tambm vo para a tela se
$ rm * tedo do arquivo especificado para a voc no especificar para onde eles de-
Sada Padro (stdout). Caso a entrada vem ser enviados. Para redirecionar os
Voc estaria na maior encrenca, pois o no seja definida, ele espera os dados da erros, use 2> SaidaDeErro. Note que en-
rm removeria todos os arquivos do stdin (a entrada padro). Ora como eu tre o nmero 2 e o sinal de maior (>)
diretrio corrente. A melhor forma de no especifiquei a entrada, ele a est no existe espao em branco.
fazer o servio : Vamos supor que durante a execuo
Redirecionamento Perigoso de um script voc pode, ou no (depen-
$ rm \* Como j havia dito, o Shell resolve a linha e dendo do rumo tomado pela execuo
depois manda o comando para a execuo. do programa), ter criado um arquivo
Desta forma, o Shell no interpreta o Assim, se voc redirecionar a sada de um chamado /tmp/seraqueexiste$$. Como
asterisco, evitando a sua expanso. arquivo para ele prprio, primeiramente o no quer ficar com sujeira no disco
Faa a seguinte experincia cientfica: Shell esvaziaeste arquivo e depois manda rgido, ao final do script voc coloca a
o comando para execuo! Desta forma, linha a seguir:
para sua alegria, voc acabou de perder o
$ cd /etc
contedo de seu querido arquivo.
$ echo '*' rm /tmp/seraqueexiste$$

84 Agosto 2004 www.linuxmagazine.com.br


Papo de Botequim LINUX USER

caprichamos, n? Ento ao invs de sair


Dados ou Erros? redigindo o mail direto no prompt, de
Etiquetas Erradas
Preste ateno! No confunda >> com 2>. O forma a tornar impossvel a correo 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 Sada de Erro presena de espaos em branco antes ou
voc escreveu um ns vai, voc edita
Padro (stderr) para um arquivo que est aps o mesmo. Fique muito atento quanto a
um arquivo com o contedo da mensa-
sendo designado. Isto importante! isso, por que este tipo de erro costuma dar
gem e aps umas quinze verificaes
uma boa surra no programador, at que seja
sem constatar nenhum erro, decide
detectado. Lembre-se: um label que se preze
Caso o arquivo no 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 no acontea faa: $ mail chefe@chefia.com.br < U
arquivocommailparaochefe nada a partir deste ponto at encontrar
rm /tmp/seraqueexiste$$ 2> U o label fimftp. Voc no entenderia
/dev/null e o chefe receber uma mensagem com o droga nenhuma, j que so instrues
contedo do arquivocommailparaochefe. especficas do ftp.
Para que voc teste a Sada de Erro Pa- Outro tipo de redirecionamento muito Se fosse s isso seria simples, mas
dro direto no prompt do seu Shell, vou louco que o Shell permite o chamado pelo prprio exemplo d para ver que
dar mais um exemplo. Faa: here document. Ele representado por existem duas variveis ($Usuario e
<< e serve para indicar ao Shell que o $Senha), que o Shell vai resolver antes
$ ls naoexiste escopo de um comando comea na linha do redirecionamento. Mas a grande
bash: naoexiste no such file U seguinte e termina quando encontra uma vantagem deste tipo de construo
or directory linha cujo contedo seja unicamente o que ela permite que comandos tam-
$ ls naoexiste 2> arquivodeerros label que segue o sinal <<. bm sejam interpretados dentro do
$ Veja o fragmento de script a seguir, escopo do here document, o que,
$ cat arquivodeerros com uma rotina de ftp: alis, 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 negcio funciona. Agora ainda no d,
user $Usuario $Senha esto faltando ferramentas.
Neste exemplo, vimos que quando fize- binary O comando user do repertrio de
mos um ls em naoexiste, ganhamos uma get arquivoremoto instrues do ftp e serve para passar o
mensagem de erro. Aps redirecionar a fimftp usurio e a senha que haviam sido
Sada de Erro Padro para arquivodeerros lidos em uma rotina anterior a este
e executar o mesmo comando, recebe- neste pedacinho de programa temos um fragmento de cdigo e colocados res-
mos somente o prompt na tela. Quan- monte de detalhes interessantes: pectivamente nas duas variveis:
do listamos o contedo do arquivo para As opes usadas para o ftp (-ivn) $Usuario e $Senha.
o qual foi redirecionada a Sada de Erro servem para ele listar tudo que est O binary outra instruo do ftp, que
Padro, vimos que a mensagem de erro acontecendo (opo -v de verbose), serve para indicar que a transferncia
tinha sido armazenada nele. para no ficar perguntando se voc de arquivoremoto ser feita em modo
interessante notar que estes carac- tem certeza que deseja transmitir cada binrio, isto o contedo do arquivo
teres de redirecionamento so cumula- arquivo (opo -i de interactive) e no ser inteerpretado para saber se
tivos, isto , se no exemplo anterior finalmente a opo -n serve para dizer est em ASCII, EBCDIC,
fizssemos o seguinte: ao ftp para ele no solicitar o usurio e O comando get arquivoremoto diz ao
sua senha, pois estes sero informados cliente ftp para pegar este arquivo no
$ ls naoexiste 2>> U pela instruo especfica (user); servidor hostremoto e traz-lo para a
arquivodeerros Quando eu usei o << fimftp, estava nossa mquina local. Se quisssemos
dizendo o seguinte para o interpreta- enviar um arquivo, bastaria usar, por
a mensagem de erro oriunda do ls seria dor: Olha aqui Shell, no se meta em exemplo, o comando put arquivolocal.
anexada ao final de arquivodeerros.
Direito de Posse Redirecionamento de
Redirecionamento da O $$ contm o PID,isto ,o nmero do seu comandos
Entrada Padro processo. Como o Linux multiusurio, 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 Padro usamos o < (menor que). arquivos para no 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 sada de um comando para a entra-
ento) seria o seu dono e a segunda ganharia
Suponha que voc queira mandar um da de outro. utilssimo e, apesar de no
um erro quando tentasse gravar algo nele.
mail para o seu chefe. Para o chefe ns 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 ingls significa tubo, j que ele cana- usuarios conectados" /home/meudir
liza a sada de um comando para a Existem who | wc -l usuarios U /etc
entrada de outro) e sua representao a conectados $ pwd
| (barra vertical). /home/meudir
Hi! Olha s, no funcionou! mesmo,
$ ls | wc -l no funcionou e no 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 diretrio 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 opo -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 mgico
mos afirmar categoricamente que no $ echo "Existem `who | wc -l` U Mandrake por a? Nada disso. O interes-
meu diretrio existiam 21 arquivos. usuarios conectados" sante do uso de parnteses que eles
Existem 8 usuarios U invocam um novo Shell para executar os
$ cat /etc/passwd | sort | lp conectados comandos que esto em seu interior.
Desta forma, fomos realmente para o
A linha de comandos acima manda a Para eliminar esse monte de brancos diretrio /etc, porm aps a execuo 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 diretrio /etc morreu e retor-
fica e envia para o lp que o gerenciador namos ao Shell anterior que estava em
da fila de impresso. $ 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
so, ns a colocamos entre parnteses, As aspas protegem da interpretao do Ola suporte, hoje as `date U
no ? Pois , por causa da aritmtica 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 espao aquele problema que eu havia U
Shell o que prioriza mesmo so as crases em branco como separador, o monte de reportado por telefone. De U
(`) e no os parnteses. Vou dar exemp- espaos ser trocado por um nico aps acordo com seu pedido segue a U
los para voc entender melhor. a retirada das aspas. listagem do diretorio:
Eu quero saber quantos usurios esto Outra coisa interessante o uso do `ls -l`
logados no computador que eu admi- ponto-e-vrgula. 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-vrgula. Ento: que conversamos anteriormente sobre
here document. Os comandos entre
O comando who passa a lista de usurios $ 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 invs de ter um nmero oito comandos date e ls foram executados
solto na tela, o que eu quero mesmo Neste exemplo, listei o nome do diretrio antes do comando mail, recebendo ento
que ele esteja no meio de uma frase. Ora, corrente com o comando pwd, mudei um instantneo do ambiente no
para mandar frases para a tela eu s pre- para o diretrio /etc, novamente listei o momento de envio do email.
ciso usar o comando echo; ento vamos nome do diretrio e finalmente voltei pa- - Garom, passa a rgua!
ver como que fica: ra o diretrio onde estava anteriormente
(cd -), listando seu nome. Repare que Julio Cezar Neves Analista de Su-
SOBRE O AUTOR

Buraco Negro coloquei o ponto-e-vrgula de todas as porte de Sistemas desde 1969 e tra-
Em Unix existe um arquivo fantasma. formas possveis, para mostrar que no balha com Unix desde 1980, quando
Chama-se /dev/null.Tudo que enviado importa se existem espaos em branco fez parte da equipe que desenvolveu
para este arquivo some. Assemelha-se a um antes ou aps 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
no me interessava guardar a possvel men- professor do curso de Mestrado em
parnteses. No exemplo a seguir, colo-
sagem de erro oriunda do comando rm, redi- Software Livre das Faculdades Estcio
camos diversos comandos separados por
recionei-a para este arquivo. de S, no Rio de Janeiro.
ponto-e-vrgula entre parnteses:

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 expresses regulares e linhas que usavam a palavra grep, em
todos os arquivos terminados em .sh.
colocar a mo na massa pela primeira vez, construindo um aplicativo simples Como uso essa extenso para definir
meus arquivos com programas em Shell,
para catalogar uma coleo de CDs. POR JLIO CSAR NEVES malandramente, o que fiz foi listar as lin-
has dos programas que poderia usar
como exemplo do comando grep.

G
arom! Traz um chops e dois Olha que legal! O grep aceita como
pastel. O meu amigo hoje no entrada a sada 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 execuo de coman-
coisa a aprender! dos). Dessa forma, no 3 exemplo,
E ento, amigo, t entendendo o comando who listou as pessoas
tudo que te expliquei at agora? logadas na mesma mquina que
Entendendo eu t, mas no vi voc (no se esquea jamais: o
nada prtico nisso Linux multiusurio) 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 coando.
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 qncia. O que muitas pessoas no
ver porque at na TV j teve pro- sabem que existem trs comandos na
grama chamado O Shell o Limite. famlia grep: grep, egrep e fgrep. A princi-
Para comear vamos falar dos coman- pais diferenas entre os 3 so:
dos da famlia grep grep - Pode ou no usar expresses
Grep? No conheo nenhum termo em $ grep franklin /etc/passwd regulares simples, porm no caso de
ingls com este nome no us-las, o fgrep melhor, por ser
claro, grep um acrnimo (sigla) Pesquisando em vrios arquivos: mais rpido.
para Global Regular Expression Print, egrep (e de extended, estendido) -
que usa expresses regulares para $ grep grep *.sh muito poderoso no uso de expresses
pesquisar a ocorrncia de cadeias de regulares. Por ser o mais poderoso dos
caracteres na entrada definida. Pesquisando na sada de um comando: trs, s deve ser usado quando for
Por falar em expresses regulares (ou necessria a elaborao de uma
regexp), o Aurlio Marinho Jargas es- $ who | grep carvalho expresso regular no aceita pelo grep.
creveu dois artigos [1 e 2] imperdveis fgrep (f de fast, rpido) - Como o
para a Revista do Linux sobre esse No 1 exemplo, procurei a palavra nome diz, o ligeirinho da famlia,
assunto e tambm publicou um livro [3] franklin em qualquer lugar do arquivo executando o servio 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 vo te ajudar no que nome de usurio, isto , somente no in- rpido que o grep e 50% mais que o
est para vir. cio dos registros desse arquivo, poderia egrep), porm no permite o uso de
digitar $ grep ^franklin /etc/passwd. expresses 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 negcio de gripe brincadeira, s apstrofos?, voc vai me perguntar. Se enas entre os membros da famlia, me
um pretexto para pedir umas caipirinhas. tivesse lido os artigos que mencionei, diga: o que voc acha dos trs exemplos
Eu te falei que o grep procura cadeias de saberia que o circunflexo serve para limi- que eu dei antes das explicaes?
caracteres dentro de uma entrada defi- tar a pesquisa ao incio de cada linha e Achei que o fgrep resolveria o teu prob-
nida, mas o que vem a ser uma entrada os apstrofos servem para o Shell no lema mais rapidamente que o grep.
definida? Bem, existem vrias formas interpretar esse circunflexo, deixando-o Perfeito! T vendo que voc est
de definir a entrada do comando grep. passar inclume 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 subdiretrios ser explorado).


Pra 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 parmetros 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 parmetros
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 msicas:
drwxr-xr-x 3 root root 4096 Jul 2 20:30 scribus
drwxrwxr-x 3 root root 4096 Jan 17 2000 sounds nomedolbum^intrprete1~nomeU
drwxr-xr-x 3 root root 4096 Dec 18 2000 xine damsica1:...:intrpreten~nomeU
drwxr-xr-x 3 root root 4096 Jun 19 2000 xplns damsican

para clarear de vez as diferenas de -l). Os apstrofos foram usados para o Isto , o nome do lbum ser separado
uso entre os membros da famlia. Shell no 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 posies possveis da sada de pelo intrprete de cada msica do CD e a
no tenho certeza se est escrito com L um ls -l em um arquivo comum (no msica interpretada. Estes grupos so
maisculo ou minsculo. Posso fazer diretrio, nem link, nem ). separados entre si por dois pontos (:) e,
uma busca de duas formas: Para descobrir todos os arquivos exe- internamente, o intrprete ser separado
cutveis em um determinado diretrio por um til (~) do nome da msica.
egrep (Linux | linux) arquivo.txt eu poderia fazer: Quero escrever um programa chamado
musinc, que incluir registros no meu
ou ento: $ ls -la | egrep ^-..(x|s) arquivo msicas. Passarei cada lbum
como parmetro para o programa:
grep [Ll]inux arquivo.txt novamente usamos o circunflexo para
limitar a pesquisa ao incio de cada $ musinc lbum^interprete~U
No primeiro caso, a expresso regular linha, ou seja, listamos as linhas que musica:interprete~musica:...
complexa (Linux | linux) usa os parnte- comeam por um trao (-), seguido de
ses para agrupar as opes 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
ingls) lgico, isto , estou procurando x ou um s. Obteramos o mesmo resul- varivel. A nica diferena entre um
Linux ou linux. tado se usssemos o comando: parmetro recebido e uma varivel que
No segundo, a expresso regular os primeiros recebem nomes numricos
[Ll]inux significa: comeado por L ou l $ ls -la | grep ^-..[xs] (o que quis dizer que seus nomes so
seguido de inux. Como esta uma formados somente por um algarismo,
expresso simples, o grep consegue e alm disso, agilizaramos 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 comear a desenvolver progra- $ cat teste
Outro exemplo. Para listar todos os mas! Creio que a montagem de um #!/bin/bash
subdiretrios do diretrio corrente, basta banco de dados de msicas bacana #Teste de passagem de parametros
usar o comando $ ls -l | grep ^d. Veja o para efeito didtico (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). No se esquea echo 3o. parm -> $3
para limitar a pesquisa primeira que, da mesma forma que vamos desen-
posio da sada do ls longo (parmetro volver um monte de programas para Agora vamos rodar esse programinha:
organizar os seus CDs de msica, com
Tabela 1 pequenas adaptaes voc pode fazer o $ teste passando parametros para U
mesmo para organizar os CDs de soft- testar
Posio Valores possveis
1 -
ware que vm 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 cutvel. Vou fazer isso e testar nova-
(o Linux multiusurio, 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: incluso 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 parmetro recebido. Vamos cadastrar 3
2o. parm -> parametros lbuns para ver se funciona (para no
Repare que a palavra testar, que seria o 3o. parm -> para ficar enchendo lingia, suponho que
quarto parmetro, no foi listada. Isso Para listar todos de uma U em cada CD s existem duas msicas):
ocorreu porque o programa teste s lista tacada eu faco passando U
os trs primeiros parmetros 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 interpretao do Shell (se no usasse as $ musinc album 2^Artista3U
1o. parm -> passando parametros contrabarras as aspas no apareceriam). ~Musica3:Artista4~Musica4
2o. parm -> para Como disse, os parmetros recebem
3o. parm -> testar nmeros de 1 a 9, mas isso no significa Listando o contedo do arquivo musicas:
que no posso usar mais de nove
As aspas no deixaram o Shell ver o parmetros. Significa que s posso $ cat musicas
espao em branco entre as duas enderear nove. Vamos testar isso: album3^Artista5~Musica5:Artista6U
primeiras palavras, e elas foram consid- ~Musica6
eradas como um nico parmetro. E $ cat teste album1^Artista1~Musica1:Artista2U
falando em passagem de parmetros, #!/bin/bash ~Musica2
uma dica: veja na Tabela 2 algumas var- # Programa para testar passagem U album2^Artista3~Musica3:Artista4U
iveis especiais. Vamos alterar o pro- de parametros (3a. Versao) ~Musica4
grama teste para usar as novas variveis: echo O programa $0 recebeu $# U
parametros Podia ter ficado melhor. Os lbuns esto
$ 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 sada para ele mesmo (para isso serve a
echo 1o. parm -> $1 Execute o programa: opo -o), aps 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 so: 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 parmetros variam de $1 a $9 uma nova verso, que desenvolveremos
echo $1 >> musicas digitei echo $11 e o que aconteceu? O aps 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: Variveis especiais
podendo o n assumir qualquer valor
$ cat musinc Varivel Significado
numrico, despreza os n primeiros
#!/bin/bash $0 Contm o nome do programa
parmetros, tornando o parmetro de
# Cadastra CDs (versao 2) $# Contm a quantidade de
ordem n+1. parmetros passados
#
Bem, agora que voc j sabe sobre $* Contm o conjunto de todos os
echo $1 >> musicas
passagem de parmetros, vamos voltar parmetros (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 no 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 parmetro. Veja o cdigo cadeia de caracteres album nos arquivos # Exclui CDs (versao 1)
na Listagem 3: 2 e musicas. Como o arquivo 2 no #
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 necessrio pro- musicas, listou a todos.
teg-la da interpretao do Shell, para melhor ignorarmos maisculas e
que ele no a interprete como dois minsculas na pesquisa. Resolveremos mando. Estamos ento prontos para
parmetros. Exemplo: os dois problemas com a Listagem 4. desenvolver o script para remover CDs
Nesse caso, usamos a opo -i do grep empenados da sua CDteca. Veja o
$ muslist album 2 que, como j vimos, serve para ignorar cdigo da Listagem 5.
grep: cant open 2 maisculas e minsculas, 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 expanso 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 $$ contm o
Que lambana! Onde est o erro? Eu tive ~Musica4 PID (identificao do processo) e, dessa
o cuidado de colocar o parmetro pas- forma, cada um que editar o arquivo
sado entre aspas para o Shell no 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 colises.
grep est sendo executado: registro; ento, da forma que estamos Os programas que fizemos at aqui
fazendo, podemos pesquisar por lbum, ainda so muito simples, devido falta
grep $1 musicas por msica, por intrprete 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 verso de muslist que permitir especi- um sistema bacana para controle dos
parmetro, quando o $1 foi passado pelo ficar por qual campo pesquisar. seus CDs. Na prxima vez que nos
Shell para o comando grep, transformou- Ah! Em um dia de vero 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
contedo 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! Garom! Mais um
grep album 2 musicas banco de dados? No tem problema, sem colarinho!
vamos desenvolver um script chamado
Como a sintaxe do grep : musexc, para excluir estes CDs. INFORMAES
Antes de desenvolver o bacalho,
Listagem 3 - muslist quero te apresentar a uma opo bas- [1] http://www.revistadolinux.com.br/ed/003/
tante til da famlia de comandos grep. ferramentas.php3
$ cat muslist
a opo -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 manipulao de cadeias de caracteres, que sero muito

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

G aron! 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
Ento, recapitulando, o layout
do arquivo o seguinte: nome do
lbum^intrprete1~nome da msica1:...:
intrpreten~nome da msican, isto , o
nome do lbum ser separado por um
Artista2
Artista4
Artista6
Artista8

usado para cortar um determinado circunexo (^) do resto do registro, que Para entender melhor isso, vamos anali-
pedao 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 intrprete de cada msica do
tos no Quadro 1: CD e a respectiva msica interpretada. $ head -1 musicas
Como d para ver, existem quatro Estes grupos so separados entre si por album 1^Artista1~Musica1: U
sintaxes distintas: na primeira (-c 1-5) dois-pontos (:) e o intrprete ser sepa- Artista2~Musica2
especifiquei uma faixa, na segunda rado do nome da msica por um til (~).
(-c -6) especifiquei todo o texto at Ento, para pegar os dados referentes Ento observe o que foi feito:
uma posio, na terceira (-c 4-) tudo de a todas as segundas msicas do arquivo
uma determinada posio em diante e musicas, devemos digitar: album 1^Artista1~Musica1: U
na quarta (-c 1,3,5,7,9), s as posies 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 no 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 ento ver o
arquivos com campos de tamanho xo, Ou seja, cortamos o segundo campo que aconteceu no segundo cut:
mas atualmente o que mais existe so z(-f de eld, campo em ingls) 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 intrpretes, deve-
delimitador. Vamos dar uma olhada no mos digitar: Agora, primeiro campo do delimitador
arquivo musicas que comeamos 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 raciocnio 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 exerccio prtico, 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-vrgula (lembre-se
[arquivo], onde PosIni a posio inicial, e
tituir, comprimir ou remover caracteres. que o ponto-e-vrgula serve para sepa-
PosFim a posio nal. Veja os exemplos:
Sua sintaxe segue o seguinte padro: rar diversos comandos em uma mesma
linha). Vou dar um exemplo simpli-
$ cat numeros
tr [opes] cadeia1 [cadeia2] cado, e idiota, de um script assim:
1234567890
0987654321
O comando copia o texto da entrada $ cat confuso
1234554321
padro (stdin), troca as ocorrncia dos echo leia Programao Shell U
9876556789
caracteres de cadeia1 pelo seu corres- Linux do Julio Cezar Neves U
pondente na cadeia2 ou troca mltiplas > livro;cat livro;pwd;ls;rm U
$ cut -c1-5 numeros
ocorrncias 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
opes do comando so mostradas na
98765
Tabela 1. $ confuso
Primeiro veja um exemplo bem bobo: leia Programao 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 ocorrncias da
letra o pela letra a. Suponha que em Mas nota de prova coisa sria (e nota
$ cut -c4- numeros
determinado ponto do meu script eu de dlar mais ainda) ento, para
4567890
pea ao operador para digitar s ou n entender o que o aluno havia feito, o
7654321
(sim ou no), e guardo sua resposta chamei e em sua frente digitei:
4554321
na varivel $Resp. Ora, o contedo de
6556789
$Resp pode conter letras maisculas $ tr ; \n < confuso
ou minsculas, e desta forma eu teria echo leia Programao 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. Ento 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 aps este comando eu teria certeza O cara cou muito desapontado, porque
1235890
de que o contedo de $Resp seria um em dois ou trs segundos eu des z a
0986321
s ou um n. Se o meu arquivo ArqEnt gozao que ele perdeu horas para fazer.
1235321
est todo em letras maisculas e desejo Mas preste ateno! Se eu estivesse em
9875789
pass-las para minsculas eu fao: uma mquina 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 diferena entre o resultado
Note que neste caso usei a notao A- de um comando date executado hoje e
Z para no escrever ABCDYZ. Outro outro executado h duas semanas:
$ cat musicas
tipo de notao que pode ser usada so
album 1^Artista1~Musica1:U
as escape sequences (como eu traduzi- Sun Sep 19 14:59:54 2004
Artista2~Musica2
ria? Seqncias de escape? Meio sem Sun Sep 5 10:12:33 2004
album 2^Artista3~Musica3:U
sentido, n? Mas v l) que tambm
Artista4~Musica4
so reconhecidas por outros comandos Notou o espao extra aps o Sep na
album 3^Artista5~Musica5:U
e tambm na linguagem C, e cujo signi- segunda linha? Para pegar a hora eu
Artista6~Musica5
cado 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 opo -d do tr remove do arquivo E veja tambm o date:
seguinte: todas as ocorrncias 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 temporrio e posterior-
mente renomeei-o para o nome original. Repare que o ms e o dia esto no
Isto porque existem 2 caracteres em Uma observao: em um sistema Unix mesmo formato em ambos os coman-
branco antes do 5 (dia). Ento o ideal eu deveria digitar: dos. Ora, se em algum registro do who
seria transformar os espaos em branco eu no encontrar a data de hoje, sinal
consecutivos em somente um espao $ tr -d \015 < ArqDoDOS.U que o usurio est logado h mais
para poder tratar os dois resultados do txt > /tmp/$$ de um dia, j que ele no pode ter se
comando date da mesma forma, e isso logado amanh Ento vamos guar-
se faz assim: Uma dica: o problema com os termina- dar o pedao que importa da data de
dores de linha (CR/LF) s aconteceu hoje para depois procur-la na sada do
$ date | tr -s porque a transferncia do arquivo foi comando who:
Sun Sep 5 10:12:33 2004 feita no modo binrio (ou image), Se
antes da transmisso do arquivo tivesse $ Data=$(date | cut -f 2-3 U
E agora eu posso cortar: sido estipulada a opo ascii do ftp, isto -d )
no teria ocorrido.
$ date | tr -s | cut -f 4 U Olha, depois desta dica t comeando a Eu usei a construo $(...), para prio-
-d gostar deste tal de shell, mas ainda tem rizar a execuo dos comandos antes
10:12:33 muita coisa que no consigo fazer. de atribuir a sua sada varivel Data.
Pois , ainda no te falei quase nada Vamos ver se funcionou:
Olha s como o Shell est quebrando o sobre programao em shell, ainda
galho. Veja o contedo de um arquivo tem muita coisa para aprender, mas $ echo $Data
baixado de uma mquina 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 esto logadas h mais que no possuem esta data.
DOS/Win e foi^M$ de um dia no seu servidor?
baixado por um^M$ Claro que no! 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 no me explicou comando grep, estou certo?
Dica: a opo -v do cat mostra os carac- como funcionam. - Deixa eu tentar Certssimo! S que eu tenho que usar o
teres de controle invisveis, com a nota- mudar um pouco a sua lgica e traz- grep com aquela opo 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 no encon-
a respectiva letra. A opo -e mostra o antes melhor tomarmos um chope. trou a cadeia. Voc se lembra que
nal da linha como um cifro ($). Agora que j molhei a palavra, vamos opo essa?
Isto ocorre porque no DOS o m dos resolver o problema que te propus. Veja Claro, a -v
registros indicado por um Carriage como funciona o comando who: Isso! T cando bom! Vamos ver:
Return (\r Retorno de Carro, CR) e
um Line Feed (\f Avano de Linha, ou $ who $ who | grep -v $Data
LF). No Linux porm o 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$ Opo Signicado Viu? No foi necessrio usar comando
foi gerado pelo$ condicional, at porque o nosso
-s Comprime n ocorrncias de
DOS/Rwin e foi$ cadeia1 em apenas uma
comando condicional, o famoso if, no
baixado por um$ testa condio, mas sim instrues.
-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 Seqncia Signicado Octal then
$ echo $? \t Tabulao \011 echo Usuario \$1\ j U
0 \n Nova linha \012 existe
$ ls ArqInexistente <ENTER> else
ls: ArqInexistente: No such U \v Tabulao \013 if useradd $1
le or directory Vertical then
$ echo $? \f Nova \014 echo Usurio \$1\ U
1 Pgina includo em /etc/passwd
$ who | grep jneves \r Incio 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 $?
0
$ who | grep juliana j existe
$ echo $? else Vamos test-lo como um usurio normal :
1 if useradd $1
then $ incusu ZeNinguem
O que que esse $? faz a? Algo come- echo Usurio \$1\ U ./incusu[6]: useradd: not found
ado por cifro ($) parece ser uma vari- includo em /etc/passwd Problemas no cadastramento. U
vel, certo? Sim uma varivel que else Voc root?
contm o cdigo de retorno da ltima echo Problemas no U
instruo executada. Posso te garan- cadastramento. Voc root? Aquela mensagem de erro no deveria
tir que se esta instruo foi bem suce- aparecer! Para evitar isso, devemos
dida, $? ter o valor zero, caso contrrio redirecionar a sada 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 so nal ca assim:
testar esta varivel. Ento vamos ver a comando grep e esta a sua nalidade.
sua sintaxe: Caso o if seja bem sucedido, ou seja, o $ cat incusu
usurio (cujo nome est em $1) foi #!/bin/bash
if cmd encontrado em /etc/passwd, os coman- # Verso 3
then dos do bloco do then sero executados if grep ^$1 /etc/passwd > U
cmd1 (neste exemplo, apenas o echo). Caso /dev/null
cmd2 contrrio, as instrues do bloco do else then
cmdn sero 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 usurio em /etc/passwd, ou exibindo if useradd $1 2> /dev/null
cmdm uma mensagem de erro, caso contrrio. then
Executar o programa e passe como echo Usurio \$1\ U
parmetro um usurio j cadastrado: includo 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?
sero executados, caso contrrio, os /home/jneves:/bin/ U
comandos do bloco opcional do else bash
(cmd3, cmd4 e cmdm) sero executados. Usuario jneves ja existe
O bloco do if terminando com um . Depois disso, vejamos o comportamento
Vamos ver na prtica 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 sada do comando
rios no arquivo /etc/passwd: grep. Para evitar que isso acontea, $ incusu botelho
devemos desviar a sada para /dev/null. Usurio botelho incluido em U
$ cat incusu O programa ca assim: /etc/passwd
#!/bin/bash
# Verso 1 $ cat incusu E novamente:
if grep ^$1 /etc/passwd #!/bin/bash
then # Verso 2 $ incusu botelho
echo Usuario \$1\U if grep ^$1 /etc/passwd > U Usurio botelho j existe

88 Outubro 2004 www.linuxmagazine.com.br

Papo de Botequim LINUX USER

Lembra que eu falei que ao longo dos no incio da cadeia e cifro ($) no m, Como voc viu, o programa melho-
nossos papos e chopes os nossos pro- servem para testar se o parmetro (o rou um pouquinho, mas ainda no est
gramas iriam se aprimorando? Ento 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 msicas executar nosso programa novamente, car cada vez melhor.
na "CDTeca": mas desta vez passamos como parme- Entendi tudo que voc me explicou,
tro um lbum j cadastrado, pra ver o mas ainda no sei como fazer um if
$ cat musinc que acontece: para testar condies, 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 condies. 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 no cadastrado: palavra. Vamos parar por aqui e na
echo Este lbum j est U prxima 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 tambm j t cando zonzo e assim
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 xar o que voc aprendeu, tente
o em relao verso anterior. Antes album 3^Artista5~Musica5: U fazer um scriptizinho para informar se
de incluir um registro (que na verso Artista6~Musica5 um determinado usurio, cujo nome
anterior poderia ser duplicado), testa- album 4^Artista7~Musica7: U ser passado como parmetro, est
mos se o registro comea (^) e termina Artista8~Musica8 logado no sistema ou no.
($) de forma idntica ao parmetro album 5^Artista9~Musica9: U A Chico! traz mais dois chopes pra
lbum passado ($1). O circunexo (^) 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 garon j perdeu a conta das cervejas, e o assunto no acaba. Desta
Dave Hamilton - www.sxc.hu

vez vamos aprender a testar os mais variados tipos de condies,

para podermos controlar a execuo de nosso programa de acordo

com a entrada fornecida pelo usurio. POR JULIO CEZAR NEVES

E a cara, tentou fazer o exerccio


que te pedi em nosso ltimo
encontro?
Claro que sim! Em programao, se
voc no treinar no 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 sada padro e uma sada de erros
(o grep uma das poucas excees:
ele no exibe uma mensagem de erro
me pediu um script para informar se Realmente funcionou. Passei meu quando no acha uma cadeia de carac-
um determinado usurio, cujo nome nome de usurio como parmetro e teres) e devemos redirecion-las para o
ser passado como parmetro para o ele disse que eu estava logado, porm buraco negro quando necessrio.
script, est logado (arghh!) ou no. ele imprimiu uma linha extra, que eu Bem, agora vamos mudar de assunto:
Fiz o seguinte: no pedi, que a sada do comando na ltima vez que nos encontramos
who. Para evitar que isso acontea, aqui no botequim, quando j estvamos
$ 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 condies. Para isso, usamos
#!/bin/bash ento como caria: o comando test
# Pesquisa se um usurio est
# logado ou no $ cat logado Testes
#!/bin/bash Todos estamos acostumados a usar o
if who | grep $1 # Pesquisa se uma pessoa est if para testar condies, e estas so
then # logada ou no (verso 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 no est no pedao echo $1 est logado Tabela 1 Opes do
else test para arquivos
echo $1 no est no pedao Opo Verdadeiro se
Calma rapaz! J vi que voc chegou -e arq arq existe
cheio de teso. 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 diretrio
-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 execuo
resultados do seu programa: chico no est no pedao

84 edio 04 www.linuxmagazine.com.br

Papo de Botequim LINUX USER

Tabela 2 Opes do test No exemplo, testei a existncia do cadeia de caracteres 01 realmente


para cadeias de caracteres diretrio lmb. Se no existisse (else), ele diferente de 1. Porm, a coisa muda
seria criado. J sei, voc vai criticar a de gura quando as variveis so testa-
Opo Verdadeiro se:
minha lgica dizendo que o script no das numericamente, j que o nmero 1
-z cadeia Tamanho de cadeia zero
est otimizado. Eu sei, mas queria que igual ao nmero 01.
-n cadeia Tamanho de cadeia maior que zero
cadeia A cadeia cadeia tem tamanho maior que zero voc o entendesse assim, para ento Para mostrar o uso dos conectores -o
c1 = c2 Cadeia c1 e c2 so idnticas poder usar o ponto-de-espantao (!) (ou) e -a (e), veja um exemplo animal,
como um negador do test. Veja s: programado direto no prompt do Bash.
Me desculpem os zologos, mas eu
diferente de. Para testar condies em if test ! -d lmb no entendo nada de reino, lo, classe,
Shell Script usamos o comando test, s then ordem, famlia, gnero, espcie e outras
que ele muito mais poderoso do que mkdir lmb coisas do tipo, desta forma o que estou
aquilo com que estamos acostuma- chamando de famlia ou de gnero tem
dos. Primeiramente, veja na Tabela 1 cd lmb grande chance de estar total e comple-
as principais opes (existem muitas tamente incorreto:
outras) para testar arquivos em disco Desta forma o diretrio lmb seria
e na Tabela 2 as principais opes para criado somente se ele ainda no exis- $ Familia=felinae
teste de cadeias de caracteres. tisse, e esta negativa deve-se ao ponto $ Genero=gato
de exclamao (!) precedendo a opo $ if test $Familia = canidea U
Tabela 3 Opes do -d. Ao m da execuo desse fragmento -a $Genero = lobo -o $Familia = U
test para nmeros de script, com certeza o programa esta- felina -a $Genero = leo
ria dentro do diretrio lmb. Vamos ver > then
Opo Verdadeiro se Signicado
dois exemplos para entender a diferena > echo Cuidado
n1 -eq n2 n1 e n2 so iguais equal
na comparao entre nmeros e entre > else
n1 -ne n2 n1 e n2 no so iguais not equal
cadeias de caracteres. > echo Pode passar a mo
n1 -gt n2 n1 maior que n2 greater than
n1 -ge n2 n1 maior ou igual a n2 greater or equal >
n1 -lt n2 n1 menor que n2 less than cad1=1 Pode passar a mo
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 famlia candea e (-a) do gnero lobo,
Pensa que acabou? Engano seu! Agora echo As variveis so iguais. ou (-o) da familia felina e (-a) do gnero
hora de algo mais familiar, as famosas else leo, seria dado um alerta, caso contr-
comparaes com valores numricos. echo As variveis so diferentes. rio a mensagem seria de incentivo.
Veja a Tabela 3, e some s opes j Ateno: Os sinais de maior (>) no
apresentadas os operadores da Tabela 4. incio das linhas internas ao if so os
Ufa! Como voc viu, tem coisa pra Executando o fragmento de programa prompts de continuao (que esto
chuchu, e o nosso if muito mais pode- acima, teremos como resultado: de nidos na varivel $PS2). Quando o
roso que o dos outros. Vamos ver em shell identica que um comando con-
uns exemplos como isso tudo funciona. As variveis so diferentes. tinuar na linha seguinte, automatica-
Testamos a existncia de um diretrio: mente ele coloca este caractere, at que
Vamos agora alter-lo um pouco para o comando seja encerrado.
if test -d lmb que a comparao seja numrica: 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
echo As variveis so iguais. $Familia = canideo -a $Genero = U
else ona -o $Genero = lobo
Tabela 4 echo As variveis so diferentes. > then
> echo Cuidado
Operador Finalidade > else
Parnteses () 0
E vamos execut-lo novamente: > echo Pode passar a mo
>
Exclamao ! 0
As variveis so iguais. Cuidado
-a 0
-o 0
Como voc viu, nas duas execues Obviamente a operao resultou em
obtive resultados diferentes, porque a erro, porque a opo -a tem precedncia


www.linuxmagazine.com.br edio 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 expresso: CDs que tenham a participao do ir car com a sintaxe semelhante das
Artista1 e do Artista2, no 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
ona egrep tambm resolve isso para ns. daqui para a frente.
Veja como: Se voc pensa que acabou, est muito
Que foi avaliada como falsa, retor- enganado. Preste ateno 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 especco) o prprio
Combinao 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 so expresso regular, na qual a barra ver-
-o, e para que uma srie de expresses tical (|) trabalha como um ou lgico e Ou seja, quando o conector e e a
conectadas entre si por diversos ou os parnteses so usados para limitar primeira condio verdadeira, o resul-
lgicos seja verdadeira, basta que uma a amplitude deste ou. J no grep da tado nal pode ser verdadeiro ou falso,
delas o seja. A expresso nal resultou linha seguinte, a palavra Artista deve dependendo da segunda condio; j no
como VERDADEIRO e o then foi exe- ser seguida por um dos valores da lista conector ou, caso a primeira condio
cutado de forma errada. Para que isso formada pelos colchetes ([]), isto , 1 ou 2. seja verdadeira, o resultado sempre ser
volte a funcionar faamos o seguinte: verdadeiro. Se a primeira for falsa, o resul-
T legal, eu aceito o argumento, o if tado depender da segunda condio.
$ 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 ns, interpretador no so bobos e esto
\($Genero = ona -o $Genero = U essa construo de if test ... muito sempre tentando otimizar ao mximo
lobo\) esquisita, pouco legvel. os algoritmos. Portanto, no caso do
> then , voc tem razo, eu tambm no conector e, a segunda condio no ser
> echo Cuidado gosto disso e acho que ningum 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 mo incorporou outra sintaxe, que substi- com o ou, a segunda ser executada
> tui o comando test. somente caso a primeira seja falsa.
Pode passar a mo Aproveitando-se disso, uma forma
Para isso vamos pegar aquele exem- abreviada de fazer testes foi criada. O
Desta forma, com o uso dos parn- plo para fazer uma troca de diretrios, conector e foi batizado de && e o ou de
teses agrupamos as expresses com o que era assim: ||. Para ver como isso funciona, vamos
conector -o, priorizando a execuo e us-los como teste no nosso velho
resultando em VERDADEIRO -a FALSO. if test ! -d lmb exemplo de troca de diretrio, que em
Para que seja VERDADEIRO o resul- then sua ltima verso estava assim:
tado de duas expresses ligadas pelo mkdir lmb
conector -a, necessrio que ambas if [ ! -d lmb ]
sejam verdadeiras, o que no o caso cd lmb then
do exemplo acima. Assim, o resultado mkdir lmb
nal foi FALSO, sendo ento o else cor- e utilizando a nova sintaxe, vamos faz-
retamente executado. lo assim: cd lmb
Se quisermos escolher um CD que
tenha faixas de 2 artistas diferentes, nos if [ ! -d lmb ] O cdigo acima tambm 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 [ ! -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 Tambm podemos retirar a negao (!):
substitudo por um par de colchetes ([]),
$ grep Artista1 musicas | grep U separados por espaos em branco dos [ -d lmb ] || mkdir lmb
Artista2 argumentos, o que aumentar enorme- cd dir

86 edio 04 www.linuxmagazine.com.br

Papo de Botequim LINUX USER

Tabela 6 diretrio para dentro dele. Para execu- sempre estar dentro de lmb, desde que
tar mais de um comando dessa forma, tenha permisso para entrar neste dire-
Caractere Signicado
necessrio fazer um grupamento de trio, permisso para criar um subdire-
* Qualquer caractere ocorrendo zero ou
comandos, o que se consegue com o trio dentro de ../lmb, que haja espao
mais vezes
? Qualquer caractere ocorrendo uma vez
uso de chaves ({}). Veja como seria o em disco suciente...
[...] Lista de caracteres modo correto: Vejamos um exemplo didtico: depen-
| ou lgico dendo do valor da varivel $opc o script
cd lmb || dever executar uma das opes a
{ seguir: incluso, excluso, alterao ou
No primeiro caso, se o primeiro mkdir lmb encerrar sua execuo. Veja como ca-
comando (o test, que est represen- cd lmb ria o cdigo:
tado pelos colchetes) for bem sucedido, }
isto , se o diretrio lmb no existir, o if [ $opc -eq 1 ]
comando mkdir ser executado porque Ainda no est legal porque, caso o then
a primeira condio era verdadeira e o diretrio no 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
diretrio lmb existia (no anterior testa- cd lmb 2> /dev/null || exclusao
mos se ele no existia) e, caso isso fosse { elif [ $opc -eq 3 ]
verdade, o mkdir no 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 opo entre U
mal sucedido, o diretrio lmb seria seguro a que me re ro diz respeito ao 1 e 4
criado mas no seria feita a mudana de fato de que ao nal da execuo voc


www.linuxmagazine.com.br edio 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 opes
2) exclusao ;; de mascaramento do resultado. Neste
#!/bin/bash
3) alteracao ;; comando, a formatao comea com
# Programa bem educado que
4) exit ;; um sinal de mais (+) e os caracteres de
# d bom-dia, boa-tarde ou
*) echo Digite uma opo U formatao vm aps um sinal de per-
# boa-noite conforme a hora
entre 1 e 4 centagem (%), assim o %H signica 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 opo, isto esmiuar a resoluo:
1[2-7] ) echo Boa Tarde
, se o asterisco atende a qualquer coisa,
;;
ento servir para qualquer coisa que 0? | 1 [01] Zero seguido de qualquer
* ) echo Boa Noite
no 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
vrgula no necessrio antes do esac. com 01, 02, ... 09, 10 e 11;
exit
Vamos agora fazer um script mais 1 [2-7] Signica 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 * Signica tudo o que no casou com
forma mais curta de else if. Essa uma veja estes comandos: nenhum dos padres anteriores.
sintaxe vlida e aceita, mas poderamos
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 exerccio para voc fazer em casa e
19 me dar a resposta da prxima vez em
case $var in que nos encontrarmos aqui no bote-
padrao1) cmd1 quim, t legal?
cmd2 Beleza!
cmdn ;; o seguinte: faa um programa que
padrao2) cmd1 receba como parmetro 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 cpia
esac de backup do arquivo caso algum
faa alteraes indevidas. Obvia-
Onde a varivel $var comparada aos mente, voc far as crticas neces-
padres padrao1, ..., padraon. Caso um srias, como vericar se foi passado
dos padres corresponda varivel, o um parmetro, se o arquivo indicado
bloco de comandos cmd1, ..., cmdn cor- existe... En m, o que te der na telha e
respondente executado at encontrar voc achar que deva constar do script.
um duplo ponto-e-vrgula (;;), quando Deu pra entender?
o uxo do programa ser interrompido Hum, hum...
e desviado para instruo imediata- Chico, traz mais um, sem colarinho!
mente aps o comando esac (que, caso
no tenham notado, case ao contrrio. Julio Cezar Neves
Ele indica o m do bloco de cdigo, da Analista de Suporte
de Sistemas desde
SOBRE O AUTOR

mesma forma que ot comando indica


1969 e trabalha com
o m de um if). Unix desde 1980,
Na formao dos padres, so aceitos quando participou
os caracteres mostrados na Tabela 6. do desenvolvimento
Para mostrar como o cdigo 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 cdigo neves@gmail.com
com if ... elif ... else ... .

88 edio 04 www.linuxmagazine.com.br

Papo de Botequim LINUX USER

Curso de Shell Script

Papo de
Botequim V
Blocos de cdigo e laos (ou loops, como preferem alguns)
Dave Hamilton - www.sxc.hu

so o tema do ms em mais uma lio de nosso curso de Shell

Script. Garom, salta uma boa redondinha, que t a m de

refrescar o pensamento! POR JULIO CEZAR NEVES


F ala cara! E as idias esto em
ordem? J fundiu a cuca ou voc
ainda agenta mais Shell?
At agora j vimos alguns blocos de
cdigo, como quando te mostrei um
exemplo para fazer um cd para dentro
Gento! T gostando muito! Gostei de um diretrio:
tanto que at caprichei no exerccio
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 parmetro o nome mkdir lmb # Verica se algum parmetro 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 cdigo. <arquivo>
que como voc fez. Tambm nesse exerccio que acabamos exit 1
Beleza, d uma olhada no quadro 1 de ver, em que salvamos o arquivo antes
, beleza! Mas me diz uma coisa: por de edit-lo, existem vrios blocos de Arq=$1
que voc terminou o programa com cdigo compreendidos entre os coman- # Caso o arquivo no exista, no
um exit 0? dos then e do if. Um bloco de cdigo # h cpia a ser salva
Eu descobri que o nmero aps o tambm pode estar dentro de um case if [ ! -f $Arq ]
exit indica o cdigo de retorno do ou entre um do e um done. then
programa (o $?, lembra?) e assim, Pera, Julio, que do e done so esses? vi $Arq
como a execuo foi bem sucedida, No me lembro de voc ter falado exit 0
ele encerra com o $?=0. Porm, se nisso, e olha que estou prestando
voc observar, ver que caso o pro- muita ateno... # Se eu no puder alterar o
grama no tenha recebido o nome do Pois , ainda no tinha falado porque #arquivo, vou usar o vi para que?
arquivo ou caso o operador no tenha no havia chegado a hora certa. if [ ! -w $Arq ]
permisso de gravao nesse arquivo, Todas as instrues de loop ou lao then
o cdigo de retorno ($?) seria dife- executam os comandos do bloco com- echo Voc no tem permisso U
rente do zero. preendidos entre um do e um done. As de escrita em $Arq
Grande garoto, aprendeu legal, mas instrues de loop ou lao so for, while exit 2
bom deixar claro que exit 0, simples- e until , que sero explicadas uma a
mente exit ou no colocar exit produ- uma a partir de hoje. # J que est tudo OK, vou
zem igualmente um cdigo de retorno # salvar a cpia e chamar o vi
($?) igual a zero. Agora vamos falar O comando For cp -f $Arq $Arq~
sobre as instrues de loop ou lao, 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 cdigo. mas o que voc no sabe que o for,


www.linuxmagazine.com.br edio 05 89

LINUX USER Papo de Botequim

que uma instruo intrnseca do Shell Ento vamos execut-lo: $ echo $IFS | od -h
(isso signica que o cdigo fonte do 0000000 0920 0a0a
comando faz parte do cdigo fonte do $ testefor1 0000004
Shell, ou seja, em bom programs um ArqDoDOS.txt1:confuso:incusu:
built-in), muito mais poderoso que os logado:musexc:musicas:musinc: Isto , mandei a varivel (protegida
seus correlatos das outras linguagens. muslist:$ da interpretao do Shell pelas aspas)
Vamos entender a sua sintaxe, pri- para um dump hexadecimal (od -h). O
meiro em portugus 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 asterstico) em uma lista de arqui-
para var em val1 val2 ... valn vos separados por espaos em branco. Tabela 1: Resultado do od -h
faa Quando o for viu aquela lista, disse:
cmd1 Opa, listas separadas por espaos Valor Hexadecimal Signicado

cmd2 comigo mesmo! 09 <TAB>

cmdn O bloco de comandos a ser executado 20 <ESPAO>


feito era somente o echo, que com a opo -n 0a <ENTER>
listou a varivel $Arq seguida de dois-
Onde a varivel var assume cada um pontos (:), sem saltar a linha. O cifro
dos valores da lista val1 val2 ... valn e, ($) do nal da linha da execuo o O ltimo 0a foi proveniente do
para cada um desses valores, executa o prompt, que permaneceu na mesma <ENTER> dado ao nal do comando.
bloco de comandos formado por cmd1, linha tambm em funo da opo -n. Para melhorar a explicao, vamos ver
cmd2 e cmdn. Agora que j vimos o Outro exemplo simples (por enquanto): isso de outra forma:
signicado da instruo em portugus,
vejamos a sintaxe correta: $ cat testefor2 $ echo :$IFS: | cat -vet
#!/bin/bash : ^I$
for var in val1 val2 ... valn # 2o. Programa didtico para :$
do # entender o for
cmd1 for Palavra in Linux Magazine U No comando cat, a opo -e repre-
cmd2 do Brasil senta o <ENTER> como um cifro ($)
cmdn do e a opo -t representa o <TAB> como
done echo $Palavra um ^I. Usei os dois-pontos (:) para mos-
done trar o incio e o m do echo. E dessa
Vamos aos exemplos, para entender forma, pudemos notar que os trs carac-
direito o funcionamento deste comando. E executando temos: teres esto presentes naquela varivel.
Vamos escrever um script para listar Agora veja voc: traduzindo, IFS sig-
todos os arquivos do diretrio, separa- $ testefor2 nica separador entre campos. Uma vez
dos por dois-pontos, mas antes veja isso: Linux entendido isso, eu posso a rmar que o
Magazine comando for no usa apenas listas sepa-
$ echo * do radas por espaos em branco, mas sim
ArqDoDOS.txt1 confuso incusu Brasil pelo contedo da varivel $IFS, cujo
logado musexc musicas musinc valor padro so os caracteres que aca-
muslist Como voc viu, esse exemplo bamos de ver. Para comprovarmos isso,
to 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 bsico do for. Veja s a fora do recebe o nome do artista como parme-
arquivos do diretrio e o comando echo comando: ainda estamos na primeira tro e lista as msicas 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 atrs nosso arquivo musicas:
ver o problema a que nos propusemos: eu havia falado que o for usava listas
separadas por espaos em branco, mas $ cat musicas
$ cat testefor1 isso uma meia-verdade, s para facili- album 1^Artista1~Musica1: U
#!/bin/bash tar a compreenso. Na verdade, as listas Artista2~Musica2
# 1o. Programa didtico para no so obrigatoriamente separadas por album 2^Artista3~Musica3: U
# entender o for espaos. Mas antes de prosseguir, pre- Artista4~Musica4
for Arq in * ciso te mostrar como se comporta uma album 3^Artista5~Musica5: U
do varivel 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 contedo: Artista1~Musica3

90 edio 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 no 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 parmetros passados), que
$ cat listartista a melhor soluo, mas nesse caso eu for var
#!/bin/bash teria que modicar a crtica dos par- do
# Dado um artista, mostra as metros e o grep. A nova verso no cmd1
# suas msicas seria se eu passei um parmetro, 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 aps a substituio do $*
passado um parmetro pelos parmetros: U, sem o in, como ele vai saber que
exit 1 valor assumir? Pois , n? Esta constru-
echo $ArtMus | grep perereca U o, primeira vista, parece esquisita,
IFS= & peteleca mas bastante simples. Neste caso, var
: assumir um a um cada parmetro 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 parmetro um
echo $ArtMus | grep $1 && U perereca & peteleca monte de msicas e liste seus autores:
echo $ArtMus | cut -f2 -d~
done Aqui adicionamos a opo -i para $ cat listamusica
que a pesquisa ignorasse maisculas e #!/bin/bash
O script, como sempre, comea tes- minsculas. As aspas foram inseridas # Recebe parte dos nomes de
tando se os parmetros foram passados para que o nome do artista fosse visto # msicas como parmetro e
corretamente, em seguida o IFS foi con- como uma s cadeia de caracteres. # lista os intrpretes. 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 incio (^) de # ex. Eu no sou cachorro no
blocos Artistan~Musicam. Desta forma, $ArtMus e que logo aps vem um til (~). # Churrasquinho de Me
a varivel $ArtMus ir receber cada um preciso redirecionar a sada do grep para / #
desses blocos do arquivo (repare que o dev/null para que os blocos no 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 parmetro ($1) no bloco, o $ cat listartista [musica2] ... [musican]
segundo cut listar somente o nome da #!/bin/bash exit 1
msica. Vamos executar o programa: # Dado um artista, mostra as
# 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 {
IFS= echo No U
pa! Aconteceram duas coisas inde- : encontrada
sejveis: os blocos tambm foram lista- for ArtMus in $(cut -f2 -d^ U continue
dos, e a Musica10 idem. Alm do mais, musicas) }
o nosso arquivo de msicas est muito do for ArtMus in $(echo $Str U
simples: na vida real, tanto a msica echo $ArtMus | grep -i U | cut -f2 -d^)
quanto o artista tm 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~
(no gosto nem de dar a idia com receio done
que isso se torne realidade). Nesse caso, O resultado : done


www.linuxmagazine.com.br edio 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 exerccio com uma crtica sobre corpo do for e passou para o bloco de
os parmetros recebidos, em seguida for i in $(seq 0 3 9) cdigo; repare tambm que, quando
fizemos um for em que a varivel do usei o let, no foi necessrio iniciali-
$Musica receber cada um dos parme- echo -n $i zar a varivel $i. Veja s os comandos a
tros passados, colocando em $Str todos done seguir, digitados diretamente no prompt,
os lbuns que contm as msicas 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
esto em $Str e lista cada artista que uma sintaxe muito semelhante ao for da $ let j++
toca aquela msica. 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 varivel $j sequer existia e
Eginha Pocot cmd1 no primeiro let assumiu o valor 0 (zero)
musica3 cmd2 para, aps o incremento, ter o valor 1.
Artista3 cmdn Veja s como as coisas cam simples:
Artista1 done
Musica4 for arq in *
Artista4 Onde var=ini signica que a vari- do
Eginha Pocot vel var comear de um valor inicial let i++
No encontrada ini; cond signica que o loop ou lao for echo $i -> $Arq
ser executado enquanto var no atingir done
A listagem cou feinha porque ainda a condio cond e incr signica o incre- 1 -> ArqDoDOS.txt1
no sabemos formatar a sada; mas mento que a varivel 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 nmero, com um determinado A varivel i partiu do valor inicial 1, o 11 -> testefor1
incremento, at alcanar uma condi- bloco de cdigo (aqui somente o echo) 12 -> testefor2
o?. E a que eu te respondo: Eu ser executado enquanto i for menor ou
no 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 prxima vez
sintaxe que vimos, como no exemplo: (e no no bloco de cdigo) no coloquei em que nos encontrarmos falaremos
um cifro ($) antes do i e a notao sobre outras instrues de loop, mas
for i in $(seq 9) para incrementar (i++) diferente do eu gostaria que at l voc zesse um
do que vimos at agora. O uso de parnte- 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 aritmtico 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 varivel i assumiu os valores intei- e a versatilidade do for, vamos fazer a tuar ao seu uso. No 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 opo -n do echo foi usada para parte do escopo do for, passando-a para
no saltar uma linha a cada nmero lis- o bloco de cdigo: 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 edio 05 www.linuxmagazine.com.br

LINUX USER Papo de Botequim

Curso de Shell Script

Papo de
Botequim VI
Blocos de cdigo e laos (ou loops, como preferem alguns)
Dave Hamilton - www.sxc.hu

so o tema do ms em mais uma lio de nosso curso de Shell

Script. Garom, salta uma boa redondinha, que t a m de

refrescar o pensamento! POR JULIO CEZAR NEVES

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


do comando for? Eu te deixei um
exerccio para treinar, se no me
engano era para contar a quantidade de
palavras de um arquivo... Voc fez?
Ou seja, o programa comea, como
sempre, vericando se a passagem de
parmetros 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 Expanso
Aritmtica (Arithmetic Expansion),
que acionada por uma construo da
Claro! T empolgado com essa lin- padro branco, TAB e ENTER, que forma $((expresso)) ou let expresso.
guagem! Eu z da forma que voc exatamente o que desejamos para sepa- No ltimo loop for usei a expanso
pediu, olha s... rar as palavras), incrementando a vari- aritmtica das duas formas, mas no
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 expresso pode ser uma das listadas
por favor. Um sem colarinho! na tabela 1.
Como eu ia dizendo, olha como eu z. $ cat ArqDoDOS.txt Mas voc pensa que o papo de loop
muito fcil... Este arquivo (ou lao) 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 pedaggico ftp mal feito. O comando while
# cuja funo 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. Supe-se que as sando esse arquivo como parmetro: linguagens. Nelas, o que normalmente
# palavras esto separadas ocorre que um bloco de comandos
# entre si por espaos, <TAB> $ contpal.sh ArqDoDOS.txt executado, enquanto (enquanto, em
# ou <ENTER>. O arquivo ArqDoDOS.txt tem 14 ingls, while) uma determinada
if [ $# -ne 1 ] palavras. condio for verdadeira.
then
echo uso: $0 /caminho/do/ U Funcionou legal! Tabela 1: Expresses no Shell
arquivo Se voc se lembra, Expresso Resultado
exit 2 em nossa ltima id++ id-- ps-incremento e ps-decremento de variveis
aula mostramos o ++id --id pr-incremento e pr-decremento de variveis
Cont=0 loop for a seguir: ** exponenciao
for Palavra in $(cat $1) */% multiplicao, diviso, resto da diviso (mdulo)
do for ((; i<=9;)) +- adio, subtrao

Cont=$((Cont+1)) do <= >= < > comparao


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

86 edio 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 programao 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? Ento a (10.2.4.144) sleep 5
sintaxe do comando ca assim: done
Isto , a cada 30 segundos a sada do echo Fim do Processo $1
while comando comando grep seria enviada para a tela,
do o que no legal, j que poluiria a tela Esse script bastante similar ao ante-
cmd1 do meu micro e a mensagem to 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 sada do ground para no prender o prompt mas
cmdn pipeline tem que ser redirecionada para o $! ser o do programa passado como
done o dispositivo /dev/null. parmetro, j que ele foi colocado em
background aps o monbg.sh propria-
e dessa forma, o bloco formado pelas $ cat logaute.sh mente dito. Repare tambm na opo -q
instrues cmd1, cmd2,... e cmdn exe- #!/bin/bash (quiet) do grep, que serve para faz-lo
cutado enquanto a execuo da instru- # Espero que a Xuxa no tenha trabalhar em silncio. 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 (alis chefe-chato uma redun- echo O xato se mandou, no U registros no arquivo musicas, mas
dncia, n?), ainda estava na sala dele, hesite, d exit e v a luta antes preciso te ensinar a pegar
que ca bem na minha passagem para a um dado da tela, e j vou avisando:
rua. Ele comeou a car 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 parme- 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 suciente
Ento voltei para a minha mesa e z, no tado em background e que me informe para resolver este nosso problema.
servidor, um script assim: do seu trmino. 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 varivel do sistema. inclusive como formatar tela, mas
#!/bin/bash Veja estes comandos executados direta- hoje estamos falando sobre loops. A
# Espero que a Xuxa no 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, no U [1]+ Done sleep 10 que voc quer que aparea escrito na
hesite, d exit e v luta $ echo $! tela. Quando o operador teclar tal dado,
16317 ele ser armazenado na varivel 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 "Ttulo do lbum: " Tit
enquanto o grep localizar a palavra somente para mostrar que a varivel $!
xefe na sada 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 especificao do nosso problema:
enquanto o chefe estiver logado (Argh!). listagem e repare, aps a linha do Done, faremos um programa que inicialmente
Assim que ele se desconectar do servi- que a varivel reteve o valor mesmo ler o nome do lbum e em seguida far
dor, o uxo do script sair do loop e te aps o trmino desse processo. um loop de leitura, pegando o nome da
mostrar a to ansiada mensagem de Bem, sabendo isso, j ca mais fcil msica e o artista. Esse loop termina
liberdade. Mas quando executei o script, monitorar qualquer processo em back- quando for informada uma msica com
adivinha o que aconteceu? ground. Veja s como: nome vazio, isto , quando o operador


www.linuxmagazine.com.br edio 06 87

LINUX USER Papo de Botequim

Dica until comando $cat chegada.sh


Leitura de arquivo signica ler um a um do #!/bin/bash
todos os registros, o que sempre uma cmd1 until who | grep julio
operao lenta. Fique atento para no usar cmd2 do
o while quando for desnecessrio. O Shell ... sleep 30
tem ferramentas como o sed e a famlia cmdn done
grep, que vasculham arquivos de forma done echo $(date "+ Em %d/%m s U
otimizada sem que seja necessrio o uso do %H:%Mh") > relapso.log
while para faz-lo registro a registro.
e dessa forma o bloco de comandos for-
mado pelas instrues cmd1, cmd2,... e Olha que safado! O cara estava mon-
der um simples <ENTER>. Para facili- cmdn executado at que a execuo da tando um log com os meus horrios de
tar a vida do operador, vamos oferecer instruo 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 msica anterior (j que normal que nam de forma antagnica, e isso muito ele quis dizer com isso?
o lbum seja todo do mesmo artista) at fcil 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 cou o programa. inimigo busca uma soluo para neu- quando julio for encontrado na sada
Nosso exemplo comea com a lei- traliz-la. Foi baseado nesse principio do comando who, isto , quando eu
tura do ttulo do lbum. Caso ele no 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 acontea, o comando sleep, que forma
o do programa. Em seguida um grep logaute.sh, um script para controlar o o bloco de instrues do until, colocar
procura, no incio (^) de cada registro meu horrio de chegada. o programa em espera por 30 segun-
de msicas, o ttulo 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
interpretao 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:
msicas do lbum, foi montado um loop
while simples, cujo nico destaque o Listagem 1
fato de ele armazenar o nome do intr-
$ cat musinc.sh
prete da msica anterior na varivel
#!/bin/bash
$oArt, que s ter o seu contedo alte-
# Cadastra CDs (versao 4)
rado quando algum dado for informado
#
para a varivel $Art, isto , quando no
clear
for teclado um simples ENTER para
read -p "Ttulo do lbum: " Tit
manter o artista anterior.
[ "$Tit" ] || exit 1 # Fim da execuo se ttulo 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, porm ainda nos falta
exit 1
bagagem para prosseguir. Depois que

aprendermos mais sobre isso, veremos
Reg="$Tit^"
essa instruo mais a fundo.
Cont=1

O comando until oArt=


while true
Este comando funciona de forma
do
idntica ao while, porm ao contrrio.
echo "Dados da trilha $Cont:"
Disse tudo mas no disse nada, n?
read -p "Msica: " 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; porm, o while
[ "$Art" ] && oArt="$Art" # Se vazio Art anterior
executa o bloco de instrues 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 diferena
echo "$Reg" >> musicas
fundamental. A sintaxe do comando
sort musicas -o musicas
praticamente a mesma do while. Veja:

88 edio 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 msicas seria ideal ciclo de programa,
que pudssemos cadastrar diversos compreendido
CDs de uma vez s. Na ltima verso entre um do e um
do programa isso no 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 verso, um loop maior foi adi- temos que colocar

cionado antes da leitura do ttulo, que um comando que
s terminar quando a varivel $Para aborte de forma
deixar de ser vazia. Caso o ttulo do c ont rolada esse
lbum no 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 uxo de execuo 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 idnticoexecuo do programa volte antes de j vimos rapidamente nos exemplos do
verso anterior. chegar ao done. Para isso, temos res- comando while) e continue, que funcio-
nam da forma mostrada na gura 1.
Listagem 2 O que eu no havia dito anterior-
mente que nas suas sintaxes genricas
$ cat musinc.sh
eles aparecem da seguinte forma:
#!/bin/bash
# Cadastra CDs (versao 5)
break [qtd loop]
#
Para=
e tambm:
until [ "$Para" ]
do
continue [qtd loop]
clear
read -p "Ttulo 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 iro atuar. Seu valor
Para=1 # Liguei ag de sada
por default 1.
else
Duvido que voc nunca tenha apa-
if grep "^$Tit\^" musicas > /dev/null
gado um arquivo e logo aps deu um
then
tabefe na testa se xingando porque
echo "Este lbum j est cadastrado"
no devia t-lo removido. Pois , na
exit 1
dcima vez que z esta besteira, criei

um script para simular uma lixeira,
Reg="$Tit^"
isto , quando mando remover um (ou
Cont=1
vrios) arquivo(s), o programa nge
oArt=
que deletou, mas no duro o que ele fez
while [ "$Tit" ]
foi mand-lo(s) para o diretrio /tmp/
do
LoginName_do_usuario. Chamei esse
echo Dados da trilha $Cont:
programa de erreeme e no arquivo /etc/
read -p "Msica: " Mus
prole 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 crticas aos
echo "$Reg" >> musicas
parmetros informados, mas como o
sort musicas -o musicas
script pode ter recebido diversos arqui-

vos a remover, a cada arquivo que no
done
se encaixa dentro do especicado h


www.linuxmagazine.com.br edio 06 89

LINUX USER Papo de Botequim

Listagem 3: erreeme.sh
$ cat erreeme.sh then
#!/bin/bash echo "$Arq nao existe."
# Erro=3
# Salvando cpia 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]" # Verica permisso de gravacao no diretrio
echo "O uso de metacaracteres e permitido. Ex.U if [ ! -w $DirOrig ]
erreeme arq*" then
exit 1 echo "Sem permisso no diretorio de $Arq"
Erro=4
continue # Volta para o comando for
# Varivel do sistema que contm o nome do usurio.
MeuDir="/tmp/$LOGNAME"
# Se no existir o meu diretrio sob o /tmp... # Se estou "esvaziando a lixeira"...
if [ ! -d $MeuDir ] if [ "$DirOrig" = "$MeuDir" ]
then then
mkdir $MeuDir # Vou cri-lo echo "$Arq cara sem copia de seguranca"
rm -i $Arq # Pergunta antes de remover
# Ser que o usurio removeu?
# Se no posso gravar no diretrio... [ -f $Arq ] || echo "$Arquivo removido"
if [ ! -w $MeuDir ] continue
then
echo "Impossivel salvar arquivos em $MeuDir. U
Mude as permisses..." # Guardo no m do arquivo o seu diretrio originalU
exit 2 para us-lo em um script de undelete
cd $DirOrig
pwd >> $Arq
# Varivel 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 nmero do erro para o cdigo
# Se este arquivo no existir... # de retorno
if [ ! -f $Arq ] exit $Erro

um continue, para que a seqncia volte faa-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 prximo 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 prximo chope ou j
perdo da m palavra) e tenta remover ar, pois no sei nem como comear... 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 no so mente fcil. para ser feito em, no Chico, traz mais um chope enquanto
removidos, no ? Ento, o continue foi mximo, 10 linhas. No se esquea 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 remoo de um arquivo, o o diretrio em que ele residia antes Suporte de Sistemas desde 1969 e tra-
programa continuar removendo os de ser removido. Tambm no se balha com Unix desde 1980, quando
outros que foram passados. esquea 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 no... ao Unix produzido pela Cobra Com-
putadores. Pode ser contatado no
que restaura o arquivo removido, no Tenha f, irmo, eu t te falando que
e-mail julio.neves@gmail.com
? Pois ento a vai vai um desao: mole! Qualquer dvida s passar

90 edio 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 informao se ela no puder ser apresentada
de forma atraente e que facilite a compreenso. 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. Garom, 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 varivel 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
pensao na tela preta, mas acho que $LOGNAME o nome do usurio logado, uma tela de entrada de dados.
nalmente consegui! Bem, pelo menos e $1 o primeiro parmetro que voc
nos testes que 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 cabea de cachorro! arquivo antes de mov-lo para o diretrio cionamento do cursor na tela. Alguns
No bem assim. que programar em (de nido na varivel Dir). O comando parmetros podem no funcionar se o
Shell Script muito fcil, mas o que grep -v apaga essa linha, restaurando modelo de terminal de nido pela vari-
realmente importante so as dicas e o arquivo ao estado original, e o manda vel de ambiente $TERM no suport-los.
macetes que no so triviais. As cor- de volta pra onde ele veio. A ltima linha A tabela 1 apresenta apenas os principais
rees que fao so justamente para o apaga da lixeira. Sensacional! Impe- parmetros e os efeitos resultantes, mas
mostr-los. Mas vamos pedir dois cho- cvel! 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 referncia [1].
script l na listagem 1. A Chico, traz Ento vamos l, chega de lesco-lesco Vamos fazer um programa bem besta
dois chopes! E no se esquea que um e bl-bl-bl, sobre o qu ns vamos e fcil para ilustrar melhor o uso desse
deles sem colarinho! falar hoje? comando. uma verso do famigerado
Al Mundo, s que dessa vez a frase
Listagem 1 restaura.sh ser escrita no centro da tela e em vdeo
reverso. Depois disso, o cursor voltar para
01 #!/bin/bash
a posio 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 #
explicao a 8, onde criamos a varivel
05
Coluna. O estranho ali aquele nmero
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/diretrio 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 diretrio
$ 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 edio 07


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

Tabela 1: Parmetros do tput e a linha 12 (echo $1) passaria a ser:

Parmetro Efeito echo $*


cup lin col CUrsor Position Posiciona o cursor na linha lin e coluna col. A origem (0,0) ca 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 vdeo reverso tudo sobre leitura. S no posso ensinar
a ler cartas e bzios porque se soubesse
smso Idntico ao anterior
estaria rico, num pub Londrino tomando
smul Sublinha os caracteres um scotch e no 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 denies de acordo com terminfo, ou seja, o ter- eu dei uma palhinha sobre o comando
minal volta ao comportamento padro denido pela varivel de ambiente $TERM read. Antes de entrarmos em detalhes,
veja s isso:
lines Informa a quantidade de linhas que compem a tela
cols Informa a quantidade de colunas que compem a tela $ read var1 var2 var3
el Erase Line Apaga a linha a partir da posio do cursor Papo de Botequim
ed Erase Display Apaga a tela a partir da posio do cursor $ echo $var1
Papo
il n Insert Lines Insere n linhas a partir da posio do cursor
$ echo $var2
dl n Delete Lines Remove n linhas a partir da posio do cursor
de
dch n Delete CHaracters Apaga n caracteres a partir da posio do cursor $ echo $var3
sc Save Cursor position Salva a posio do cursor Botequim
rc Restore Cursor position Coloca o cursor na posio marcada pelo ltimo sc $ read var1 var2
Papo de Botequim
$ echo $var1
Ahhh, melhorou! Ento agora sabemos essa construo devolve o nmero de Papo
que a construo ${#variavel} devolve caracteres do primeiro parmetro pas- $ echo $var2
a quantidade de caracteres da varivel. sado para o programa. Se o parmetro de Botequim
Assim sendo, vamos otimizar o nosso tivesse espaos em branco, seria preciso
programa para que ele escreva em vdeo coloc-lo entre aspas, seno o $1 leva- Como voc viu, o read recebe uma
reverso, no centro da tela (e indepen- ria em conta somente o pedao antes lista de parmetros separada por espa-
dente do nmero de caracteres) a cadeia do primeiro espao. Para evitar este os em branco e coloca cada item dessa
de caracteres passada como parmetro e aborrecimento, s substituir o $1 por lista em uma varivel. Se a quantidade
depois retorne o cursor posio em que $*, que como sabemos o conjunto de de variveis for menor que a quantidade
estava antes da execuo do script. Veja todos os parmetros. Ento a linha 8 de itens, a ltima varivel recebe o res-
o resultado na listagem 3. caria assim: tante deles. Eu disse lista separada por
Este script igual ao anterior, s que espaos em branco, mas agora que voc
trocamos o valor xo na varivel 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 (verso 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 posio 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 # Vdeo reverso 11 tput rev # Video reverso
12 echo Al Mundo 12 echo $1
13 tput sgr0 # Restaura o vdeo ao normal 13 tput sgr0 # Restaura o vdeo ao normal
14 tput rc # Restaura o cursor posio original 14 tput rc # Devolve o cursor posio original

88 abril 2005 edio 07


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

eu te apresentei quando falvamos 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 no " Nom || echo 'Eita moleza!'
nisso? Vamos testar: como um literal. Sob o Bash exis- Digite seu nome completo: Eita moleza!
tem diversas opes 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 opes. Para ler meu nome completo e mal tive tempo de
$ echo $var1 um campo Matrcula: teclar um J (aquele colado no Eita), mas
Papo de Botequim ele serviu para mostrar duas coisas:
$ echo $var2 # -n no salta linha P 1) O comando aps o par de barras verti-
$ echo $var3 $ echo -n "Matricula: "; read Mat cais (o ou or lgico, lembra-se?) ser
$ read var1 var2 var3 Matricula: 12345 executado caso a digitao no tenha
Papo:de:Botequim $ echo $Mat sido concluda no tempo estipulado;
$ echo $var1 12345 P 2) A varivel Nom permaneceu vazia. Ela
Papo s receber um valor quando o ENTER
$ echo $var2 Podemos simplicar as coisas usando for teclado.
de a opo -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 varivel $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 varivel 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 varivel $REPLY do Bash
julio:x:500:544:Julio C. Neves - 7070:U -p- Compl contm a ltima seqncia de caracteres
/home/julio:/bin/bash CEP: 12345-678$ digitada e me aproveitei disso para no
$ 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 opo -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 cifro ($) logo como se lem os dados dos arquivos.
/home/julio aps o ltimo algarismo digitado
/bin/bash necessrio porque o read no inclui Lendo arquivos
$ IFS="$oIFS" # Restaura o IFS por padro um caractere new line Como eu j havia lhe dito, e voc deve se
implcito, como o echo. lembrar, o while testa um comando e exe-
Como voc viu, a sada do grep foi Para ler s durante um determinado cuta um bloco de instrues enquanto esse
redirecionada para o comando read, limite de tempo (tambm 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 opo -e do echo foi usada voc tem permisso de leitura, o read s
ser mal sucedido quando alcanar o EOF
Tabela 2: Opes do read (End Of File Fim do Arquivo). Portanto,
podemos ler um arquivo de duas maneiras.
Opo Ao 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 concluda do
-s No exibe na tela os caracteres digitados. echo $Linha
done < arquivo

abril 2005 edio 07 89


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

A segunda redirecionando a sada Como voc viu, o script lista suas pr- que vou mostrar com um exemplo prtico.
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
nal, exibe o contedo da varivel $Ultimo. pare para que o operador possa ler o con-
cat arquivo | Repare, no entanto, que o contedo dessa tedo da tela, e que ela s continue depois
while read Linha varivel permanece vazio. U, ser que de o operador pressionar qualquer tecla.
do a varivel no foi atualizada? Foi, e isso Para no 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
Ento por que isso aconteceu? nmeros seqenciais. Veja:
Cada um dos processos tem suas van- Como eu disse, o bloco de instrues
tagens e desvantagens. O primeiro mais redirecionado pelo pipe (|) executado $ seq 30 > numeros
rpido e no necessita de um subshell em um subshell e, l, as variveis so $ cat 10porpag.sh
para assisti-lo mas, em contrapartida, o atualizadas. Quando esse subshell ter- #!/bin/bash
redirecionamento ca pouco visvel em mina, as atualizaes das variveis vo # Programa de teste para escrever
um bloco de instrues grande, o que para as profundezas do inferno junto com # 10 linhas e parar para ler
por vezes prejudica a visualizao do ele. Repare que vou fazer uma pequena # Verso 1
cdigo. O segundo processo traz a van- mudana 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 visualizao do as coisas passaro a funcionar na mais let ContLin++ # Contando...
cdigo mais fcil. Entretanto, o Pipe perfeita ordem: # -n para no 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. genrico criamos a varivel $ContLin (na
#!/bin/bash Ultimo="(vazio)" vida real, os registros no so somente
# readpipe.sh # Passa o script ($0) para o while nmeros seqenciais) e, quando testamos
# Exemplo de read passando um arquivo while read Linha se o resto da diviso era zero, mandamos
# por um pipe. do a sada para /dev/null, pra que ela no
Ultimo="(vazio)" Ultimo="$Linha" aparea 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 nmero 11 e a lista-
-#!/bin/bash- gem no parou no read. Toda a entrada do
Vamos ver o resultado de sua execuo: -# 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 nmero 11. Vamos
-#!/bin/bash- -- mostrar como deveria car o cdigo 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 - Verso 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 no saltar linha
-echo "-$Ultimo-"- echo -n "$Num "
-done- Bem, amigos da Rede Shell, para nali- ((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 edio 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 # Verso 3 acho que voc j est de saco cheio
mais seno o terminal corrente, expli- clear Num t no, pode continuar
citando desta forma que aquela leitura while read Num Se voc no estiver eu estou Mas j
seria feita do teclado e no do arquivo do que voc est to empolgado com o shell,
numeros. bom realar que isso no # Contando... vou te deixar um servio bastante sim-
acontece somente quando usamos o redi- ((ContLin++)) ples para voc melhorar a sua cdteca:
recionamento de entrada; se tivssemos 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
cuo do script: # para ler qualquer caractere ser digitado pelo operador.
read -n1 -p"Tecle Algo " < /dev/tty No se esqueam que, em caso de
$ 10porpag.sh # limpa a tela aps a leitura qualquer dvida 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 tambm para fazer uma propaganda:
digam aos amigos que quem estiver a
Isso est quase bom, mas ainda falta A mudana substancial feita neste exem- m de fazer um curso porreta de pro-
um pouco para car excelente. Vamos plo com relao quebra de pgina, j gramao 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 (-) trs, isto informar-se. At mais!
aumente o nmero 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. Informaes
que haja quebra de linha). No comando read tambm foi feita uma [1] Pgina ocial do Tput: http://www.cs.utah.edu/
alterao, inserido o parmetro -n1 para dept/old/texinfo/tput/tput.html#SEC4
$ cat 10porpag.sh ler somente um caractere qualquer, no
[2] Pgina ocial do Bash: http://www.gnu.org/
#!/bin/bash necessariamente um ENTER, e a opo software/bash/bash.html
# Programa de teste para escrever -p para exibir uma mensagem.

abril 2005 edio 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 funes e chamadas externas
os scripts cam menores, a manuteno mais fcil
e ainda por cima reaproveitamos cdigo.
por Jlio 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 z mas j sei
que voc vai querer molhar o bico primeiro, n?
passagem de parmetros para programas tambm valem para
funes, mas muito importante realar que os parmetros
S pra te contrariar, hoje no quero. Vai, mostra logo a passados para um programa no se confundem com aqueles
o que voc fez. que so passados para suas funes. Isso signica, por exem-
Poxa, o exerccio 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: funes internas.
, o programa t legal, t todo estruturadinho, mas gosta- Repare que as variveis $Msg, $TamMsg e $Col so de uso
ria de fazer alguns poucos comentrios: s para relembrar, restrito dessa rotina e, por isso, foram criadas como variveis
as seguintes construes: [ ! $Album ] && e [ $Musica ] locais. A razo simplesmente a economia de memria, j que
|| representam a mesma coisa, isto : no caso da primeira, ao sair da rotina elas sero devidamente detonadas, coisa que
testamos se a varivel $Album no (!) tem nada dentro, ento no aconteceria se eu no tivesse usado esse artifcio.
(&&) Na segunda, testamos se $Musica tem algum dado, A linha de cdigo que cria a varivel local Msg concatena ao
seno (||) texto recebido ($1) um parntese, a resposta padro ($2) em
Se voc reclamou do tamanho do programa, porque ainda caixa alta, uma barra, a outra resposta ($3) em caixa baixa
no te dei algumas dicas. Repare que a maior parte do script e naliza fechando o parntese. Uso essa conveno para, ao
para mostrar mensagens centraliza-
das na penltima 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
so s de advertncia. Isso um caso
03 # Cadastra CDs (versao 5)
tpico que pede o uso de funes, que
04 #
seriam escritas somente uma vez e
05 clear
executadas em diversos pontos do
06 LinhaMesg=$((`tput lines` - 3)) # Linha onde sero mostradas as msgs para o operador
script. Vou montar duas funes 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
Incluso de Msicas
resultado nal.
======== == =======
Chico! Agora traz dois chopes, um
Ttulo 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 funo
| orientar o preenchimento
de nida quando digitamos nome_da_
Nome da Msica:
funo () e todo o seu corpo est
Intrprete: # 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 funes os recebem da
11 tput cup 5 38; tput el # Posiciona e limpa linha
mesma forma, isto , so parmetros

86 maio 2005 edio 08


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

12 read Album mesmo tempo, mostrar as opes dis-


13 [ ! $Album ] && # Operador deu <ENTER> ponveis e realar a resposta oferecida
14 { como padro.
15 Msg=Deseja Terminar? (S/n) Quase no m da rotina, a resposta
16 TamMsg=${#Msg} recebida ($SN) convertida para caixa
17 Col=$(((TotCols - TamMsg) / 2)) # Centraliza msg na linha baixa (minsculas) de forma que no
18 tput cup $LinhaMesg $Col corpo do programa no precisemos
19 echo $Msg fazer esse teste. Veja na listagem 3
20 tput cup $LinhaMesg $((Col + TamMsg + 1)) como caria a funo 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 denir
23 [ $SN = N -o $SN = n ] && continue # $SN igual a N ou (-o) n? uma funo: no a chamamos, como
24 clear; exit # Fim da execuo no exemplo anterior, usando uma
25 } construo com a sintaxe nome_da_
26 grep ^$Album\^ musicas > /dev/null && funo (), mas sim como function
27 { nome_da_funo. Em nada mais ela
28 Msg=Este lbum j est cadastrado difere da anterior, exceto que, como
29 TamMsg=${#Msg} consta dos comentrios, usamos a
30 Col=$(((TotCols - TamMsg) / 2)) # Centraliza msg na linha varivel $* que, como j sabemos,
31 tput cup $LinhaMesg $Col representa o conjunto de todos os
32 echo $Msg parmetros passados ao script, para
33 read -n1 que o programador no 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 funo.
36 } Para terminar com esse bl-bl-bl,
37 Reg=$Album^ # $Reg receber os dados para gravao vamos ver na listagem 4 as alteraes
38 oArtista= # Varivel que guarda artista anterior no programa quando usamos o con-
39 while true ceito de funes:
40 do Repare que a estrutura do script
41 ((Faixa++)) segue a ordem Variveis Globais, Fun-
42 tput cup 7 38 es e Corpo do Programa. Esta estrutu-
43 echo $Faixa rao se deve ao fato de Shell Script ser
44 tput cup 9 38 # Posiciona para ler msica 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 funes,
48 Msg=Fim de lbum? (S/n)
uma varivel 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 funes devem ser declaradas
52 echo $Msg
antes do corpo do programa propria-
53 tput cup $LinhaMesg $((Col + TamMsg + 1)
mente dito. A explicao 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 funo
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 criao de fun-
59 tput cup 11 38 # Posiciona para ler Artista
es faz-las to genricas quanto
60 [ $oArtista ] && echo -n ($oArtista) # Artista anterior default
possvel, 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 funes que
64 tput cup 9 38; tput el # Apaga Msica da tela
acabamos de ver so bons exemplos,
65 tput cup 11 38; tput el # Apaga Artista da tela
pois difcil um script de entrada de
66 done
dados que no use uma rotina como
67 echo $Reg >> musicas # Grava registro no fim do arquivo
a MandaMsg ou que no interaja com
38 sort musicas -0 musicas # Classifica o arquivo
o operador por meio de algo seme-
69 done
lhante Pergunta.

maio 2005 edio 08 87


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

Conselho de amigo: crie um arquivo e anexe a ele cada fun- diretrio /home. S que assim que a execuo do script termi-
o nova que voc criar. Ao nal de algum tempo voc ter nou, o sub-shell foi para o belelu e, com ele, todo o ambiente
uma bela biblioteca de funes que lhe poupar muito tempo criado. Agora preste ateno no exemplo abaixo e veja como a
de programao. coisa muda de gura:

O comando source $ source script_bobo


Veja se voc nota algo de diferente na sada 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
No olhe a resposta no, volte a prestar ateno! 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 so automaticamente executados /home
quando voc se loga (ARRGGHH! Odeio
esse termo!) no sistema. Agora olhe nova- Listagem 2: Funo Pergunta
mente para a sada do comando ls e me
01 Pergunta ()
diga o que h de diferente nela.
02 {
Como eu disse, o .bash_profile exe-
03 # A funo recebe 3 parmetros na seguinte ordem:
cutado durante o logon, mas repare que ele
no tem nenhuma permisso de execuo. 04 # $1 - Mensagem a ser mostrada na tela
Isso acontece porque se voc o executasse 05 # $2 - Valor a ser aceito com resposta padro
como qualquer outro script careta, no m 06 # $3 - O outro valor aceito
de sua execuo 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 so 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, tambm 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 parmetro no 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 explicao em 453 palavras. Veja 17 echo $SN | tr A-Z a-z # A sada de SN ser em minscula
o scriptzinho a seguir: 18 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
19 return # Sai da funo
$ cat script_bobo 20 }
cd ..
ls
Listagem 3: Funo MandaMsg
Ele simplesmente deveria ir para o dire-
01 function MandaMsg
trio acima do diretrio atual. Vamos
02 {
executar uns comandos envolvendo o
03 # A funo recebe somente um parmetro
script_bobo e analisar os resultados:
04 # com a mensagem que se deseja exibir.
05 # para no obrigar o programador a passar
$ pwd
06 # a msg entre aspas, usaremos $* (todos
/home/jneves
07 # os parmetros, lembra?) e no $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 diretrio,
13 read -n1
por que no 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 funo
cutar o script tanto subiu que listou os
16 }
diretrios dos quatro usurios abaixo do

88 maio 2005 edio 08


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

Listagem 4: musinc6.sh 52 echo


Incluso de Msicas
01 $ cat musinc6
======== == =======
02 #!/bin/bash
Ttulo do lbum:
03 # Cadastra CDs (versao 6)
Faixa:
04 #
Nome da Msica:
05 # rea de variveis globais
Intrprete:
06 # Linha onde as mensagens sero 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 funes
57 [ ! $Album ] && # Operador deu <ENTER>
11 Pergunta ()
58 {
12 {
59 Pergunta Deseja Terminar s n
13 # A funo recebe 3 parmetros 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 execuo
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 gravao
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 sada de SN ser em minsculas
75 echo $Faixa
29 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
76 tput cup 9 38 # Posiciona para ler msica
30 return # Sai da funo
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 funo recebe somente um parmetro
81 # Agora s testo a caixa baixa
35 # com a mensagem que se deseja exibir;
82 [ $SN = n ] && continue
36 # para no obrigar o programador a passar
83 break # Sai do loop para gravar dados
37 # a msg entre aspas, usaremos $* (todos
84 }
38 # os parmetro, lembra?) e no $1.
85 tput cup 11 38 # Posiciona para ler Artista
39 local Msg=$*
86 # O artista anterior o padro
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 Msica 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 funo
94 # Grava registro no fim do arquivo
48 }
95 echo $Reg >> musicas
49 # O corpo do programa propriamente dito comea 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 edio 08 89


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

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


como parmetro 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 ta
05 # rea de variveis globais at o incio da explicao sobre este
06 LinhaMesg=$((`tput lines` - 3)) # Linha onde sero 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 comea aqui que sua incumbncia , logo aps o
09 clear login, preparar o ambiente de trabalho
10 echo para o usurio. Agora entendemos que
Incluso de Msicas por isso mesmo que ele executado
======== == ======= usando esse artifcio.
Ttulo 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 Msica:
gens e uma das mais usadas tratar
Intrprete: # Tela montada com um nico echo
funes 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 funo 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 execuo que assim podem ser chamados por
20 } qualquer outro programa, dessa forma
21 grep -iq ^$Album\^ musicas 2> /dev/null && reutilizando o seu cdigo.
22 { Por motivos meramente didticos, as
23 . mandamsg.func Este lbum j est cadastrado chamadas a pergunta.func e manda-
24 continue # Volta para ler outro lbum msg.func esto sendo feitas por source
25 } e por . (ponto) indiscriminadamente,
26 Reg=$Album^ # $Reg receber os dados de gravao embora eu prera o source que, por
27 oArtista= # Guardar artista anterior
ser mais visvel, melhora a legibili-
28 while true
dade do cdigo e facilita sua posterior
29 do
manuteno. Veja na listagem 6 como
30 ((Faixa++))
caram esses dois arquivos.
31 tput cup 7 38
Em ambos os arquivos, z somente
32 echo $Faixa
33 tput cup 9 38 # Posiciona para ler msica duas mudanas, que veremos nas
34 read Musica observaes a seguir. Porm, tenho
35 [ $Musica ] || # Se o operador tiver dado <ENTER>... mais trs observaes a fazer:
36 { 1. As variveis no esto 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 funes e, portanto, essas
40 } variveis 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 no est mais
43 read Artista presente, mas poderia estar sem alte-
44 [ $Artista ] && oArtista=$Artista rar em nada a lgica 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 Msica da tela
eventual erro por meio de um cdigo
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 so
50 sort musicas -o musicas # Classifica o arquivo
51 done
idnticos e signicam que a rotina foi
executada sem erros;

90 maio 2005 edio 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 cdigo de retorno
01 $ cat pergunta.func
o exit, mas a sada de uma rotina
02 # A funo recebe 3 parmetros na seguinte ordem:
externa no 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 execuo 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 varivel 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 esquea 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
no se envergonhe, exagere nos comen-
16 echo $SN | tr A-Z a-z # A sada de SN ser em minscula
trios, principalmente sobre a passagem
17 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
dos parmetros, para facilitar a manu-
18 $ cat mandamsg.func
teno e o uso dessa rotina por outros
19 # A funo recebe somente um parmetro
programas no futuro.
20 # com a mensagem que se deseja exibir;
Bem, agora voc j tem mais um
21 # para no obrigar o programador a passar
monte de novidades para melhorar
22 # a msg entre aspas, usaremos $* (todos
os scripts que zemos. Voc se lem-
23 # os parmetro, lembra?) e no $1.
bra do programa listartista.sh no qual
24 Msg=$*
voc passava o nome de um artista
25 TamMsg=${#Msg}
como parmetro e ele devolvia as suas
26 Col=$(((TotCols - TamMsg) / 2)) # Centraliza msg na linha
msicas? 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 rmar os conceitos que te pas-
30 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
sei, faa-o com a tela formatada e a
execuo em loop, de forma que ele
s termine quando receber um Enter no lugar do nome No se esquea: em caso de dvida 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 endereo
msicas 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 tambm para mandar minha propaganda: diga aos amigos que
devem ser passadas usando a rotina mandamsg.func que quem estiver a m de fazer um curso porreta de programao
acabamos de desenvolver. Chico, manda mais dois!! O meu em Shell deve mandar um e-mail para julio.neves@tecnohall.com.br
com pouca presso para informar-se. At mais!

Listagem 7: listartista.sh Informaes


01 $ cat listartista.sh [1] Bash, pgina ocial:
02 #!/bin/bash http://www.gnu.org/software/bash/bash.html
03 # Dado um artista, mostra as suas musicas
[2] Manual de referncia 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 edio 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 formatao de cadeias de caracteres,
conhecer as principais variveis do Shell e nos aventurar no (ainda)
desconhecido territrio da expanso de parmetros. E d-lhe chope!
por Jlio Cezar Neves

T
bom, j sei que voc vai querer chope antes de comear, Bom, a resposta "mais ou menos". Com estes comandos
mas t to a m de te mostrar o que z que j vou pedir a voc escreve 90% do que precisa, porm se precisar escrever
rodada enquanto isso. A Chico, manda dois! O dele sem algo formatado eles lhe daro muito trabalho. Para formatar a
colarinho pra no deixar cheiro ruim nesse bigodo sada veremos agora uma instruo muito mais interessante,
Enquanto o chope no chega, deixa eu te lembrar o que a printf. Sua sintaxe a seguinte:
voc me pediu na edio passada: era para refazer
o programa listartista com a tela formatada e exe- Listagem 1: mandamsg.func e pergunta.func
cuo em loop, de forma que ele s termine quando mandamsg.func
receber um [ENTER] sozinho como nome do artista. 01 # A funo recebe somente um parmetro
Eventuais mensagens de erro e perguntas feitas ao 02 # com a mensagem que se deseja exibir.
usurio deveriam ser mostradas na antepenltima 03 # Para no 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 parmetro, lembra?) e no $1.
06 Msg="$*"
durante nosso papo na edio 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 caram como 09 tput cup $LinhaMesg $Col
na listagem 1. E na listagem 2 voc tem o grando, 10 read -n1 -p "$Msg "
nossa verso refeita do listaartista.
Puxa, voc chegou com a corda toda! Gostei da pergunta.func
forma como voc resolveu o problema e estruturou 01 # A funo recebe 3 parmetros 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 padro
o cou muito legal e voc explorou bastante as
04 # $3 - O outro valor aceito
opes 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, no com o
13 SN=$(echo $SN | tr A-Z a-z) # A sada de SN ser em minscula
comando echo e com os redirecionamentos de sada
14 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
que se escreve dados?.

84 junho 2005 edio 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 contm trs
04 LinhaMesg=$((`tput lines` - 3)) # Linha onde as msgs sero mostradas
tipos de objeto: caracteres 05 TotCols=$(tput cols) # Qtd de colunas na tela para enquadrar msgs
simples, caracteres para es- 06 clear
pecicao de formato (ou de 07 echo "
+-----------------------------------+
controle) e seqncia de esca-
| Lista Todas as Msicas de um Determinado Artista |
pe no padro 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 no apagar barra vertical)
racter % e, logo a seguir, vem 11 read Nome
a especicao de formato de 12 if [ ! "$Nome" ] # $Nome esta vazio?
13 then
acordo com a tabela 1.
14 . pergunta.func "Deseja Sair?" s n
As seqncias de escape 15 [ $SN = n ] && continue
padro da linguagem C so 16 break
sempre precedidas pelo ca- 17 fi
18 fgrep -iq "^$Nome~" musicas || # fgrep no interpreta ^ como expresso regular
ractere contra-barra (\). As
19 {
reconhecidas pelo comando 20 . mandamsg.func "No existe msica desse artista"
printf so as da tabela 2. 21 continue
No acaba por a no! Tem 22 }
23 tput cup 7 29; echo '| |'
muito mais coisa sobre essa
24 LinAtual=8
instruo, 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
mentrios. 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 no saltou linha ao nal
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 no 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 edio 09 85


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

Tabela 1: Formatos de caractere (%) signica o tamanho que a cadeia ter O bc devolveu duas casas decimais e
aps a execuo do comando. Vamos ver o printf colocou o zero direita. O co-
Caractere A expresso ser impressa como:
c Caractere simples a seguir mais alguns exemplos. Os co- mando a seguir:
d Nmero no sistema decimal mandos abaixo:
e Notao cientca exponencial $ printf "%o\n" 10
f Nmero com ponto decimal (oat) $ printf "%d\n" 32 12
g O menor entre os formatos %e e %f com 32
omisso dos zeros no signicativos $ printf "%10d\n" 32 Converteu o valor 10 para base octal.
o Nmero no sistema octal 32 Para melhorar experimente:
s Cadeia de caracteres
x Nmero no sistema hexadecimal
preenchem a string com espaos em $ printf "%03o\n" 27
% Imprime um %. No h nenhum
tipo de converso branco esquerda (oito espaos mais dois 033
caracteres, 10 dgitos), no com zeros. J
no comando abaixo: Assim a converso ca com mais jeito
Opa, essa a forma correta! O %c rece- de octal, n?. O que este aqui faz?
beu o valor 1, como queramos: $ printf "%04d\n" 32
0032 $ printf "%s\n" Peteleca
$ a=2 Peteleca
$ printf "%c caracteres\n" $a O 04 aps % signica formate a string $ printf "%15s\n" Peteleca
2 caracteres em quatro dgitos, com zeros esquerda Peteleca
se necessrio. No comando:
O %c recebeu o valor da varivel $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 espaos em branco esquerda. J no co-
2 caracteres mando:
$ printf "%10c\n" $a caracteres O padro 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 espaos 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 especicou duas casas decimais. caracteres pedidos. E o comando abaixo,
passada como parmetro. O valor 10 Observe agora: o que faz?
frente do c no signica 10 caracteres. Um
nmero seguindo o sinal de percentagem $ printf "%f\n" 32.3 $ printf "%.3s\n" Peteleca
32.300000 Pet
Tabela 2: Seqncias de escape
O padro do %f seis casas decimais. O .3 manda truncar a cadeia de ca-
Seqncia Efeito
E no comando: racteres aps as trs primeiras letras. E
a Soa o beep
b Volta uma posio (backspace) o comando a seguir:
f Salta para a prxima pgina $ printf "%.2f\n" 32.3
lgica ( form feed) 32.30 $ printf "%10.3sa\n" Peteleca
n Salta para o incio da linha se- Peta Pet
guinte (line feed) O .2 especicou duas casas decimais.
r Volta para o incio da linha cor-
Agora observe: Imprime a cadeia com 10 caracteres,
rente (carriage return)
truncada aps os trs primeiros, conca-
t Avana para a prxima marca de
tabulao $ printf "%.3f\n" `echo "scale=2 ; 100/6" | bc` tenada com o caractere a (aps o s). E
33.330 esse comando a seguir, o que faz?

86 junho 2005 edio 09


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

$ printf EXEMPLO %x\n 45232 trabalho, principalmente em instalaes (1 minuto). A cada intervalo o Shell far
EXEMPLO b0b0 com estrutura de diretrios em mltiplos a vericao antes de exibir o prximo
nveis. Veja o exemplo a seguir: prompt primrio ($PS1). Se essa varivel
Ele transformou o nmero 45232 para estiver sem valor ou com um valor menor
hexadecimal (b0b0), mas os zeros no $ echo $CDPATH ou igual a zero, a busca por novas men-
combinam com o resto. Experimente: .:..:~:/usr/local sagens no ser efetuada.
$ pwd PATH Caminhos que sero pesquisa-
$ printf EXEMPLO %X\n 45232 /home/jneves/LM dos para tentar localizar um arquivo espe-
EXEMPLO B0B0 $ cd bin cicado. Como cada script um arquivo,
$ pwd caso use o diretrio corrente (.) na sua
Assim disfarou melhor! (repare no X /usr/local/bin varivel $PATH, voc no necessitar usar
maisculo). 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
varivel $CDPATH e no existia o diretrio o modo que prero.
$ printf %X %XL%X\n 49354 192 10 bin em nenhum dos seus antecessores (., PIPESTATUS uma varivel do tipo
C0CA C0LA .. e ~), o comando cd foi executado tendo vetor (array) que contm uma lista de
como destino /usr/local/bin. valores de cdigos de retorno do ltimo
Este a no marketing e bastante HISTSIZE Limita o nmero de ins- pipeline executado, isto , um array que
completo, veja s como funciona: trues que cabem dentro do arquivo de abriga cada um dos $? de cada instruo
O primeiro %X converteu 49354 em he- histrico 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 varivel $HISTFILE). Seu
um espao em branco seguido por outro valor padro 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 nal- (que tambm pode ser obtido com o co- jneves pts/1 Apr 12 12:04 (10.2.4.144)
mente o ltimo parmetro %X transformou mando uname -n). $ who | grep ^botelho
o nmero 10 na letra A. LANG Usada para determinar o idioma $ echo ${PIPESTATUS[*]}
Conforme vocs podem notar, a instru- falado no pas (mais especicamente 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 no estava logado, em
car o printf atravs de exemplos, pois Thu Apr 14 11:54:13 BRT 2005 seguida executamos um pipeline que
no saberia como enumerar tantas regri- $ LANG=pt_BR date procurava por ele. Usa-se a notao [*]
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 variveis do Shell LINENO O nmero da linha do script meira instruo (who) foi bem-sucedida
O Bash possui diversas variveis que ou funo que est sendo executada. (cdigo de retorno 0) e a seguinte (grep)
servem para dar informaes sobre Seu uso principal mostrar mensagens no (cdigo de retorno 1).
o ambiente ou alter-lo. So muitas e de erro juntamente com as variveis $0 PROMPT_COMMAND Se esta varivel re-
no 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 funo em execuo). que voc teclar um [ENTER] sozinho no
elaborao de scripts. Veja a seguir as LOGNAME Esta varivel armazena o prompt principal ($PS1), esse comando
principais delas: nome de login do usurio . ser executado. muito til quando voc
CDPATH Contm os caminhos que MAILCHECK Especica, em segundos, a precisa repetindo constantemente uma
sero pesquisados para tentar localizar freqncia com que o Shell verica a pre- determinada instruo.
um diretrio especicado. Apesar dessa sena de correspondncia nos arquivos PS1 o prompt principal. No Papo
varivel ser pouco conhecida, seu uso indicados pela variveis $MAILPATH ou de Botequim usamos os padres $ para
deve ser incentivado por poupar muito $MAIL. O tempo padro de 60 segundos usurio comum e # para root, mas mui-

junho 2005 edio 09 87


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

to freqente que ele esteja personalizado. Expanso de parmetros $ cadeia="Papo de Botequim"


Uma curiosidade que existe at concurso Bem, muito do que vimos at agora so $ echo ${cadeia#*' '}
de quem programa o $PS1 mais criativo. comandos externos ao Shell. Eles quebram de Botequim
PS2 Tambm chamado prompt de o maior galho, facilitam a visualizao, $ echo "Conversa "${cadeia#*' '}
continuao, aquele sinal de maior manuteno e depurao do cdigo, mas Conversa de Botequim
(>) que aparece aps um [ENTER] sem no so to ecientes quanto os intrnse-
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 preferncia esquerda tudo o que casa com a menor
($PATH) do diretrio corrente. Tem o ao uso dos intrnsecos. A partir de agora ocorrncia da expresso * , ou seja,
mesmo efeito do comando pwd. vou te mostrar algumas tcnicas para o todos os caracteres at o primeiro espao
RANDOM Cada vez que esta varivel seu programa pisar no acelerador. em branco. Esses exemplos tambm po-
acessada, devolve um inteiro aleatrio en- Na tabela 3 e nos exemplos a seguir, deriam ser escritos sem proteger o espao
tre 0 e 32767. Para gerar um inteiro entre veremos uma srie de construes cha- da interpretao do Shell (mas prero
0 e 100, por exemplo, digitamos: madas expanso (ou substituio) de proteg-lo para facilitar a legibilidade
parmetros (Parameter Expansion), que do cdigo). Veja s:
$ echo $((RANDOM%101)) substituem instrues 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 diviso do pergunta o S oferecido como valor de- $ echo "Conversa "${cadeia#* }
nmero randmico gerado por 101 porque fault (padro) e a sada vai para a varivel Conversa de Botequim
o resto da diviso de qualquer nmero SN, aps ler o valor podemos fazer:
por 101 varia entre 0 e 100. Repare que na construo de expr
REPLY Use esta varivel para recuperar SN=$(SN:-S} permitido o uso de metacaracteres.
o ltimo campo lido, caso ele no tenha Utilizando o mesmo valor da varivel
nenhuma varivel associada. Exemplo: Para saber o tamanho de uma cadeia: cadeia, observe como faramos 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 varivel informa, em se- o um at o nal 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 ocorrncia da expresso
estabilidade do Linux e esnobar usurios $ 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 no
que necessita de reboots freqentes. Repare que a origem zero e no um. aparea o caminho (path) completo do seu
TMOUT Se esta varivel contiver um Vamos extrair 3 caracteres a partir da 2 programa ($0) em uma mensagem de erro,
valor maior do que zero, esse valor posio da mesma varivel cadeia: inicie o seu texto da seguinte forma:
ser considerado o timeout padro 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 ao antes de expirar a sesso. Neste exemplo seria suprimido es-
Supondo que a varivel contenha o valor Repare que novamente a origem da con- querda tudo at a ltima barra (/) do
30, o Shell encerrar a sesso do usurio tagem zero e no um. Para suprimir caminho, restando somente o nome do
(ou seja, far logout) aps 30 segundos tudo esquerda da primeira ocorrncia programa. O caractere % simtrico ao
sem nenhuma ao no prompt. de uma cadeia, faa: #, veja o exemplo:

88 junho 2005 edio 09


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

Tabela 3: Tipos de expanso de parmetros H vrias formas de trocar uma subca-


deia no incio ou no m de uma varivel.
Expanso de parmetros Resultado esperado
Para trocar no incio fazemos:
${var:-padrao} Se var vazia, o resultado da expresso padro
${#cadeia} Tamanho de $cadeia
${cadeia:posicao} Extrai uma subcadeia de $cadeia a partir de posio. Origem zero $ echo $Passaro
${cadeia:posicao:tamanho} Extrai uma subcadeia de $cadeia a partir de posio com tamanho quero quero
igual a tamanho. Origem zero $ echo Como diz o sulista - ${PassaroU
${cadeia#expr} Corta a menor ocorrncia de $cadeia esquerda da expresso expr /#quero/no}
${cadeia##expr} Corta a maior ocorrncia de $cadeia esquerda da expresso expr Como diz o sulista - no quero
${cadeia%expr} Corta a menor ocorrncia de $cadeia direita da expresso expr
${cadeia%%expr} Corta a maior ocorrncia de $cadeia direita da expresso expr
Para trocar no nal fazemos:
${cadeia/subcad1/subcad2} Troca a primeira ocorrncia de subcad1 por subcad2
${cadeia//subcad1/subcad2} Troca todas as ocorrncias de subcad1 por subcad2
${cadeia/#subcad1/subcad2} Se subcad1 combina com o incio de cadeia, ento trocado por subcad2 $ echo Como diz o nordestino - U

${cadeia/%subcad1/subcad2} Se subcad1 combina com o m de cadeia, ento trocado por subcad2 ${Passaro/%quero/no}
Como diz o nordestino - quero no

$ 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 ocorrncias de uma dicas e depois guarde-os para consultas
subcadeia por outra. O comando: futuras. Mas voltando vaca fria: t
Para trocar primeira ocorrncia de uma na hora de tomar outro e ver o jogo do
subcadeia em uma cadeia por outra: $ echo ${cadeia//o/a} Mengo. Pra prxima 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 incio 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 varivel $SN receba o valor
$ echo ${cadeia/de /} diretrio corrente. Observe o exemplo: padro por expanso de parmetros,
Papo Botequim como vimos.
$ ls | wc -l E no se esquea: em caso de dvidas
Preste ateno quando for usar metaca- 30 ou falta de companhia para um (ou mais)
racteres! Eles so gulosos e sempre com- chope s mandar um e-mail para julio.
binaro com a maior possibilidade; No O wc pe um monte de espaos 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 m de fazer um curso
Botequim por Conversa de Botequim: porreta de programao em Shell deve
# QtdArqs recebe a sada 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 sada era desde 1980, quando participou do desen-
A idia era pegar tudo at o primeiro composta de brancos e nmeros, por isso volvimento do SOX, um sistema operacio-
o, mas acabou sendo trocado tudo at o montei essa expresso para trocar todos os nal similar ao Unix produzido pela Cobra
ltimo o. Isto poderia ser resolvido de espaos por nada. Note que antes e aps o Computadores. Pode ser contatado no
diversas maneiras. Eis algumas: asterisco (*) h espaos em branco. e-mail julio.neves@gmail.com

junho 2005 edio 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 captulo de nossa saga atravs do mundo do
Shell Script, vamos aprender a avaliar expresses, capturar
sinais e receber parmetros atravs da linha de comando.
por Jlio Cezar Neves

E
a amigo, te dei a maior moleza na a legibilidade do cdigo est horrorvel, 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 execuo, est timo. Como funes que voc resolva:
, mas nos testes que eu z, e de so coisas muito pessoais, j que cada
acordo com o que voc ensinou sobre um usa as suas e quase no h neces- $ var1=3
substituio de parmetros, achei que sidade de manuteno, eu sempre opto $ var2=var1
deveria fazer algumas alteraes nas fun- pelo desempenho.
es que desenvolvemos para torn-las de Hoje vamos sair daquela chatura que Te dei essas duas variveis e quero que
uso geral, como voc disse que todas as foi o nosso ltimo papo e voltar lgica, voc me diga como eu posso, me referindo
funes deveriam ser. Quer ver? saindo da decoreba. Mas volto a te lem- apenas varivel 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 m de te ver aprender, aqui no Boteco do Chico vlido e quebra Ah, isso mole, mole! s digitar
mas pera, d um tempo. Chico! Manda um galho. 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 vo ser muito teis. echo $`echo $var2`
Bem, alm do que voc pediu, eu
reparei que o programa que chamava a Listagem 1: funo pergunta.func
funo teria de ter previamente de nidas 01 # A funo recebe 3 parmetros 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 padro
z foi incluir duas linhas nas quais 04 # $3 - O outro valor aceito
empreguei substituio de parmetros 05 # Supondo que $1=Aceita?, $2=s e $3=n, a linha
para que, caso uma dessas variveis no 06 # abaixo colocaria em Msg o valor "Aceita? (S/n)"
07 TotCols=${TotCols:-$(tput cols)} # Se no estava definido, agora est
fosse informada, ela recebesse um valor
08 LinhaMesg=${LinhaMesg:-$(($(tput lines)-3))} # Idem
atribudo pela prpria funo. A linha
09 Msg="$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)"
de mensagem trs linhas antes do 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 cou: 13 read -n1 -p "$Msg " SN
Gostei, voc j se antecipou ao que eu 14 SN=${SN:-$2} # Se vazia coloca o padro em SN
ia pedir. S pra gente encerrar esse papo 15 SN=$(echo $SN | tr A-Z a-z) # A sada de SN ser em minsculas
16 tput cup $LinhaMesg $Col; tput el # Apaga Msg da tela
de substituio de parmetros, repare que

86 julho 2005 edio 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 execuo e resultar em var1. $ $var2
E echo $var1 produzir 3 10porpag1.sh alo2.sh incusu logado
Ah, ? Ento 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 raciocnio me parecia var1 vamos colocar l*, vejamos o resultado:
bastante lgico
O seu raciocnio realmente foi lgico, 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 variveis pelos seus valores; listamusica listartista listartista3 logado logaute.sh
P Resolve e substitui os meta caracteres;
P Passa a linha j toda esmiuada para execuo. Novamente, no tempo de substituio das variveis, $var1
Dessa forma, quando o interpretador chegou na fase de re- ainda no havia se apresentado ao Shell para ser resolvida.
soluo de variveis, que como eu disse anterior execuo, Assim, s nos resta executar o comando eval para dar as duas
a nica varivel existente era var2 e por isso a tua soluo passadas necessrias.
produziu como sada $var1. O comando echo identicou isso Uma vez um colega da excelente lista de discusso groups.yahoo.com/
como uma cadeia de caracteres e no como uma varivel. group/shell-script colocou uma dvida: queria fazer um menu
Problemas desse tipo so relativamente freqentes e seriam que numerasse e listasse todos os arquivos com extenso .sh e,
insolveis caso no existisse a instruo eval, cuja sintaxe quando o operador escolhesse uma opo, 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 pe o eval na frente, no entanto, o que ocorre Listagem 2: fazmenu.sh
que o Shell trata cmd como um parmetro do eval e, em seguida, 01 #!/bin/bash
o eval executa a linha recebida, submetendo-a ao Shell. Ou 02 #
seja, na prtica cmd analisado duas vezes. Dessa forma, se 03 # Lista que enumera os programas com extenso .sh no
executssemos o comando que voc props colocando o eval 04 # diretrio corrente e executa o escolhido pelo operador
05 #
na frente, teramos a sada esperada. Veja:
06 clear; i=1
07 printf "%11s\t%s\n\n" Opo Programa
$ eval echo $`echo $var2` 08 CASE='case $opt in'
3 09 for arq in *.sh
10 do
Esse exemplo tambm 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 opo 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 edio 10 87


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

Parece complicado porque usei muitos dos por) processos em execuo. Vamos, "limpar a rea" ao seu trmino. Se seu
printf para formatao 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 trmino normal,
ro printf foi colocado para imprimir frente vamos dar uma passada rpida muito fcil fazer essa limpeza; porm,
o cabealho e logo em seguida come- pelos sinais gerados pelos processos. se o seu programa tiver um m brusco,
cei a montar dinamicamente a varivel Para mandar um sinal a um processo, muita coisa ruim pode ocorrer:
$CASE, na qual ao nal ser feito um eval usamos normalmente o comando kill, P possvel que em um determinado es-
para execuo do programa escolhido. cuja sintaxe : pao de tempo, o seu computador esteja
Repare no entanto que dentro do loop cheio de arquivos de trabalho inteis
do for existem dois printf: o primeiro $ kill -sig PID P Seu processador poder car 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 identicador do proces- dos por processos lhos que perderam
do read voc colocar uma linha echo so (Process Identication ou Process ID). os pais e esto rfos;
"$CASE", ver que o comando case mon- Alm do comando kill, algumas seqn- P necessrio liberar sockets abertos para
tado dentro da varivel est todo inden- cias de teclas tambm podem gerar sinais. no deixar os clientes congelados;
tado. Frescura, n?:). Na sada do for, foi A tabela 1 mostra os sinais mais impor- P Seus bancos de dados podero car
adicionada uma linha varivel $CASE tantes para monitorarmos: corrompidos porque sistemas gerencia-
para, no caso de uma escolha invlida, Alm desses, existe o famigerado si- dores de bancos de dados necessitam
ser executada uma funo 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 sada gerada: o dedo no boto de desligar do compu- Enm, existem mil razes para no usar
tador o que altamente indesejvel, 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 verso do fazmenu.sh
001 10porpag1.sh
01 #!/bin/bash
002 10porpag2.sh
02 #
003 10porpag3.sh 03 # Lista enumerando os programas com extenso .sh no
004 alo1.sh 04 # diretrio 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" Opo 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 opo desejada: 12 CASE="$CASE
13 "$(printf "%03d)\t %s;;" $i $arq)
Seria interessante incluir uma opo 14 i=$((i+1))
para terminar o programa e, para isso, 15 done
seria necessria a incluso de uma linha 16 printf "\t%d\t%s\n\n" 999 "Fim do programa" # Linha includa
aps o loop de montagem da tela e a alte- 17 CASE="$CASE

rao da linha na qual fazemos a atribui- 18 999) exit;; # Linha alterada

o nal do valor da varivel $CASE. Veja 19 *) ./erro;;


20 esac"
na listagem 3 como ele caria:
21 read -n3 -p "Informe a opo 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 edio 10


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

O comando trap Caso a transferncia seja interrompida Tabela 1: Principais sinais


Para fazer a monitorao de sinais existe por um kill ou um [CTRL]+[C], certa-
Cdigo 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 incio dele, 2 SIGINT Interrupo pelo teclado.
trap 'cmd1; cmd2; cmdn' S1 S2 SN digitar o comando: ([CTRL]+[C])
3 SIGQUIT Interrupo pelo teclado
([CTRL]+[\])
Onde os comandos cmd1, cmd2, cmdn trap "rm -f /tmp/$$ ; exit" 0 1 2 3 15
15 SIGTERM Quando o programa
sero 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 apstrofes (') s so necessrias caso rupo 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 m normal (sinal 1 Caso: O comando ftp encontra-se
bm uma funo interna, uma externa 0), o arquivo /tmp/$$ seria removido. em script1. Nesse caso, o argumento do
ou outro script. Caso no houvesse a instruo exit comando trap deveria vir entre aspas (")
Para entender o uso de aspas (") e na linha de comando do trap, ao nal porque, caso ocorresse uma interrupo
apstrofes (') vamos recorrer a um da execuo dessa linha o uxo 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 transferncia de quando recebeu o sinal que originou a mento e o PID do script2 seria diferente
arquivos via FTP para uma mquina execuo desse trap. do encontrado em /tmp/$$ (no esquea
remota ($RemoComp), na qual o usurio Note tambm que o Shell pesquisa a que $$ a varivel que contm 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 variveis foram coloc-lo no incio 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. Ento, no ltimo exemplo, o va-apstrofes ('), pois caso a interrupo
por diversas pessoas. Vejamos o trecho lor de $$ ser substitudo no momento em se desse durante a execuo de script1,
de cdigo a seguir: que o comando trap lido pela primeira o arquivo no teria sido criado; caso ela
vez, j que as aspas (") no protegem o ocorresse durante a execuo de script2,
ftp -ivn $RemoComp << FimFTP >> /tmp/$$ U cifro ($) da interpretao do Shell. o valor de $$ seria o PID desse processo,
2>> /tmp/$$ Se voc quisesse fazer a substituio 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 apstrofes (').
sem argumentos, lista os sinais que esto
get $Arq Assim, na primeira interpretao do trap,sendo monitorados no ambiente, bem
FimFTP o Shell no veria o cifro ($), as apstro-
como a linha de comando que ser exe-
fes (') seriam removidas e, nalmente, o cutada quando tais sinais forem recebidos.
Repare que tanto as sadas dos dilo- 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 caria assim: (vazia), isso signica que os sinais espe-
esto sendo redirecionados para /tmp/$$, cicados devem ser ignorados quando
o que uma construo bastante comum trap 'rm -f /tmp/$$ ; exit' 0 1 2 3 15 recebidos. Por exemplo, o comando trap
para arquivos temporrios usados em "" 2 especica que o sinal de interrup-
scripts com mais de um usurio, porque Suponha dois casos: voc tem dois o ([CTRL]+[C]) deve ser ignorado. No
$$ a varivel que contm o nmero 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 especicado para que o
tipo de construo evita-se que dois ou colocado em execuo por script1. Por sinal seja ignorado e no equivalente a
mais usurios disputem a posse e os di- serem dois processos diferentes, tero escrever trap 2, cuja nalidade retornar
reitos sobre um arquivo. dois PIDs distintos. o sinal 2 ao seu estado padro.

julho 2005 edio 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 iro ignor-lo. Portanto, se voc console grco e escreva no prompt de desejar que uma opo seja seguida por
especicar qual ao 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 iro tomar a mesma ao quando $ trap "echo Mudou o tamanho da janela" 28 Isso diz ao getopts que a opo -a tem a
receberem esse sinal. Ou seja, os sinais forma -a argumento. Normalmente um
so automaticamente exportados. Para o Em seguida, pegue o mouse e arraste-o ou mais espaos em branco separam o
sinal mostrado (sinal 2), isso signica que de forma a variar o tamanho da janela parmetro da opo; no entanto, getopts
os sub-shells sero encerrados. Suponha corrente. Surpreso? o Shell orientado tambm manipula parmetros que vm
que voc execute o comando trap "" 2 e a eventos Mais unzinho, porque no colados opo como em -aargumento.
ento execute um sub-shell, que tornar a consigo resistir. Escreva isto: cadeiadeopcoes no pode conter um si-
executar outro script como um sub-shell. nal de interrogao (?).
Se for gerado um sinal de interrupo, $ trap "echo j era" 17 O nome constante da linha de sintaxe
este no ter efeito nem sobre o Shell acima dene uma varivel 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 ignoraro executado, o prximo dos parmetros
o sinal. $ sleep 3 & posicionais e o colocar na varivel nome.
Em korn shell (ksh) no existe a opo getopts coloca uma interrogao (?) na
-s do comando read para ler uma senha. Voc acabou de criar um sub-shell que varivel denida em nome se achar uma
O que costumamos fazer usar usar o ir dormir durante trs segundos em opo no denida em cadeiadeopcoes
comando stty com a opo -echo, que background. Ao m desse tempo, voc ou se no achar o argumento esperado
inibe a escrita na tela at que se encon- receber a mensagem j era, porque o para uma determinada opo.
tre um stty echo para restaurar essa sinal 17 emitido a cada vez em que um Como j sabemos, cada opo passada
escrita. Ento, se estivssemos usando o sub-shell termina a sua execuo. Para por uma linha de comandos tem um n-
interpretador ksh, a leitura da senha teria devolver esses sinais ao seu comporta- dice numrico; assim, a primeira opo
que ser feita da seguinte forma: mento padro, 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 obtm
echo -n "Senha: " descobrir algum material bacana sobre uma opo, ele armazena o ndice do
stty -echo uso de sinais, por favor me informe por prximo parmetro a ser processado na
read Senha email, porque muito rara a literatura varivel OPTIND.
stty echo sobre o assunto. Quando uma opo 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 no soubes- O comando getopts recupera as opes e na varivel OPTARG. Se uma opo no
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 no for encontrado, a varivel
a instruo read para descontinuar o pro- isto , letras (ou nmeros) aps um sinal OPTARG ser "apagada" (com unset). O co-
grama e, caso agisse dessa forma, o seu de menos (-) seguidas ou no de um mando encerra sua execuo quando:
terminal estaria sem echo. Para evitar que argumento; no caso de somente letras P Encontra um parmetro que no come-
isso acontea, o melhor a fazer : (ou nmeros), elas podem ser agrupa- a com um hfen (-).
das. Voc deve usar esse comando para P O parmetro especial -- indica o m
echo -n "Senha: " "fatiar" opes e argumentos passados das opes.
trap "stty echo para o seu script. P Quando encontra um erro (por exemplo,
exit" 2 3 A sintaxe getopts cadeiadeopcoes uma opo no reconhecida).
stty -echo nome. A cadeiadeopcoes deve explicitar O exemplo da listagem 4 meramente
read Senha uma cadeia de caracteres com todas as didtico, servindo para mostrar, em um
stty echo opes reconhecidas pelo script; assim, pequeno fragmento de cdigo, o uso ple-
trap 2 3 se ele reconhece as opes -a -b e c, no do comando.

90 julho 2005 edio 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 opo invlida a
getopts fez a variavel OPT_LETRA igual a 'P' varivel $OPT_LETRA receber um ponto de interrogao (?) 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 opo f no valida
./getoptst.sh: illegal option -- f
Dessa forma, sem ter muito trabalho, separei getopts fez a variavel OPT_LETRA igual a '?'
todas as opes com seus respectivos argumentos, OPTARG eh ''
deixando somente os parmetros que foram passa- getopts fez a variavel OPT_LETRA igual a 'P'
dos pelo operador para posterior tratamento. Repare OPTARG eh 'impressora'
que, se tivssemos escrito a linha de comando com Dispensando os primeiros $OPTIND-1 = 2 argumentos
o argumento (impressora) separado da opo (-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 identica um conjun- Me diz uma coisa: voc no poderia ter usado um condicional com
to de trs opes (ou argumentos) e, no anterior, case para evitar o getopts?
somente dois. Veja s: Poderia sim, mas para qu? Os comandos esto a para serem usados
O exemplo foi didtico, mas imagine um programa que aceitasse muitas
$ getoptst.sh -h -P impressora arq1 arq2 opes e cujos parmetros poderiam ou no estar colados s opes, sen-
getopts fez a variavel OPT_LETRA igual a 'h' do que as opes tambm poderiam ou no 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 razo. porque eu j estou
meio cansado com tanta informao nova na minha
Listagem 4: getoptst.sh cabea. 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 tambm j cansei mas
03 hoje no 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 graduao em Informtica para o uso de Soft-
07 #
ware Livre. Mas antes vou te deixar um problema
08 # e note que as informaes de todas as opes so exibidas
para te encucar: quando voc varia o tamanho de
09 #
10 # A cadeia 'P:h' diz que a opo -P uma opo complexa uma janela do terminal, no centro dela no aparece
11 # e requer um argumento e que h uma opo simples que no requer dinamicamente, em vdeo reverso, a quantidade de
12 # argumentos. linhas e colunas? Ento! 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 no trouxer eu me mando!
16 echo "getopts fez a variavel OPT_LETRA igual a '$OPT_LETRA'"
No se esquea, qualquer dvida 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 tambm 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 m de fazer um curso porreta de programao 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 edio 10 91


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

Papo de botequim
Curso de Shell Script

Dave Hamilton - www.sxc.hu


Parte nal
A conversa est boa, mas uma hora eles tem que sair do bar. Na ltima
parte do nosso papo, falamos sobre pipes e sincronizao entre processos.
por Jlio Cezar Neves

E
a rapaz, tudo bom? No estou nem a para a aparncia, 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 aparncia, 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 z, mas mesmo depois de quebrar muito a funcionando e est bem enxuto.
cabea a aparncia no cou igual. Pxa, 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 tambm
05 # quando o tamanho da tela eh alterado. chamado de FIFO. FIFO um acrnimo 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 sada.
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 so 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 diferenas, 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 permisses, 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 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 padro.

86 agosto 2005 edio 11


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

Nos sistemas mais antigos, os named pipes so criados pelo Sincronizao de processos
utilitrio mknod, normalmente situado no diretrio /etc. Nos Suponha que voc dispare paralelamente dois programas (pro-
sistemas mais modernos, a mesma tarefa feita pelo mkfo, cessos), chamados programa1 e programa2, cujos diagramas de
que recebe um ou mais nomes como argumento e cria pipes blocos de suas rotinas so como mostrado na tabela 1. Os dois
com esses nomes. Por exemplo, para criar um named pipe com processos so disparados em paralelo e, no bloco 1 do programa1,
o nome pipe1, digite: as trs classicaes so 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 ns tenhamos criado o named then
pipe mostrado anteriormente. Vamos agora trabalhar com duas Manda=va
sesses ou dois consoles virtuais. Em um deles digite: else
Manda=pare
$ ls -l > pipe1 break
fi
e em outro faa: done
echo $Manda > pipe1
$ cat < pipe1 [ $Manda = pare ] &&
{
Voil! A sada do comando executado no primeiro console echo Erro durante a classificao dos arquivos
foi exibida no segundo. Note que a ordem em que os comandos exit 1
ocorreram no importa. }
Se voc prestou ateno, reparou que o primeiro comando
executado parecia ter "pendurado". Isto acontece porque a outra
ponta do pipe ainda no estava conectada, e ento o sistema Assim sendo, o comando if testa cada classicao que est
operacional suspendeu o primeiro processo at que o segundo sendo efetuada. Caso ocorra qualquer problema, as classicaes
"abrisse" o pipe. Para que um processo que usa pipe no que seguintes sero abortadas, uma mensagem contendo a string
em modo de espera, necessrio 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 cdigo de sada 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 classicaes), o programa2 executava o seu bloco 1, proces-
sem nenhuma relao possam se comunicar entre si. Os named sando as suas rotinas de abertura e menu paralelamente ao
pipes tambm so 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 cdigo do programa2 a seguir mostra a transio 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/corrupo de dados devido a atualizaes simul- then
tneas (a famosa concorrncia). Vamos ver alguns exemplos
para ilustrar estes casos. Rotina de impresso

Tabela 1
Programa1 Programa2
Bloco 1 Rotina de classicao de trs grandes arquivos Rotina de abertura e gerao de menus
Bloco 2 Acertos nais e encerramento Impresso dos dados classicados pelo programa 1

agosto 2005 edio 11 87


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

Ento, o que fazer? Para resolver o problema de concor-


else rncia, 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 pgina no nosso site que precise de um contador.
Aps a execuo de seu primeiro bloco, o programa2 passar Como apenas este script altera os arquivos, no existe o pro-
a "ouvir" o pipe1, cando parado at que as classicaes do blema de concorrncia.
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 esto ntegros para serem Quando uma pgina 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 possvel disparar programas de forma assncrona e
sincroniz-los quando necessrio, ganhando bastante tempo echo "teste_pagina.html" > /tmp/pipe_contador
de processamento.
Para evitar erros, em cada pgina 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 varivel $REQUEST_URI contm 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 idias 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 pgina receber dois ou mais acessos simul- (www.thobias.org). Aconselho a todos os que querem aprender
tneos, um ou mais poder ser perdido, bastando que o segundo Shell a dar uma passada l.
acesso seja feito aps a leitura do arquivo page.hits e antes Voc pensa que o assunto named pipes est esgotado? Enga-
da sua gravao, isto , aps 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.
Substituio de processos
Listagem 2: contahits.sh Vou mostrar que o Shell tambm usa os named
01 #!/bin/bash pipes de uma maneira bastante singular, que a
02 substituio de processos (process substitution). Uma
03 PIPE="/tmp/pipe_contador" # arquivo named pipe substituio de processos ocorre quando voc pe
04 # dir onde serao colocados os arquivos contadores de cada pagina um < ou um > grudado na frente do parntese 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, porm redirecionar a sada
12 do para um named pipe temporrio, que o Shell cria,
13 FILE="$DIR/$(echo $URL | sed 's,.*/,,')" nomeia e depois remove. Ento o cat ter um nome
14 # quando rodar como daemon comente a proxima linha de arquivo vlido para ler (que ser este named pipe
15 echo "arquivo = $FILE" e cujo dispositivo lgico associado /dev/fd/63).
15
O resultado a mesma sada que a gerada pelo
17 n="$(cat $FILE 2> /dev/null)" || n=0
ls -l, porm dando um ou mais passos que o usual.
18 echo $((n=n+1)) > "$FILE"
19 done
Pra que simplicar?
20 done Como poderemos nos certicar disso? Fcil Veja
o comando a seguir:

88 agosto 2005 edio 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? Ento suponha que voc LIST="$FILE $LIST" # Alterada dentro do subshell
tenha dois diretrios, chamados dir e dir.bkp, e deseja saber done # Fim do subshell
se os dois so iguais. Basta comparar o contedo dos diretrios echo $LIST
com o comando cmp:
No incio deste exemplo eu disse que ele era meramente
$ cmp <(cat dir/*) <(cat dir.bkp/*) || echo backup furado didtico 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 ento, usando a prpria substituio de processos:
Este um exemplo meramente didtico, mas so tantos os
comandos que produzem mais de uma linha de sada que ele $ cat -n <(ls)
serve como guia para outros. Eu quero gerar uma listagem dos
meus arquivos, numerando-os, e ao nal mostrar o total de Um ltimo exemplo: voc deseja comparar arq1 e arq2 usando
arquivos no diretrio corrente: o comando comm, mas esse comando necessita que os arquivos
estejam classicados. Ento 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 faa as seguintes operaes:
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 m. Curti muito
ls | while read arq e recebi diversos elogios pelo trabalho desenvolvido ao longo
do de doze meses e, o melhor de tudo, z muitas amizades e tomei
((i++)) # assim nao eh necessario inicializar i muitos chopes de graa com os leitores que encontrei pelos
echo "$i: $arq" congressos e palestras que ando fazendo pelo nosso querido
done Brasil. Me despeo de todos mandando um grande abrao 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, porm no coman- sade de todos ns: Tim, Tim.
do echo aps 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 varivel 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 variveis criadas no seu interior trabalha com Unix desde 1980, quando participou do desenvolvi-
e as alteraes l feitas por variveis criadas externamente. mento do SOX, um sistema operacional similar ao Unix produzido
Somente para te mostrar que uma varivel criada fora do pela Cobra Computadores. autor do livro Programao Shell
Linux, publicado pela editora Brasport. Pode ser contatado no
sub-shell e alterada em seu interior perde as alteraes feitas
e-mail julio.neves@gmail.com
quando o sub-shell se encerra, execute o script a seguir:

agosto 2005 edio 11 89


www.linuxmagazine.com.br

Você também pode gostar