Você está na página 1de 59

LINUX USER

Papo de Botequim

Curso de Shell Script

Papo de Botequim
Voc no agenta mais aquele seu amigo usurio de Linux enchendo o seu saco com aquela histria de que o sistema fantstico e o Shell uma ferramenta maravilhosa? A partir desta edio vai ficar mais fcil entender o porqu deste entusiasmo...
POR JULIO CEZAR NEVES
porque, em ingls, Shell significa concha, carapaa, isto , fica entre o usurio e o sistema operacional, de forma que tudo que interage com o sistema operacional, tem que passar pelo seu crivo.

O ambiente Shell
Bom j que para chegar ao ncleo do Linux, no seu kernel que o que interessa a todo aplicativo, necessria a filtragem do Shell, vamos entender como ele funciona de forma a tirar o mximo proveito das inmeras facilidades que ele nos oferece. O Linux, por definio, um sistema multiusurio no podemos nunca nos esquecer disto e para permitir o acesso de determinados usurios e barrar a entrada de outros, existe um arquivo chamado /etc/passwd, que alm de fornecer dados para esta funo de leo-de-chcara do Linux, tambm prov informaes para o incio de uma sesso (ou login, para os ntimos) daqueles que passaram por esta primeira barreira. O ltimo campo de seus registros informa ao sistema qual o Shell que a pessoa vai receber ao iniciar sua sesso. Lembra que eu te falei de Shell, famlia, irmo? Pois , vamos comear a entender isto: o Shell a conceituao de concha envolvendo o sistema operacional propriamente dito, o nome genrico para tratar os filhos desta idia que, ao longo dos muitos anos de exis-

ilogo entreouvido em uma mesa de um botequim, entre um usurio de Linux e um empurrador de mouse: Quem o Bash? o filho caula da famlia Shell. P cara! Ests a fim de me deixar maluco? Eu tinha uma dvida e voc me deixa com duas! No, maluco voc j h muito tempo: desde que decidiu usar aquele sistema operacional que voc precisa reiniciar dez vezes por dia e ainda por cima no tem domnio nenhum sobre o que esta acontecendo no seu computador. Mas deixa isso pr l, pois vou te explicar o que Shell e os componentes de sua famlia e ao final da nossa conversa voc dir: Meu Deus do Shell! Porque eu no optei pelo Linux antes?.

O ambiente Linux
Para voc entender o que e como funciona o Shell, primeiro vou te mostrar como funciona o ambiente em camadas do Linux. D uma olhada no grfico mostrado na Figura 1. Neste grfico podemos ver que a camada de hardware a mais profunda e formada pelos componentes fsicos do seu computador. Em torno dela, vem a camada do kernel que o cerne do Linux, seu ncleo, e quem pe o hardware para funcionar, fazendo seu gerenciamento e controle. Os programas e comandos que envolvem o kernel, dele se utilizam para realizar as tarefas para que foram desenvolvidos. Fechando tudo isso vem o Shell, que leva este nome

Quadro 1: Uma rapidinha nos principais sabores de Shell


Bourne Shell (sh): Desenvolvido por Stephen Bourne do Bell Labs (da AT&T, onde tambm foi desenvolvido o Unix), foi durante muitos anos o Shell padro do sistema operacional Unix. tambm chamado de Standard Shell por ter sido durante vrios anos o nico, e at hoje o mais utilizado. Foi portado para praticamente todos os ambientes Unix e distribuies Linux. Korn Shell (ksh): Desenvolvido por David Korn, tambm do Bell Labs, um superconjunto do sh, isto , possui todas as facilidades do sh e a elas agregou muitas outras. A compatibilidade total com o sh vem trazendo muitos usurios e programadores de Shell para este ambiente. Boune Again Shell (bash): Desenvolvido inicialmente por Brian Fox e Chet Ramey, este o Shell do projeto GNU. O nmero de seus adeptos o que mais cresce em todo o mundo, seja por que ele o Shell padro do Linux, seja por sua grande diversidade de comandos, que incorpora inclusive diversos comandos caractersticos do C Shell. C Shell (csh): Desenvolvido por Bill Joy, da Universidade de Berkley, o Shell mais utilizado em ambientes BSD. Foi ele quem introduziu o histrico de comandos. A estruturao de seus comandos bem similar da linguagem C. Seu grande pecado foi ignorar a compatibilidade com o sh, partindo por um caminho prprio. Alm destes Shells existem outros, mas irei falar somente sobre os trs primeiros, tratando-os genericamente por Shell e assinalando as especificidades de cada um.

82

Agosto 2004

www.linuxmagazine.com.br

Papo de Botequim

LINUX USER

tncia do sistema operacional Unix, foram aparecendo. Atualmente existem diversos sabores de Shell (veja Quadro 1 na pgina anterior).

Com que Shell eu vou?


Quando digo que o ltimo campo do arquivo /etc/passwd informa ao sistema qual o Shell que o usurio vai usar ao se logar, isto deve ser interpretado ao p-da-letra. Se este campo do seu registro contm o termo prog, ao acessar o sistema o usurio executar o programa prog. Ao trmino da execuo, a sesso do usurio se encerra automaticamente. Imagine quanto se pode incrementar a segurana com este simples artifcio.

redirecionamento, que pode ser de entrada (stdin), de sada (stdout) ou dos erros (stderr), conforme vou explicar a seguir. Mas antes precisamos falar de...

Como funciona o Shell


O Shell o primeiro programa que voc ganha ao iniciar sua sesso (se quisermos assassinar a lngua portuguesa podemos tambm dizer ao se logar) no Linux. ele quem vai resolver um monte de coisas de forma a no onerar o kernel com tarefas repetitivas, poupando-o para tratar assuntos mais nobres. Como cada usurio possui o seu prprio Shell interpondo-se entre ele e o Linux, o Shell quem interpreta os comandos digitados e examina as suas sintaxes, passando-os esmiuados para execuo. pa! Esse negcio de interpretar comando no tem nada a ver com interpretador no, n? Tem sim: na verdade o Shell um interpretador que traz consigo uma poderosa linguagem com comandos de alto nvel, que permite construo de loops, de tomadas de deciso e de armazenamento de valores em variveis, como vou te mostrar. Vou explicar as principais tarefas que o Shell cumpre, na sua ordem de execuo. Preste ateno, porque esta ordem fundamental para o entendimento do resto do nosso bate papo.

Substituio de Variveis
Neste ponto, o Shell verifica se as eventuais variveis (parmetros comeados por $), encontradas no escopo do comando, esto definidas e as substitui por seus valores atuais.

volvidos (inclusive o prprio programa), e retorna um erro caso o usurio que chamou o programa no esteja autorizado a executar esta tarefa.
$ ls linux linux

Substituio de MetaCaracteres
Se algum meta-caracter (ou coringa, como *, ? ou []) for encontrado na linha de comando, ele ser substitudo por seus possveis valores. Supondo que o nico item no seu diretrio corrente cujo nome comea com a letra n seja um diretrio chamado nomegrandeprachuchu, se voc fizer:
$ cd n*

Neste exemplo o Shell identificou o ls como um programa e o linux como um parmetro passado para o programa ls.

Atribuio
Se o Shell encontra dois campos separados por um sinal de igual (=) sem espaos em branco entre eles, ele identifica esta seqncia como uma atribuio.
$ valor=1000

como at aqui quem est manipulando a linha de comando ainda o Shell e o programa cd ainda no foi executado, o Shell expande o n* para nomegrandeprachuchu (a nica possibilidade vlida) e executa o comando cd com sucesso.

Anlise da linha de comando


Neste exame o Shell identifica os caracteres especiais (reservados) que tm significado para a interpretao da linha e logo em seguida verifica se a linha passada um comando ou uma atribuio de valores, que so os tens que vou descrever a seguir.

Neste caso, por no haver espaos em branco (que um dos caracteres reservados), o Shell identificou uma atribuio e colocou 1000 na varivel valor.

Entrega da linha de comando para o kernel


Completadas todas as tarefas anteriores, o Shell monta a linha de comando, j com todas as substituies feitas e chama o kernel para execut-la em um novo Shell (Shell filho), que ganha um nmero de processo (PID ou Process IDentification) e fica inativo, tirando uma soneca durante a execuo do programa. Uma vez encerrado este processo (e o Shell filho), o Shell pai recebe novamente o controle e exibe um prompt, mostrando que est pronto para executar outros comandos.

Resoluo de Redirecionamentos
Aps identificar os componentes da linha que voc digitou, o Shell parte para a resoluo de redirecionamentos. O Shell tem incorporado ao seu elenco de habilidades o que chamamos de

Comando
Quando um comando digitado no prompt (ou linha de comando) do Linux, ele dividido em partes, separadas por espaos em branco: a primeira parte o nome do programa, cuja existncia ser verificada; em seguida, nesta ordem, vm as opes/parmetros, redirecionamentos e variveis. Quando o programa identificado existe, o Shell verifica as permisses dos arquivos en-

Shell Programas e Comandos Ncleo ou Kernel Hardware

Cuidado na Atribuio
Jamais faa:

$ valor = 1000 bash: valor: not found


Neste caso, o Bash achou a palavra valor isolada por espaos e julgou que voc estivesse mandando executar um programa chamado valor, para o qual estaria passando dois parmetros: = e 1000.

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


Para tirar aquela sensao que voc tem quando v um script Shell, que mais parece uma sopa de letrinhas ou um conjunto de hierglifos, vou lhe mostrar os principais caracteres especiais para que voc saia por a como Champollion decifrando a Pedra de Roseta.

$ echo \* $ echo *

Caracteres para remoo do significado.


isso mesmo, quando no desejamos que o Shell interprete um caractere especfico, devemos escond-lo dele. Isso pode ser feito de trs maneiras diferentes, cada uma com sua peculiaridade: Apstrofo (): quando o Shell v uma cadeia de caracteres entre apstrofos, ele retira os apstrofos da cadeia e no interpreta seu contedo.
$ ls linuxm* linuxmagazine $ ls 'linuxm*' bash: linuxm* no such file U or directory

Viu a diferena? Aspas (): exatamente iguais ao apstrofo, exceto que, se a cadeia entre aspas contiver um cifro ($), uma crase (`), ou uma barra invertida (\), estes caracteres sero interpretados pelo Shell. No precisa se estressar, eu no te dei exemplos do uso das aspas por que voc ainda no conhece o cifro ($) nem a crase (`). Daqui para frente veremos com muita constncia o uso destes caracteres especiais; o mais importante entender seu significado.

esperando pelo teclado (Entrada Padro) e como tambm no citei a sada, o que eu teclar ir para a tela (Sada Padro), criando desta forma como eu havia proposto um programa gago. Experimente!

Redirecionamentop da Sada Padro


Para especificarmos a sada de um programa usamos o smbolo > ou o >>, seguido do nome do arquivo para o qual se deseja mandar a sada. Vamos transformar o programa anterior em um editor de textos:
$ cat > Arq

Caracteres de redirecionamento
A maioria dos comandos tem uma entrada, uma sada e pode gerar erros. Esta entrada chamada Entrada Padro ou stdin e seu dispositivo padro o teclado do terminal. Analogamente, a sada do comando chamada Sada Padro ou stdout e seu dispositivo padro a tela do terminal. Para a tela tambm so enviadas normalmente as mensagens de erro oriundas dos comandos, chamada neste caso de Sada de Erro Padro ou stderr. Veremos agora como alterar este estado de coisas. Vamos fazer um programa gago. Para isto digite (tecle Enter ao final de cada linha comandos do usurio so ilustrados em negrito):
$ cat E-e-eu sou gago. Vai encarar? E-e-eu sou gago. Vai encarar?

O cat continua sem ter a entrada especificada, portanto est aguardando que os dados sejam teclados, porm a sua sada est sendo desviada para o arquivo Arq. Assim sendo, tudo que esta sendo teclado esta indo para dentro de Arq, de forma que fizemos o editor de textos mais curto e ruim do planeta. Se eu fizer novamente:
$ cat > Arq

No primeiro caso o Shell expandiu o asterisco e descobriu o arquivo linuxmagazine para listar. No segundo, os apstrofos inibiram a interpretao do Shell e veio a resposta que no existe o arquivo linuxm*. Contrabarra ou Barra Invertida (\): idntico aos apstrofos exceto que a barra invertida inibe a interpretao somente do caractere que a segue. Suponha que voc, acidentalmente, tenha criado um arquivo chamado * (asterisco) o que alguns sabores de Unix permitem e deseja remov-lo. Se voc fizesse:
$ rm *

Os dados contidos em Arq sero perdidos, j que antes do redirecionamento o Shell criar um Arq vazio. Para colocar mais informaes no final do arquivo eu deveria ter feito:
$ cat >> Arq

Redirecionamento da Sada de Erro Padro


Assim como por padro o Shell recebe os dados do teclado e envia a sada para a tela, os erros tambm vo para a tela se voc no especificar para onde eles devem ser enviados. Para redirecionar os erros, use 2> SaidaDeErro. Note que entre o nmero 2 e o sinal de maior (>) no existe espao em branco. Vamos supor que durante a execuo de um script voc pode, ou no (dependendo do rumo tomado pela execuo do programa), ter criado um arquivo chamado /tmp/seraqueexiste$$. Como no quer ficar com sujeira no disco rgido, ao final do script voc coloca a linha a seguir:
rm /tmp/seraqueexiste$$

Voc estaria na maior encrenca, pois o rm removeria todos os arquivos do diretrio corrente. A melhor forma de fazer o servio :
$ rm \*

O cat um comando que lista o contedo do arquivo especificado para a Sada Padro (stdout). Caso a entrada no seja definida, ele espera os dados da stdin (a entrada padro). Ora como eu no especifiquei a entrada, ele a est

Redirecionamento Perigoso
Desta forma, o Shell no interpreta o asterisco, evitando a sua expanso. Faa a seguinte experincia cientfica:
$ cd /etc $ echo '*'
Como j havia dito, o Shell resolve a linha e depois manda o comando para a execuo. Assim, se voc redirecionar a sada de um arquivo para ele prprio, primeiramente o Shell esvaziaeste arquivo e depois manda o comando para execuo! Desta forma, para sua alegria, voc acabou de perder o contedo de seu querido arquivo.

84

Agosto 2004

www.linuxmagazine.com.br

Papo de Botequim

LINUX USER

Dados ou Erros?
Preste ateno! No confunda >> com 2>. O primeiro anexa dados ao final de um arquivo, e o segundo redireciona a Sada de Erro Padro (stderr) para um arquivo que est sendo designado. Isto importante!

Caso o arquivo no existisse seria enviado para a tela uma mensagem de erro. Para que isso no acontea faa:
rm /tmp/seraqueexiste$$ 2> U /dev/null

caprichamos, n? Ento ao invs de sair redigindo o mail direto no prompt, de forma a tornar impossvel a correo de uma frase anterior onde, sem querer, voc escreveu um ns vai, voc edita um arquivo com o contedo da mensagem e aps umas quinze verificaes sem constatar nenhum erro, decide envi-lo e para tal faz:
$ mail chefe@chefia.com.br < U arquivocommailparaochefe

Etiquetas Erradas
Um erro comum no uso de labels (como o fimftp do exemplo anterior) causado pela presena de espaos em branco antes ou aps o mesmo. Fique muito atento quanto a isso, por que este tipo de erro costuma dar uma boa surra no programador, at que seja detectado. Lembre-se: um label que se preze tem que ter uma linha inteira s para ele.

Para que voc teste a Sada de Erro Padro direto no prompt do seu Shell, vou dar mais um exemplo. Faa:
$ ls naoexiste bash: naoexiste no such file U or directory $ ls naoexiste 2> arquivodeerros $ $ cat arquivodeerros bash: naoexiste no such file U or directory

e o chefe receber uma mensagem com o contedo do arquivocommailparaochefe. Outro tipo de redirecionamento muito louco que o Shell permite o chamado here document. Ele representado por << e serve para indicar ao Shell que o escopo de um comando comea na linha seguinte e termina quando encontra uma linha cujo contedo seja unicamente o label que segue o sinal <<. Veja o fragmento de script a seguir, com uma rotina de ftp:
ftp -ivn hostremoto << fimftp user $Usuario $Senha binary get arquivoremoto fimftp

Neste exemplo, vimos que quando fizemos um ls em naoexiste, ganhamos uma mensagem de erro. Aps redirecionar a Sada de Erro Padro para arquivodeerros e executar o mesmo comando, recebemos somente o prompt na tela. Quando listamos o contedo do arquivo para o qual foi redirecionada a Sada de Erro Padro, vimos que a mensagem de erro tinha sido armazenada nele. interessante notar que estes caracteres de redirecionamento so cumulativos, isto , se no exemplo anterior fizssemos o seguinte:
$ ls naoexiste 2>> U arquivodeerros

a mensagem de erro oriunda do ls seria anexada ao final de arquivodeerros.

neste pedacinho de programa temos um monte de detalhes interessantes: As opes usadas para o ftp (-ivn) servem para ele listar tudo que est acontecendo (opo -v de verbose), para no ficar perguntando se voc tem certeza que deseja transmitir cada arquivo (opo -i de interactive) e finalmente a opo -n serve para dizer ao ftp para ele no solicitar o usurio e sua senha, pois estes sero informados pela instruo especfica (user); Quando eu usei o << fimftp, estava dizendo o seguinte para o interpretador: Olha aqui Shell, no se meta em

nada a partir deste ponto at encontrar o label fimftp. Voc no entenderia droga nenhuma, j que so instrues especficas do ftp. Se fosse s isso seria simples, mas pelo prprio exemplo d para ver que existem duas variveis ($Usuario e $Senha), que o Shell vai resolver antes do redirecionamento. Mas a grande vantagem deste tipo de construo que ela permite que comandos tambm sejam interpretados dentro do escopo do here document, o que, alis, contraria o que acabei de dizer. Logo a seguir te explico como esse negcio funciona. Agora ainda no d, esto faltando ferramentas. O comando user do repertrio de instrues do ftp e serve para passar o usurio e a senha que haviam sido lidos em uma rotina anterior a este fragmento de cdigo e colocados respectivamente nas duas variveis: $Usuario e $Senha. O binary outra instruo do ftp, que serve para indicar que a transferncia de arquivoremoto ser feita em modo binrio, isto o contedo do arquivo no ser inteerpretado para saber se est em ASCII, EBCDIC, O comando get arquivoremoto diz ao cliente ftp para pegar este arquivo no servidor hostremoto e traz-lo para a nossa mquina local. Se quisssemos enviar um arquivo, bastaria usar, por exemplo, o comando put arquivolocal.

Redirecionamento da Entrada Padro


Para fazermos o redirecionamento da Entrada Padro usamos o < (menor que). E pra que serve isso?, voc vai me perguntar. Deixa eu dar um exemplo, que voc vai entender rapidinho. Suponha que voc queira mandar um mail para o seu chefe. Para o chefe ns

Direito de Posse
O $$ contm o PID,isto ,o nmero do seu processo. Como o Linux multiusurio, bom anexar sempre o $$ ao nome dos seus arquivos para no haver problema de propriedade,isto ,caso voc batizasse o seu arquivo simplesmente como seraqueexiste,a primeira pessoa que o usasse (criando-o ento) seria o seu dono e a segunda ganharia um erro quando tentasse gravar algo nele.

Redirecionamento de comandos
Os redirecionamentos de que falamos at agora sempre se referiam a arquivos, isto , mandavam para arquivo, recebiam de arquivo, simulavam arquivo local, O que veremos a partir de agora, redireciona a sada de um comando para a entrada de outro. utilssimo e, apesar de no ser macaco gordo, sempre quebra os

www.linuxmagazine.com.br

Agosto 2004

85

LINUX USER

Papo de Botequim

maiores galhos. Seu nome pipe (que em ingls significa tubo, j que ele canaliza a sada de um comando para a entrada de outro) e sua representao a | (barra vertical).
$ ls | wc -l 21

$ echo "Existem who | wc -l U usuarios conectados" Existem who | wc -l usuarios U conectados

$ (pwd ; cd /etc ; pwd) /home/meudir /etc $ pwd /home/meudir

O comando ls passou a lista de arquivos para o comando wc, que quando est com a opo -l conta a quantidade de linhas que recebeu. Desta forma, podemos afirmar categoricamente que no meu diretrio existiam 21 arquivos.
$ cat /etc/passwd | sort | lp

Hi! Olha s, no funcionou! mesmo, no funcionou e no foi por causa das aspas que eu coloquei, mas sim por que eu teria que ter executado o who | wc -l antes do echo. Para resolver este problema, tenho que priorizar a segunda parte do comando com o uso de crases:
$ echo "Existem `who | wc -l` U usuarios conectados" Existem 8 usuarios U conectados

A linha de comandos acima manda a listagem do arquivo /etc/passwd para a entrada do comando sort. Este a classifica e envia para o lp que o gerenciador da fila de impresso.

Para eliminar esse monte de brancos antes do 8 que o wc -l produziu, basta retirar as aspas. Assim:
$ echo Existem `who | wc -l` U usuarios conectados Existem 8 usuarios conectados

Caracteres de ambiente
Quando queremos priorizar uma expresso, ns a colocamos entre parnteses, no ? Pois , por causa da aritmtica normal pensarmos deste jeito. Mas em Shell o que prioriza mesmo so as crases (`) e no os parnteses. Vou dar exemplos para voc entender melhor. Eu quero saber quantos usurios esto logados no computador que eu administro. Eu posso fazer:
$ who | wc -l 8

Quequeiiisso minha gente? Eu estava no /home/meudir, mudei para o /etc, constatei que estava neste diretrio com o pwd seguinte e quando o agrupamento de comandos terminou, eu vi que continuava no /etc/meudir! Hi! Ser que tem coisa do mgico Mandrake por a? Nada disso. O interessante do uso de parnteses que eles invocam um novo Shell para executar os comandos que esto em seu interior. Desta forma, fomos realmente para o diretrio /etc, porm aps a execuo de todos os comandos, o novo Shell que estava no diretrio /etc morreu e retornamos ao Shell anterior que estava em /home/meudir. Que tal usar nossos novos conceitos?
$ mail suporte@linux.br << FIM Ola suporte, hoje as `date U +%hh:mm` ocorreu novamente U aquele problema que eu havia U reportado por telefone. De U acordo com seu pedido segue a U listagem do diretorio: `ls -l` Abracos a todos. FIM

As aspas protegem da interpretao do Shell tudo que est dentro dos seus limites. Como para o Shell basta um espao em branco como separador, o monte de espaos ser trocado por um nico aps a retirada das aspas. Outra coisa interessante o uso do ponto-e-vrgula. Quando estiver no Shell, voc deve sempre dar um comando em cada linha. Para agrupar comandos em uma mesma linha, temos que separ-los por ponto-e-vrgula. Ento:
$ pwd ; cd /etc; pwd ;cd -;pwd /home/meudir /etc /home/meudir

O comando who passa a lista de usurios conectados ao sistema para o comando wc -l, que conta quantas linhas recebeu e mostra a resposta na tela. Muito bem, mas ao invs de ter um nmero oito solto na tela, o que eu quero mesmo que ele esteja no meio de uma frase. Ora, para mandar frases para a tela eu s preciso usar o comando echo; ento vamos ver como que fica:

Buraco Negro
Em Unix existe um arquivo fantasma. Chama-se /dev/null.Tudo que enviado para este arquivo some. Assemelha-se a um Buraco Negro. No caso do exemplo, como no me interessava guardar a possvel mensagem de erro oriunda do comando rm, redirecionei-a para este arquivo.

Neste exemplo, listei o nome do diretrio corrente com o comando pwd, mudei para o diretrio /etc, novamente listei o nome do diretrio e finalmente voltei para o diretrio onde estava anteriormente (cd -), listando seu nome. Repare que coloquei o ponto-e-vrgula de todas as formas possveis, para mostrar que no importa se existem espaos em branco antes ou aps este caracter. Finalmente, vamos ver o caso dos parnteses. No exemplo a seguir, colocamos diversos comandos separados por ponto-e-vrgula entre parnteses:

Finalmente agora podemos demonstrar o que conversamos anteriormente sobre here document. Os comandos entre crases tem prioridade, portanto o Shell os executar antes do redirecionamento do here document. Quando o suporte receber a mensagem, ver que os comandos date e ls foram executados antes do comando mail, recebendo ento um instantneo do ambiente no momento de envio do email. - Garom, passa a rgua! s
Julio Cezar Neves Analista de Suporte de Sistemas desde 1969 e trabalha com Unix desde 1980, quando fez parte da equipe que desenvolveu o SOX, sistema operacional, similar ao Unix, da Cobra Computadores. professor do curso de Mestrado em Software Livre das Faculdades Estcio de S, no Rio de Janeiro.

86

Agosto 2004

www.linuxmagazine.com.br

SOBRE O AUTOR

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 colocar a mo na massa pela primeira vez, construindo um aplicativo simples para catalogar uma coleo de CDs. POR JLIO CSAR NEVES

arom! Traz um chops e dois pastel. O meu amigo hoje no vai beber porque est finalmente sendo apresentado a um verdadeiro sistema operacional, e ainda tem muita coisa a aprender! E ento, amigo, t entendendo tudo que te expliquei at agora? Entendendo eu t, mas no vi nada prtico nisso Calma rapaz, o que te falei at agora serve como base ao que h de vir daqui pra frente. Vamos usar essas ferramentas que vimos para montar programas estruturados. Voc ver porque at na TV j teve programa chamado O Shell o Limite. Para comear vamos falar dos comandos da famlia grep Grep? No conheo nenhum termo em ingls com este nome claro, grep um acrnimo (sigla) para Global Regular Expression Print, que usa expresses regulares para pesquisar a ocorrncia de cadeias de caracteres na entrada definida. Por falar em expresses regulares (ou regexp), o Aurlio Marinho Jargas escreveu dois artigos [1 e 2] imperdveis para a Revista do Linux sobre esse assunto e tambm publicou um livro [3] pela Editora Novatec. Acho bom voc ler esses artigos, eles vo te ajudar no que est para vir.

$ grep franklin /etc/passwd

Pesquisando em vrios arquivos:


$ grep grep *.sh

Pesquisando na sada de um comando:


$ who | grep carvalho

Eu fico com grep,voc com gripe


Esse negcio de gripe brincadeira, s um pretexto para pedir umas caipirinhas. Eu te falei que o grep procura cadeias de caracteres dentro de uma entrada definida, mas o que vem a ser uma entrada definida? Bem, existem vrias formas de definir a entrada do comando grep. Veja s. Para pesquisar em um arquivo:

No 1 exemplo, procurei a palavra franklin em qualquer lugar do arquivo /etc/passwd. Se quisesse procurar um nome de usurio, isto , somente no incio dos registros desse arquivo, poderia digitar $ grep ^franklin /etc/passwd. E para que servem o circunflexo e os apstrofos?, voc vai me perguntar. Se tivesse lido os artigos que mencionei, saberia que o circunflexo serve para limitar a pesquisa ao incio de cada linha e os apstrofos servem para o Shell no interpretar esse circunflexo, deixando-o passar inclume para o comando grep. No 2 exemplo mandei listar todas as

linhas que usavam a palavra grep, em todos os arquivos terminados em .sh. Como uso essa extenso para definir meus arquivos com programas em Shell, malandramente, o que fiz foi listar as linhas dos programas que poderia usar como exemplo do comando grep. Olha que legal! O grep aceita como entrada a sada de outro comando, redirecionado por um pipe (isso muito comum em Shell e um tremendo acelerador da execuo de comandos). Dessa forma, no 3 exemplo, o comando who listou as pessoas logadas na mesma mquina que voc (no se esquea jamais: o Linux multiusurio) e o grep foi usado para verificar se o Carvalho estava trabalhando ou coando. O grep um comando muito conhecido, pois usado com muita freqncia. O que muitas pessoas no sabem que existem trs comandos na famlia grep: grep, egrep e fgrep. A principais diferenas entre os 3 so: grep - Pode ou no usar expresses regulares simples, porm no caso de no us-las, o fgrep melhor, por ser mais rpido. egrep (e de extended, estendido) - muito poderoso no uso de expresses regulares. Por ser o mais poderoso dos trs, s deve ser usado quando for necessria a elaborao de uma expresso regular no aceita pelo grep. fgrep (f de fast, rpido) - Como o nome diz, o ligeirinho da famlia, executando o servio de forma muito veloz (por vezes cerca de 30% mais rpido que o grep e 50% mais que o egrep), porm no permite o uso de expresses regulares na pesquisa. Agora que voc j conhece as diferenas entre os membros da famlia, me diga: o que voc acha dos trs exemplos que eu dei antes das explicaes? Achei que o fgrep resolveria o teu problema mais rapidamente que o grep. Perfeito! T vendo que voc est atento, entendendo tudo que estou te explicando! Vamos ver mais exemplos

www.linuxmagazine.com.br

Setembro 2004

87

LINUX USER

Papo de Botequim

Quadro 1 - Listando subdiretrios


$ ls -l | grep drwxr-xr-x drwxr-xr-x drwxr-xr-x drwxr-xr-x drwxr-xr-x drwxrwxr-x drwxrwxr-x drwxrwxr-x drwxr-xr-x drwxrwxr-x drwxr-xr-x drwxr-xr-x ^d 3 11 3 3 2 14 12 3 3 3 3 3 root root root root root root root root root root root root root root root root root root root root root root root root 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 4096 Dec Jul Oct Aug Aug Jul Jan Jan Jul Jan Dec Jun 18 2000 doc 13 18:58 freeciv 17 2000 gimp 8 2000 gnome 8 2000 idl 13 18:58 locale 14 2000 lyx 17 2000 pixmaps 2 20:30 scribus 17 2000 sounds 18 2000 xine 19 2000 xplns

ser explorado). Pra a! De onde eu vou receber os dados dos CDs? Vou mostrar como o programa pode receber parmetros de quem o estiver executando e, em breve, ensinarei a ler os dados da tela ou de um arquivo.

Passando parmetros
Veja abaixo a estrutura do arquivo contendo a lista das msicas:
nomedolbum^intrprete1~nomeU damsica1:...:intrpreten~nomeU damsican

para clarear de vez as diferenas de uso entre os membros da famlia. Eu sei que em um arquivo qualquer existe um texto falando sobre Linux, s no tenho certeza se est escrito com L maisculo ou minsculo. Posso fazer uma busca de duas formas:
egrep (Linux | linux) arquivo.txt

-l). Os apstrofos foram usados para o Shell no ver o circunflexo. Vamos ver mais um. Veja na Tabela 1 as quatro primeiras posies possveis da sada de um ls -l em um arquivo comum (no diretrio, nem link, nem ). Para descobrir todos os arquivos executveis em um determinado diretrio eu poderia fazer:
$ ls -la | egrep ^-..(x|s)

ou ento:
grep [Ll]inux arquivo.txt

Isto , o nome do lbum ser separado por um circunflexo do resto do registro, formado por diversos grupos compostos pelo intrprete de cada msica do CD e a msica interpretada. Estes grupos so separados entre si por dois pontos (:) e, internamente, o intrprete ser separado por um til (~) do nome da msica. Quero escrever um programa chamado musinc, que incluir registros no meu arquivo msicas. Passarei cada lbum como parmetro para o programa:
$ musinc lbum^interprete~U musica:interprete~musica:...

No primeiro caso, a expresso regular complexa (Linux | linux) usa os parnteses para agrupar as opes e a barra vertical (|) usada como um ou (or, em ingls) lgico, isto , estou procurando Linux ou linux. No segundo, a expresso regular [Ll]inux significa: comeado por L ou l seguido de inux. Como esta uma expresso simples, o grep consegue resolv-la, por isso melhor usar a segunda forma, j que o egrep tornaria a pesquisa mais lenta. Outro exemplo. Para listar todos os subdiretrios do diretrio corrente, basta usar o comando $ ls -l | grep ^d. Veja o resultado no Quadro 1. No exemplo, o circunflexo (^) serviu para limitar a pesquisa primeira posio da sada do ls longo (parmetro

novamente usamos o circunflexo para limitar a pesquisa ao incio de cada linha, ou seja, listamos as linhas que comeam por um trao (-), seguido de qualquer coisa (o ponto), novamente seguido de qualquer coisa, e por fim um x ou um s. Obteramos o mesmo resultado se usssemos o comando:
$ ls -la | grep ^-..[xs]

e alm disso, agilizaramos a pesquisa.

A CDteca
Vamos comear a desenvolver programas! Creio que a montagem de um banco de dados de msicas bacana para efeito didtico (e til nestes tempos de downloads de arquivos MP3 e queimadores de CDs). No se esquea que, da mesma forma que vamos desenvolver um monte de programas para organizar os seus CDs de msica, com pequenas adaptaes voc pode fazer o mesmo para organizar os CDs de software que vm com a Linux Magazine e outros que voc compra ou queima, e disponibilizar esse banco de software para todos os que trabalham com voc (o Linux multiusurio, e como tal deve

Desta forma, musinc estar recebendo os dados de cada lbum como se fosse uma varivel. A nica diferena entre um parmetro recebido e uma varivel que os primeiros recebem nomes numricos (o que quis dizer que seus nomes so formados somente por um algarismo, isto , $1, $2, $3, , $9). Vamos, fazer mais alguns testes:
$ cat teste #!/bin/bash #Teste de passagem echo 1o. parm -> echo 2o. parm -> echo 3o. parm ->

de parametros $1 $2 $3

Agora vamos rodar esse programinha:


$ teste passando parametros para U testar bash: teste: cannot execute

Tabela 1
Posio Valores possveis

1 2 3 4

r ou w ou x,s(suid) ou -

Ops! Esqueci-me de tornar o script executvel. Vou fazer isso e testar novamente o programa:

88

Setembro 2004

www.linuxmagazine.com.br

Papo de Botequim

LINUX USER

$ chmod 755 teste $ teste passando parametros para U testar 1o. parm -> passando 2o. parm -> parametros 3o. parm -> para

Execute o programa:
$ teste passando parametros para testar O programa teste recebeu 4 U parametros 1o. parm -> passando 2o. parm -> parametros 3o. parm -> para Para listar todos de uma U tacada eu faco passando U parametros para testar

Repare que a palavra testar, que seria o quarto parmetro, no foi listada. Isso ocorreu porque o programa teste s lista os trs primeiros parmetros recebidos. Vamos execut-lo de outra forma:
$ teste passando parametros U para testar 1o. parm -> passando parametros 2o. parm -> para 3o. parm -> testar

incluso de CDs no meu banco chamado musicas. O programa muito simples (como tudo em Shell). Veja a Listagem 1. O script simples e funcional; limitome a anexar ao fim do arquivo musicas o parmetro recebido. Vamos cadastrar 3 lbuns para ver se funciona (para no ficar enchendo lingia, suponho que em cada CD s existem duas msicas):
$ musinc album3^Artista5U ~Musica5:Artista6~Musica5 $ musinc album1^Artista1U ~Musica1:Artista2~Musica2 $ musinc album 2^Artista3U ~Musica3:Artista4~Musica4

As aspas no deixaram o Shell ver o espao em branco entre as duas primeiras palavras, e elas foram consideradas como um nico parmetro. E falando em passagem de parmetros, uma dica: veja na Tabela 2 algumas variveis especiais. Vamos alterar o programa teste para usar as novas variveis:
$ cat teste #!/bin/bash # Programa para testar passagem U de parametros (2a. Versao) echo O programa $0 recebeu $# U parametros echo 1o. parm -> $1 echo 2o. parm -> $2 echo 3o. parm -> $3 echo Para listar todos de uma U \tacada\ eu faco $*

Repare que antes das aspas usei uma barra invertida, para escond-las da interpretao do Shell (se no usasse as contrabarras as aspas no apareceriam). Como disse, os parmetros recebem nmeros de 1 a 9, mas isso no significa que no posso usar mais de nove parmetros. Significa que s posso enderear nove. Vamos testar isso:
$ cat teste #!/bin/bash # Programa para testar passagem U de parametros (3a. Versao) echo O programa $0 recebeu $# U parametros echo 11o. parm -> $11 shift echo 2o. parm -> $1 shift 2 echo 4o. parm -> $1

Listando o contedo do arquivo musicas:


$ cat musicas album3^Artista5~Musica5:Artista6U ~Musica6 album1^Artista1~Musica1:Artista2U ~Musica2 album2^Artista3~Musica3:Artista4U ~Musica4

Execute o programa:
$ teste passando parametros para U testar O programa teste recebeu 4 U parametros que so: 11o. parm -> passando1 2o. parm -> parametros 4o. parm -> testar

Podia ter ficado melhor. Os lbuns esto fora de ordem, dificultando a pesquisa. Vamos alterar nosso script e depois testlo novamente. Veja a listagem 2. Simplesmente inseri uma linha que classifica o arquivo musicas, redirecionando a sada para ele mesmo (para isso serve a opo -o), aps cada lbum ser anexado.
$ cat musicas album1^Artista1~Musica1:Artista2U ~Musica2 albu2^Artista3~Musica3:Artista4U ~Musica4 album3^Artista5~Musica5:Artista6U ~Musica6

Listagem 1: Incluindo CDs na CDTeca


$ cat musinc #!/bin/bash # Cadastra CDs (versao 1) # echo $1 >> musicas

Listagem 2
$ cat musinc #!/bin/bash # Cadastra CDs (versao 2) # echo $1 >> musicas sort -o musicas musicas

Duas coisas muito interessantes aconteceram neste script. Para mostrar que os nomes dos parmetros variam de $1 a $9 digitei echo $11 e o que aconteceu? O Shell interpretou como sendo $1 seguido do algarismo 1 e listou passando1; O comando shift, cuja sintaxe shift n, podendo o n assumir qualquer valor numrico, despreza os n primeiros parmetros, tornando o parmetro de ordem n+1. Bem, agora que voc j sabe sobre passagem de parmetros, vamos voltar nossa cdteca para fazer o script de

Oba! Agora o programa est legal e quase funcional. Ficar muito melhor em uma nova verso, que desenvolveremos aps aprender a adquirir os dados da tela e formatar a entrada.

Tabela 2: Variveis especiais


Varivel Significado

$0 $# $*

Contm o nome do programa Contm a quantidade de parmetros passados Contm o conjunto de todos os parmetros (muito parecido com $@)

www.linuxmagazine.com.br

Setembro 2004

89

LINUX USER

Papo de Botequim

Ficar listando arquivos com o comando cat no est com nada, vamos fazer um programa chamado muslist para listar um lbum, cujo nome ser passado como parmetro. Veja o cdigo na Listagem 3: Vamos execut-lo, procurando pelo album 2. Como j vimos antes, para passar a string album 2 necessrio proteg-la da interpretao do Shell, para que ele no a interprete como dois parmetros. Exemplo:
$ muslist album 2 grep: cant open 2 musicas: album1^Artista1~Musica1U :Artista2~Musica2 musicas: album2^Artista3~Musica3U :Artista4~Musica4 musicas:album3^Artista5~Musica5U :Artista6~Musica6

grep <cadeia de caracteres> U [arq1, arq2, ..., arqn]

Listagem 5 - musexc
$ cat musexc #!/bin/bash # Exclui CDs (versao 1) # grep -v $1 musicas > /tmp/mus$$ mv -f /tmp/mus$$ musicas

O grep entendeu que deveria procurar a cadeia de caracteres album nos arquivos 2 e musicas. Como o arquivo 2 no existe, grep gerou o erro e, por encontrar a palavra album em todos os registros de musicas, listou a todos. melhor ignorarmos maisculas e minsculas na pesquisa. Resolveremos os dois problemas com a Listagem 4. Nesse caso, usamos a opo -i do grep que, como j vimos, serve para ignorar maisculas e minsculas, e colocamos o $1 entre aspas, para que o grep continuasse a ver a cadeia de caracteres resultante da expanso da linha pelo Shell como um nico argumento de pesquisa.
$ muslist album 2 album2^Artista3~Musica3:Artista4U ~Musica4

Que lambana! Onde est o erro? Eu tive o cuidado de colocar o parmetro passado entre aspas para o Shell no o dividir em dois! , mas repare como o grep est sendo executado:
grep $1 musicas

Mesmo colocando lbum 2 entre aspas, para que fosse encarado como um nico parmetro, quando o $1 foi passado pelo Shell para o comando grep, transformouse em dois argumentos. Dessa forma, o contedo da linha que o grep executou foi o seguinte:
grep album 2 musicas

Como a sintaxe do grep :

Listagem 3 - muslist
$ cat muslist #!/bin/bash # Consulta CDs (versao 1) # grep $1 musicas

Agora repare que o grep localiza a cadeia pesquisada em qualquer lugar do registro; ento, da forma que estamos fazendo, podemos pesquisar por lbum, por msica, por intrprete e mais. Quando conhecermos os comandos condicionais, montaremos uma nova verso de muslist que permitir especificar por qual campo pesquisar. Ah! Em um dia de vero voc foi praia, esqueceu os CDs no carro, aquele solzinho de 40 graus empenou seu disco favorito e agora voc precisa de uma ferramenta para remov-lo do banco de dados? No tem problema, vamos desenvolver um script chamado musexc, para excluir estes CDs. Antes de desenvolver o bacalho, quero te apresentar a uma opo bastante til da famlia de comandos grep. a opo -v, que quando usada lista todos os registros da entrada, exceto o(s) localizado(s) pelo comando. Exemplos:
$ grep -v album 2 musicas album1^Artista1~Musica1:Artista2U ~Musica2 album3^Artista5~Musica5:Artista6U ~Musica6

mando. Estamos ento prontos para desenvolver o script para remover CDs empenados da sua CDteca. Veja o cdigo da Listagem 5. Na primeira linha mandei para /tmp/mus$$ o arquivo musicas, sem os registros que atendessem a consulta feita pelo comando grep. Em seguida, movi /tmp/mus$$ por cima do antigo musicas. Usei o arquivo /tmp/mus$$ como arquivo de trabalho porque, como j havia citado no artigo anterior, o $$ contm o PID (identificao do processo) e, dessa forma, cada um que editar o arquivo musicas o far em um arquivo de trabalho diferente, evitando colises. Os programas que fizemos at aqui ainda so muito simples, devido falta de ferramentas que ainda temos. Mas bom praticar os exemplos dados porque, eu prometo, chegaremos a desenvolver um sistema bacana para controle dos seus CDs. Na prxima vez que nos encontrarmos, vou te ensinar como funcionam os comandos condicionais e aprimoraremos mais um pouco esses scripts. Por hoje chega! J falei demais e estou de goela seca! Garom! Mais um s sem colarinho!

INFORMAES
[1] http://www.revistadolinux.com.br/ed/003/ ferramentas.php3 [2] http://www.revistadolinux.com.br/ed/007/ ereg.php3 [3] http://www.aurelio.net/er/livro/

Listagem 4 muslist melhorado


$ cat muslist #!/bin/bash # Consulta CDs (versao 2) # grep -i $1 musicas

Conforme expliquei antes, o grep do exemplo listou todos os registros de musicas exceto o referente a album 2, porque atendia ao argumento do co-

Julio Cezar Neves Analista de Suporte de Sistemas desde 1969 e trabalha com Unix desde 1980, quando fez parte da equipe que desenvolveu o SOX, um sistema operacional similar ao Unix, produzido pela Cobra Computadores.

90

Setembro 2004

www.linuxmagazine.com.br

SOBRE O AUTOR


Papo de Botequim LINUX USER

Curso de Shell Script

Papo de botequim III


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 usado para cortar um determinado pedao de um arquivo. A sintaxe e alguns exemplos de uso podem ser vistos no Quadro 1: Como d para ver, existem quatro sintaxes distintas: na primeira (-c 1-5) especifiquei uma faixa, na segunda (-c -6) especifiquei todo o texto at uma posio, na terceira (-c 4-) tudo de uma determinada posio em diante e na quarta (-c 1,3,5,7,9), s as posies determinadas. A ltima possibilidade (-c -3,5,8-) foi s para mostrar que podemos misturar tudo. Mas no pense que acabou por a! Como voc deve ter percebido, esta forma de cut muito til para lidar com arquivos com campos de tamanho xo, mas atualmente o que mais existe so arquivos com campos de tamanho varivel, onde cada campo termina com um delimitador. Vamos dar uma olhada no arquivo musicas que comeamos a preparar na ltima vez que viemos aqui no botequim. Veja o Quadro 2.

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

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 circunexo (^) do resto do registro, que formado por diversos grupos compostos pelo intrprete de cada msica do CD e a respectiva msica interpretada. Estes grupos so separados entre si por dois-pontos (:) e o intrprete ser separado do nome da msica por um til (~). Ento, para pegar os dados referentes a todas as segundas msicas do arquivo musicas, devemos digitar:
$ cut -f2 -d: musicas Artista2~Musica2 Artista4~Musica4 Artista6~Musica5 Artista8~Musica8@10_L:

Artista2 Artista4 Artista6 Artista8

Para entender melhor isso, vamos analisar a primeira linha de musicas:


$ head -1 musicas album 1^Artista1~Musica1: U Artista2~Musica2

Ento observe o que foi feito:


album 1^Artista1~Musica1: U Artista2~Musica2

Ou seja, cortamos o segundo campo z(-f de eld, campo em ingls) delimitado (-d) por dois-pontos (:). Mas, se quisermos somente os intrpretes, devemos digitar:
$ cut -f2 -d: musicas | cut U -f1 -d~

Desta forma, no primeiro cut o primeiro campo do delimitador (-d) dois-pontos (:) album 1^Artista1~Musica1 e o segundo, que o que nos interessa, Artista2~Musica2. Vamos ento ver o que aconteceu no segundo cut:
Artista2~Musica2

Agora, primeiro campo do delimitador (-d) til (~), que o que nos interessa, Artista2 e o segundo Musica2. Se o raciocnio que fizemos para a pri-

www.linuxmagazine.com.br

Outubro 2004

85


LINUX USER Papo de Botequim

Quadro 1 O comando cut


A sintaxe do cut : cut -c PosIni-PosFim [arquivo], onde PosIni a posio inicial, e PosFim a posio nal. Veja os exemplos: $ cat numeros 1234567890 0987654321 1234554321 9876556789 $ cut -c1-5 numeros 12345 09876 12345 98765 $ cut -c-6 numeros 123456 098765 123455 987655 $ cut -c4- numeros 4567890 7654321 4554321 6556789 $ cut -c1,3,5,7,9 numeros 13579 08642 13542 97568 $ cut -c -3,5,8- numeros 1235890 0986321 1235321 9875789

meira linha for aplicado ao restante do arquivo, chegaremos resposta anteriormente dada. Outro comando muito interessante o tr que serve para substituir, comprimir ou remover caracteres. Sua sintaxe segue o seguinte padro:
tr [opes] cadeia1 [cadeia2]

posta a um exerccio prtico, valendo nota, que passei ele me entregou um script com todos os comandos separados por ponto-e-vrgula (lembre-se que o ponto-e-vrgula serve para separar diversos comandos em uma mesma linha). Vou dar um exemplo simplicado, e idiota, de um script assim:
$ cat confuso echo leia Programao Shell U Linux do Julio Cezar Neves U > livro;cat livro;pwd;ls;rm U -f livro2>/dev/null;cd ~

O comando copia o texto da entrada padro (stdin), troca as ocorrncia dos caracteres de cadeia1 pelo seu correspondente na cadeia2 ou troca mltiplas ocorrncias dos caracteres de cadeia1 por somente um caracter, ou ainda caracteres da cadeia1. As principais opes do comando so mostradas na Tabela 1. Primeiro veja um exemplo bem bobo:
$ echo bobo | tr o a baba

Eu executei o programa e ele funcionou:


$ confuso leia Programao Shell Linux U do Julio Cezar Neves /home/jneves/LM confuso livro musexc musicas musinc muslist numeros

Isto , troquei todas as ocorrncias da letra o pela letra a. Suponha que em determinado ponto do meu script eu pea ao operador para digitar s ou n (sim ou no), e guardo sua resposta na varivel $Resp. Ora, o contedo de $Resp pode conter letras maisculas ou minsculas, e desta forma eu teria que fazer diversos testes para saber se a resposta dada foi S, s, N ou n. Ento o melhor fazer:
$ Resp=$(echo $Resp | tr SN sn)

Mas nota de prova coisa sria (e nota de dlar mais ainda) ento, para entender o que o aluno havia feito, o chamei e em sua frente digitei:
$ tr ; \n < confuso echo leia Programao Shell U Linux do Julio Cezar Neves pwd cd ~ ls -l rm -f lixo 2>/dev/null

e aps este comando eu teria certeza de que o contedo de $Resp seria um s ou um n. Se o meu arquivo ArqEnt est todo em letras maisculas e desejo pass-las para minsculas eu fao:
$ tr A-Z a-z < ArqEnt > / tmp/$$ $ mv -f /tmp/$$ ArqEnt

O cara cou muito desapontado, porque em dois ou trs segundos eu des z a gozao que ele perdeu horas para fazer. Mas preste ateno! Se eu estivesse em uma mquina Unix, eu teria digitado:
$ tr ; \012 < confuso

Quadro 2 O arquivo musicas


$ cat musicas album 1^Artista1~Musica1:U Artista2~Musica2 album 2^Artista3~Musica3:U Artista4~Musica4 album 3^Artista5~Musica5:U Artista6~Musica5 album 4^Artista7~Musica7:U Artista8~Musica8

Note que neste caso usei a notao AZ para no escrever ABCDYZ. Outro tipo de notao que pode ser usada so as escape sequences (como eu traduziria? Seqncias de escape? Meio sem sentido, n? Mas v l) que tambm so reconhecidas por outros comandos e tambm na linguagem C, e cujo signicado voc ver na Tabela 2: Deixa eu te contar um causo: um aluno que estava danado comigo resolveu complicar minha vida e como res-

Agora veja a diferena entre o resultado de um comando date executado hoje e outro executado h duas semanas:
Sun Sep 19 14:59:54 Sun Sep 5 10:12:33 2004 2004

Notou o espao extra aps o Sep na segunda linha? Para pegar a hora eu deveria digitar:
$ date | cut -f 4 -d 14:59:54

86
Outubro 2004
www.linuxmagazine.com.br


Papo de Botequim LINUX USER
Mas h duas semanas ocorreria o seguinte:
$ date | cut -f 4 -d 5

Isto porque existem 2 caracteres em branco antes do 5 (dia). Ento o ideal seria transformar os espaos em branco consecutivos em somente um espao para poder tratar os dois resultados do comando date da mesma forma, e isso se faz assim:
$ date | tr -s Sun Sep 5 10:12:33 2004

E agora eu posso cortar:


$ date | tr -s | cut -f 4 U -d 10:12:33

Olha s como o Shell est quebrando o galho. Veja o contedo de um arquivo baixado de uma mquina Windows:
$ cat -ve ArqDoDOS.txt Este arquivo^M$ foi gerado pelo^M$ DOS/Win e foi^M$ baixado por um^M$ ftp mal feito.^M$

Dica: a opo -v do cat mostra os caracteres de controle invisveis, com a notao ^L, onde ^ a tecla Control e L a respectiva letra. A opo -e mostra o nal da linha como um cifro ($). Isto ocorre porque no DOS o m dos registros indicado por um Carriage Return (\r Retorno de Carro, CR) e um Line Feed (\f Avano de Linha, ou LF). No Linux porm o nal do registro indicado somente pelo Line Feed. Vamos limpar este arquivo:
$ tr -d \r < ArqDoDOS.txt > /tmp/$$ $ mv -f /tmp/$$ ArqDoDOS.txt

Repare que o ms e o dia esto no mesmo formato em ambos os comandos. Ora, se em algum registro do who eu no encontrar a data de hoje, sinal $ tr -d \015 < ArqDoDOS.U que o usurio est logado h mais txt > /tmp/$$ de um dia, j que ele no pode ter se logado amanh Ento vamos guarUma 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 porque a transferncia do arquivo foi comando who: feita no modo binrio (ou image), Se antes da transmisso do arquivo tivesse $ Data=$(date | cut -f 2-3 U sido estipulada a opo ascii do ftp, isto -d ) no teria ocorrido. Olha, depois desta dica t comeando a Eu usei a construo $(...), para priogostar deste tal de shell, mas ainda tem rizar a execuo dos comandos antes muita coisa que no consigo fazer. de atribuir a sua sada varivel Data. Pois , ainda no te falei quase nada Vamos ver se funcionou: sobre programao em shell, ainda tem muita coisa para aprender, mas $ echo $Data com o que aprendeu, j d para resolSep 20 ver muitos problemas, desde que voc adquira o modo shell de pensar. Voc Beleza! Agora, o que temos que fazer seria capaz de fazer um script que diga procurar no comando who os registros quais pessoas esto logadas h mais que no possuem esta data. de um dia no seu servidor? Claro que no! Para isso seria necess- Ah! Eu acho que estou entendendo! rio eu conhecer os comandos condicioVoc falou em procurar e me ocorreu o nais que voc ainda no me explicou comando grep, estou certo? como funcionam. - Deixa eu tentar Certssimo! S que eu tenho que usar o mudar um pouco a sua lgica e trazgrep com aquela opo que ele s lista la para o modo shell de pensar, mas os registros nos quais ele no enconantes melhor tomarmos um chope. trou a cadeia. Voc se lembra que Agora que j molhei a palavra, vamos opo essa? resolver o problema que te propus. Veja Claro, a -v como funciona o comando who: Isso! T cando bom! Vamos ver:
$ who jneves 1 rtorres 0 rlegaria 1 lcarlos 3 $ who | grep -v $Data jneves pts/ U 1 Sep 18 13:40

Bem a opo -d do tr remove do arquivo todas as ocorrncias do caractere especificado. Desta forma eu removi os caracteres indesejados, salvei o texto em um arquivo temporrio e posteriormente renomeei-o para o nome original. Uma observao: em um sistema Unix eu deveria digitar:

E veja tambm o date:


$ date Mon Sep 20 10:47:19 BRT 2004

Agora vamos ver o que aconteceu:


$ cat -ve ArqDoDOS.txt Este arquivo$ foi gerado pelo$ DOS/Rwin e foi$ baixado por um$ ftp mal feito.$

pts/ U Sep 18 pts/ U Sep 20 pts/ U Sep 20 pts/ U Sep 20

13:40 07:01 08:19 10:01

Se eu quisesse um pouco mais de perfumaria eu faria assim:


who | grep -v $Data |U cut -f1 -d jneves $

Opo -s -d

Tabela 1 O comando tr
Signicado Comprime n ocorrncias de cadeia1 em apenas uma Remove os caracteres de cadeia1

Viu? No foi necessrio usar comando condicional, at porque o nosso comando condicional, o famoso if, no testa condio, mas sim instrues. Mas antes veja isso:

www.linuxmagazine.com.br

Outubro 2004

87


LINUX USER Papo de Botequim
$ ls musicas musicas $ echo $? 0 $ ls ArqInexistente ls: ArqInexistente: No such U le or directory $ echo $? 1 $ who | grep jneves jneves pts/1 Sep 18 U 13:40 (10.2.4.144) $ echo $? 0 $ who | grep juliana $ echo $? 1

Tabela 2
Seqncia \t \n \v \f \r \\ Signicado Tabulao Nova linha <ENTER> Tabulao Vertical Nova Pgina Incio da linha <^M> Uma barra invertida Octal \011 \012 \013 \014 \015 \0134

/dev/null then echo Usuario \$1\ j U existe else if useradd $1 then echo Usurio \$1\ U includo em /etc/passwd else echo Problemas no U cadastramento. Voc root?

O que que esse $? faz a? Algo comeado por cifro ($) parece ser uma varivel, certo? Sim uma varivel que contm o cdigo de retorno da ltima instruo executada. Posso te garantir que se esta instruo foi bem sucedida, $? ter o valor zero, caso contrrio seu valor ser diferente de zero. O que nosso comando condicional (if) faz testar esta varivel. Ento vamos ver a sua sintaxe:
if cmd then cmd1 cmd2 cmdn else cmd3 cmd4 cmdm

j existe else if useradd $1 then echo Usurio \$1\ U includo em /etc/passwd else echo Problemas no U cadastramento. Voc root?

Vamos test-lo como um usurio normal :


$ incusu ZeNinguem ./incusu[6]: useradd: not found Problemas no cadastramento. U Voc root?

Repare que o if est testando direto o comando grep e esta a sua nalidade. Caso o if seja bem sucedido, ou seja, o usurio (cujo nome est em $1) foi encontrado em /etc/passwd, os comandos do bloco do then sero executados (neste exemplo, apenas o echo). Caso contrrio, as instrues do bloco do else sero executadas, quando um novo if testa se o comando useradd foi executado a contento, criando o registro do usurio em /etc/passwd, ou exibindo uma mensagem de erro, caso contrrio. Executar o programa e passe como parmetro um usurio j cadastrado:
$ incusu jneves jneves:x:54002:1001:Julio Neves: U /home/jneves:/bin/ U bash Usuario jneves ja existe

Aquela mensagem de erro no deveria aparecer! Para evitar isso, devemos redirecionar a sada de erro (stderr) do comando useradd para /dev/null. A verso nal ca assim:
$ cat incusu #!/bin/bash # Verso 3 if grep ^$1 /etc/passwd > U /dev/null then echo Usuario \$1\ j U existe else if useradd $1 2> /dev/null then echo Usurio \$1\ U includo em /etc/passwd else echo Problemas no U cadastramento. Voc root?

Ou seja, caso comando cmd tenha sido executado com sucesso, os comandos do bloco do then (cmd1, cmd2 e cmdn) sero executados, caso contrrio, os comandos do bloco opcional do else (cmd3, cmd4 e cmdm) sero executados. O bloco do if terminando com um . Vamos ver na prtica como isso funciona, usando um script que inclui usurios no arquivo /etc/passwd:
$ cat incusu #!/bin/bash # Verso 1 if grep ^$1 /etc/passwd then echo Usuario \$1\U

No exemplo dado, surgiu uma linha indesejada, ela a sada do comando grep. Para evitar que isso acontea, devemos desviar a sada para /dev/null. O programa ca assim:
$ cat incusu #!/bin/bash # Verso 2 if grep ^$1 /etc/passwd > U

Depois disso, vejamos o comportamento do programa, se executado pelo root:


$ incusu botelho Usurio botelho incluido em U /etc/passwd

E novamente:
$ incusu botelho Usurio botelho j existe

88
Outubro 2004
www.linuxmagazine.com.br


Papo de Botequim LINUX USER
Lembra que eu falei que ao longo dos nossos papos e chopes os nossos programas iriam se aprimorando? Ento vejamos agora como podemos melhorar o nosso programa para incluir msicas na "CDTeca":
$ cat musinc #!/bin/bash # Cadastra CDs (versao 3) # if grep ^$1$ musicas > U /dev/null then echo Este lbum j est U cadastrado else echo $1 >> musicas sort musicas -o musicas

no incio da cadeia e cifro ($) no m, servem para testar se o parmetro (o lbum e seus dados) exatamente igual a algum registro j existente. Vamos executar nosso programa novamente, mas desta vez passamos como parmetro um lbum j cadastrado, pra ver o que acontece:
$ musinc album 4^Artista7~ U Musica7:Artista8~Musica8 Este lbum j est cadastrado

E agora um no cadastrado:
$ musinc album 5^Artista9~ U Musica9:Artista10~Musica10 $ cat musicas album 1^Artista1~Musica1: U Artista2~Musica2 album 2^Artista3~Musica3: U Artista4~Musica4 album 3^Artista5~Musica5: U Artista6~Musica5 album 4^Artista7~Musica7: U Artista8~Musica8 album 5^Artista9~Musica9: U Artista10~Musica10

Como voc viu, uma pequena evoluo em relao verso anterior. Antes de incluir um registro (que na verso anterior poderia ser duplicado), testamos se o registro comea (^) e termina ($) de forma idntica ao parmetro lbum passado ($1). O circunexo (^)

Como voc viu, o programa melhorou um pouquinho, mas ainda no est pronto. medida que eu te ensinar a programar em shell, nossa CDteca vai car cada vez melhor. Entendi tudo que voc me explicou, mas ainda no sei como fazer um if para testar condies, ou seja o uso normal do comando. Para isso existe o comando test, que testa condies. O comando if testa o comando test. Como j falei muito, preciso de uns chopes para molhar a palavra. Vamos parar por aqui e na prxima vez te explico direitinho o uso do test e de diversas outras sintaxes do if. Falou! Acho bom mesmo porque eu tambm j t cando zonzo e assim tenho tempo para praticar esse monte de coisas que voc me falou hoje. Para xar o que voc aprendeu, tente fazer um scriptizinho para informar se um determinado usurio, cujo nome ser passado como parmetro, est logado no sistema ou no. A Chico! traz mais dois chopes pra mim por favor

www.linuxmagazine.com.br

Outubro 2004

89


LINUX USER Papo de Botequim

Curso de Shell Script

O garon j perdeu a conta das cervejas, e o assunto no acaba. Desta 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

Papo de botequim IV
$ logado jneves jneves pts/0 12:02 (10.2.4.144) jneves est logado Oct 18 U

Dave Hamilton - www.sxc.hu

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 me pediu um script para informar se um determinado usurio, cujo nome ser passado como parmetro para o script, est logado (arghh!) ou no. Fiz o seguinte:
$ cat logado #!/bin/bash # Pesquisa se um usurio est # logado ou no if who | grep $1 then echo $1 est logado else echo $1 no est no pedao

Realmente funcionou. Passei meu nome de usurio como parmetro e ele disse que eu estava logado, porm ele imprimiu uma linha extra, que eu no pedi, que a sada do comando who. Para evitar que isso acontea, s mand-la para o buraco negro do mundo UNIX, o /dev/null. Vejamos ento como caria:
$ cat logado #!/bin/bash # Pesquisa se uma pessoa est # logada ou no (verso 2) if who | grep $1 > /dev/null then echo $1 est logado else echo $1 no est no pedao

Ah, agora sim! Lembre-se dessa pegadinha: 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 quando no acha uma cadeia de caracteres) e devemos redirecion-las para o buraco negro quando necessrio. Bem, agora vamos mudar de assunto: na ltima vez que nos encontramos aqui no botequim, quando j estvamos de goela seca, voc me perguntou como se testam condies. Para isso, usamos o comando test

Testes
Todos estamos acostumados a usar o if para testar condies, e estas so sempre maior que, menor que, maior ou igual a, menor ou igual a, igual a e

Tabela 1 Opes do test para arquivos


-s arq -f arq -e arq Opo arq existe e tem tamanho maior que zero arq existe e um arquivo regular arq existe Verdadeiro se

Calma rapaz! J vi que voc chegou cheio de teso. Primeiro vamos pedir os nossos chopes de praxe e depois vamos ao Shell. Chico, traz dois chopes, um sem colarinho! Aaah! Agora que j molhamos os nossos bicos, vamos dar uma olhada nos resultados do seu programa:

Agora vamos aos testes:


$ logado jneves jneves est logado $ logado chico chico no est no pedao

-x arq

-w arq

-r arq

-d arq

arq existe e com direito de execuo

arq existe e com direito de escrita

arq existe e com direito de leitura

arq existe e um diretrio

84
edio 04
www.linuxmagazine.com.br


Papo de Botequim LINUX USER

Tabela 2 Opes do test para cadeias de caracteres


Opo -z cadeia cadeia c1 = c2 Verdadeiro se: -n cadeia Tamanho de cadeia zero Tamanho de cadeia maior que zero Cadeia c1 e c2 so idnticas

A cadeia cadeia tem tamanho maior que zero

No exemplo, testei a existncia do diretrio lmb. Se no existisse (else), ele seria criado. J sei, voc vai criticar a minha lgica dizendo que o script no est otimizado. Eu sei, mas queria que voc o entendesse assim, para ento poder usar o ponto-de-espantao (!) como um negador do test. Veja s:
if test ! -d lmb then mkdir lmb cd lmb

diferente de. Para testar condies em Shell Script usamos o comando test, s que ele muito mais poderoso do que aquilo com que estamos acostumados. Primeiramente, veja na Tabela 1 as principais opes (existem muitas outras) para testar arquivos em disco e na Tabela 2 as principais opes para teste de cadeias de caracteres.

cadeia de caracteres 01 realmente diferente de 1. Porm, a coisa muda de gura quando as variveis so testadas numericamente, j que o nmero 1 igual ao nmero 01. Para mostrar o uso dos conectores -o (ou) e -a (e), veja um exemplo animal, programado direto no prompt do Bash. Me desculpem os zologos, mas eu no entendo nada de reino, lo, classe, ordem, famlia, gnero, espcie e outras coisas do tipo, desta forma o que estou chamando de famlia ou de gnero tem grande chance de estar total e completamente incorreto:
$ Familia=felinae $ Genero=gato $ if test $Familia = canidea U -a $Genero = lobo -o $Familia = U felina -a $Genero = leo > then > echo Cuidado > else > echo Pode passar a mo > Pode passar a mo

Tabela 3 Opes do test para nmeros


Opo n1 -eq n2 n1 -gt n2 n1 -lt n2 Verdadeiro se n1 -ne n2 n1 -ge n2 n1 -le n2 n1 e n2 so iguais Signicado equal not equal n1 e n2 no so iguais n1 maior que n2 n1 maior ou igual a n2 n1 menor que n2 n1 menor ou igual a n2

greater than less than

Desta forma o diretrio lmb seria criado somente se ele ainda no existisse, e esta negativa deve-se ao ponto de exclamao (!) precedendo a opo -d. Ao m da execuo desse fragmento de script, com certeza o programa estaria dentro do diretrio lmb. Vamos ver dois exemplos para entender a diferena na comparao entre nmeros e entre cadeias de caracteres.
cad1=1 cad2=01 if test $cad1 = $cad2 then echo As variveis so iguais. else echo As variveis so diferentes.

greater or equal less or equal

Pensa que acabou? Engano seu! Agora hora de algo mais familiar, as famosas comparaes com valores numricos. Veja a Tabela 3, e some s opes j apresentadas os operadores da Tabela 4. Ufa! Como voc viu, tem coisa pra chuchu, e o nosso if muito mais poderoso que o dos outros. Vamos ver em uns exemplos como isso tudo funciona. Testamos a existncia de um diretrio:
if test -d lmb then cd lmb else mkdir lmb cd lmb

Executando o fragmento de programa acima, teremos como resultado:


As variveis so diferentes.

Vamos agora alter-lo um pouco para que a comparao seja numrica:


cad1=1 cad2=01 if test $cad1 -eq $cad2 then echo As variveis so iguais. else echo As variveis so diferentes.

Neste exemplo, caso o animal fosse da famlia candea e (-a) do gnero lobo, ou (-o) da familia felina e (-a) do gnero leo, seria dado um alerta, caso contrrio a mensagem seria de incentivo. Ateno: Os sinais de maior (>) no incio das linhas internas ao if so os prompts de continuao (que esto de nidos na varivel $PS2). Quando o shell identica que um comando continuar na linha seguinte, automaticamente ele coloca este caractere, at que o comando seja encerrado. Vamos mudar o exemplo para ver se o programa continua funcionando:
$ Familia=felino $ Genero=gato $ if test $Familia = felino -o U $Familia = canideo -a $Genero = U ona -o $Genero = lobo > then > echo Cuidado > else > echo Pode passar a mo > Cuidado

Tabela 4
Operador Parnteses () Exclamao ! -a -o Finalidade 0 0 0 0

E vamos execut-lo novamente:


As variveis so iguais.

Como voc viu, nas duas execues obtive resultados diferentes, porque a

Obviamente a operao resultou em 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 avaliado primeiro foi a expresso:
$Familia = canideo -a $Genero = U ona

Da mesma forma, para escolhermos CDs que tenham a participao do Artista1 e do Artista2, no necessrio montar um if com o conector -o. O egrep tambm resolve isso para ns. Veja como:
$ egrep (Artista1|Artista2) U musicas

Que foi avaliada como falsa, retornando o seguinte:


$Familia = felino -o FALSO -o U $Genero = lobo

mente a legibilidade, pois o comando if ir car com a sintaxe semelhante das outras linguagens; por isso, esse ser o modo como o comando test ser usado daqui para a frente. Se voc pensa que acabou, est muito enganado. Preste ateno Tabela Verdade na Tabela 5.

Ou (nesse caso especco) o prprio grep poderia nos quebrar o galho:


$grep Artista[12] musicas

Tabela 5 - Tabela Verdade


Combinao VERDADEIRO-VERDADEIRO VERDADEIRO-FALSO FALSO-VERDADEIRO FALSO-FALSO E TRUE OU FALSE FALSE FALSE TRUE TRUE TRUE FALSE

Que resolvida resulta em:


VERDADEIRO -o FALSO -o FALSO

Como agora todos os conectores so -o, e para que uma srie de expresses conectadas entre si por diversos ou lgicos seja verdadeira, basta que uma delas o seja. A expresso nal resultou como VERDADEIRO e o then foi executado de forma errada. Para que isso volte a funcionar faamos o seguinte:
$ if test \($Familia = felino U -o $Familia = canideo\) -a U \($Genero = ona -o $Genero = U lobo\) > then > echo Cuidado > else > echo Pode passar a mo > Pode passar a mo

No egrep acima, foi usada uma expresso regular, na qual a barra vertical (|) trabalha como um ou lgico e os parnteses so usados para limitar a amplitude deste ou. J no grep da linha seguinte, a palavra Artista deve ser seguida por um dos valores da lista formada pelos colchetes ([]), isto , 1 ou 2. T legal, eu aceito o argumento, o if do shell muito mais poderoso que os outros caretas - mas, c entre ns, essa construo de if test ... muito esquisita, pouco legvel. , voc tem razo, eu tambm no gosto disso e acho que ningum gosta. Acho que foi por isso que o shell incorporou outra sintaxe, que substitui o comando test. Para isso vamos pegar aquele exemplo para fazer uma troca de diretrios, que era assim:
if test ! -d lmb then mkdir lmb cd lmb

Desta forma, com o uso dos parnteses agrupamos as expresses com o conector -o, priorizando a execuo e resultando em VERDADEIRO -a FALSO. Para que seja VERDADEIRO o resultado de duas expresses ligadas pelo conector -a, necessrio que ambas sejam verdadeiras, o que no o caso do exemplo acima. Assim, o resultado nal foi FALSO, sendo ento o else corretamente executado. Se quisermos escolher um CD que tenha faixas de 2 artistas diferentes, nos sentimos tentados a usar um if com o conector -a, mas sempre bom lembrar que o bash nos oferece muitos recursos e isso poderia ser feito de forma muito mais simples com um nico comando grep, da seguinte forma:
$ grep Artista1 musicas | grep U Artista2

Ou seja, quando o conector e e a primeira condio verdadeira, o resultado nal pode ser verdadeiro ou falso, dependendo da segunda condio; j no conector ou, caso a primeira condio seja verdadeira, o resultado sempre ser verdadeiro. Se a primeira for falsa, o resultado depender da segunda condio. Ora, os caras que desenvolveram o interpretador no so bobos e esto sempre tentando otimizar ao mximo os algoritmos. Portanto, no caso do conector e, a segunda condio no ser avaliada, caso a primeira seja falsa, j que o resultado ser sempre falso. J com o ou, a segunda ser executada somente caso a primeira seja falsa. Aproveitando-se disso, uma forma abreviada de fazer testes foi criada. O conector e foi batizado de && e o ou de ||. Para ver como isso funciona, vamos us-los como teste no nosso velho exemplo de troca de diretrio, que em sua ltima verso estava assim:
if [ ! -d lmb ] then mkdir lmb cd lmb

e utilizando a nova sintaxe, vamos fazlo assim:


if [ ! -d lmb ] then mkdir lmb cd lmb

O cdigo acima tambm poderia ser escrito de maneira abreviada:


[ ! -d lmb ] && mkdir lmb cd dir

Ou seja, o comando test pode ser substitudo por um par de colchetes ([]), separados por espaos em branco dos argumentos, o que aumentar enorme-

Tambm podemos retirar a negao (!):


[ -d lmb ] || mkdir lmb cd dir

86
edio 04
www.linuxmagazine.com.br


Papo de Botequim LINUX USER

Tabela 6
Caractere * Signicado Qualquer caractere ocorrendo zero ou mais vezes Lista de caracteres ou lgico

? |

[...]

Qualquer caractere ocorrendo uma vez

diretrio para dentro dele. Para executar mais de um comando dessa forma, necessrio fazer um grupamento de comandos, o que se consegue com o uso de chaves ({}). Veja como seria o modo correto:
cd lmb || { mkdir lmb cd lmb }

No primeiro caso, se o primeiro comando (o test, que est representado pelos colchetes) for bem sucedido, isto , se o diretrio lmb no existir, o comando mkdir ser executado porque a primeira condio era verdadeira e o conector era e. No exemplo seguinte, testamos se o diretrio lmb existia (no anterior testamos se ele no existia) e, caso isso fosse verdade, o mkdir no seria executado porque o conector era ou. Outra forma de escrever o programa:
cd lmb || mkdir lmb

sempre estar dentro de lmb, desde que tenha permisso para entrar neste diretrio, permisso para criar um subdiretrio dentro de ../lmb, que haja espao em disco suciente... Vejamos um exemplo didtico: dependendo do valor da varivel $opc o script dever executar uma das opes a seguir: incluso, excluso, alterao ou encerrar sua execuo. Veja como caria o cdigo:
if [ $opc -eq 1 ] then inclusao elif [ $opc -eq 2 ] then exclusao elif [ $opc -eq 3 ] then alteracao elif [ $opc -eq 4 ] then exit else echo Digite uma opo entre U 1 e 4

Ainda no est legal porque, caso o diretrio no exista, o cd exibir uma mensagem de erro. Veja o modo certo:
cd lmb 2> /dev/null || { mkdir lmb cd lmb }

Nesse caso, se o comando cd fosse mal sucedido, o diretrio lmb seria criado mas no seria feita a mudana de

Como voc viu, o comando if nos permitiu fazer um cd seguro de diversas maneiras. sempre bom lembrar que o seguro a que me re ro diz respeito ao fato de que ao nal da execuo voc

www.linuxmagazine.com.br

edio 04

87


LINUX USER Papo de Botequim

Quadro 1 - Script bem-educado


#!/bin/bash # Programa bem educado que # d bom-dia, boa-tarde ou # boa-noite conforme a hora Hora=$(date +%H) case $Hora in 0? | 1[01]) echo Bom Dia ;; 1[2-7] ) echo Boa Tarde ;; * ) echo Boa Noite ;; esac exit

case $opc in 1) inclusao ;; 2) exclusao ;; 3) alteracao ;; 4) exit ;; *) echo Digite uma opo U entre 1 e 4 esac

Neste exemplo voc viu o uso do comando elif como um substituto ou forma mais curta de else if. Essa uma sintaxe vlida e aceita, mas poderamos fazer ainda melhor. Para isso usamos o comando case, cuja sintaxe mostramos a seguir:
case $var in padrao1) cmd1 cmd2 cmdn ;; padrao2) cmd1 cmd2 cmdn ;; padraon) cmd1 cmd2 cmdn ;; esac

Como voc deve ter percebido, eu usei o asterisco como ltima opo, isto , se o asterisco atende a qualquer coisa, ento servir para qualquer coisa que no esteja no intervalo de 1 a 4. Outra coisa a ser notada que o duplo ponto-evrgula no necessrio antes do esac. Vamos agora fazer um script mais radical. Ele te dar bom dia, boa tarde ou boa noite dependendo da hora em que for executado, mas primeiramente veja estes comandos:
$ date Tue Nov 9 19:37:30 BRST 2004 $ date +%H 19

O comando date informa a data completa do sistema e tem diversas opes de mascaramento do resultado. Neste comando, a formatao comea com um sinal de mais (+) e os caracteres de formatao vm aps um sinal de percentagem (%), assim o %H signica a hora do sistema. Dito isso, veja o exemplo no Quadro 1. Peguei pesado, n? Que nada, vamos esmiuar a resoluo:

coisa (?), ou (|) um seguido de zero ou um ([01]), ou seja, esta linha "casa" com 01, 02, ... 09, 10 e 11; 1 [2-7] Signica um seguido da lista de caracteres entre dois e sete, ou seja, esta linha pega 12, 13, ... 17; * Signica tudo o que no casou com nenhum dos padres anteriores. Cara, at agora eu falei muito e bebi pouco. Agora eu vou te passar um exerccio para voc fazer em casa e me dar a resposta da prxima vez em que nos encontrarmos aqui no botequim, t legal? Beleza! o seguinte: faa um programa que receba como parmetro o nome de um arquivo e que quando executado salve esse arquivo com o nome original seguido de um til (~) e abra esse arquivo dentro do vi para ser editado. Isso para ter sempre uma cpia de backup do arquivo caso algum faa alteraes indevidas. Obviamente, voc far as crticas necessrias, como vericar se foi passado um parmetro, se o arquivo indicado existe... En m, o que te der na telha e voc achar que deva constar do script. Deu pra entender? Hum, hum... Chico, traz mais um, sem colarinho!
Julio Cezar Neves Analista de Suporte de Sistemas desde 1969 e trabalha com Unix desde 1980, quando participou do desenvolvimento do SOX, um sistema operacional similar ao Unix produzido pela Cobra computadores. Pode ser contatado no e-mail julio. neves@gmail.com

0? | 1 [01] Zero seguido de qualquer

Onde a varivel $var comparada aos padres padrao1, ..., padraon. Caso um dos padres corresponda varivel, o bloco de comandos cmd1, ..., cmdn correspondente executado at encontrar um duplo ponto-e-vrgula (;;), quando o uxo do programa ser interrompido e desviado para instruo imediatamente aps o comando esac (que, caso no tenham notado, case ao contrrio. Ele indica o m do bloco de cdigo, da mesma forma que ot comando indica o m de um if). Na formao dos padres, so aceitos os caracteres mostrados na Tabela 6. Para mostrar como o cdigo fica melhor, vamos repetir o exemplo anterior, s que desta vez usaremos o case em vez do tradicional bloco de cdigo com if ... elif ... else ... .

88
edio 04
www.linuxmagazine.com.br

SOBRE O AUTOR


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

ala cara! E as idias esto em At agora j vimos alguns blocos de ordem? J fundiu a cuca ou voc cdigo, como quando te mostrei um ainda agenta mais Shell? exemplo para fazer um cd para dentro Gento! T gostando muito! Gostei de um diretrio: tanto que at caprichei no exerccio que voc passou. Lembra que voc cd lmb 2> /dev/null || me pediu para fazer um programa { que recebe como parmetro o nome mkdir lmb de um arquivo e que quando execucd lmb tado salva esse arquivo com o nome } original seguido de um til (~) e o abre dentro do vi? O fragmento contido entre as duas Claro que lembro, me mostre e expli- chaves ({}) forma um bloco de cdigo. que como voc fez. Tambm nesse exerccio que acabamos 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 que voc terminou o programa com cdigo compreendidos entre os comanum exit 0? dos then e do if. Um bloco de cdigo Eu descobri que o nmero aps o tambm pode estar dentro de um case exit indica o cdigo de retorno do ou entre um do e um done. programa (o $?, lembra?) e assim, Pera, Julio, que do e done so esses? como a execuo foi bem sucedida, No me lembro de voc ter falado ele encerra com o $?=0. Porm, se nisso, e olha que estou prestando voc observar, ver que caso o promuita ateno... grama no tenha recebido o nome do Pois , ainda no tinha falado porque arquivo ou caso o operador no tenha no havia chegado a hora certa. permisso de gravao nesse arquivo, Todas as instrues de loop ou lao o cdigo de retorno ($?) seria dife- executam os comandos do bloco comrente do zero. preendidos entre um do e um done. As Grande garoto, aprendeu legal, mas instrues de loop ou lao so for, while 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. zem igualmente um cdigo de retorno O comando For ($?) igual a zero. Agora vamos falar sobre as instrues de loop ou lao, Se voc est habituado a programar, mas antes vou passar o conceito de certamente j conhece o comando for, bloco de cdigo. mas o que voc no sabe que o for,

Quadro 1: vira.sh
$ cat vira.sh #!/bin/bash # # vira - vi resguardando # arquivo anterior # Verica se algum parmetro foi # passado if [ $# -ne 1 ] then echo Erro -> Uso: $0 U <arquivo> exit 1 Arq=$1 # Caso o arquivo no exista, no # h cpia a ser salva if [ ! -f $Arq ] then vi $Arq exit 0 # Se eu no puder alterar o #arquivo, vou usar o vi para que? if [ ! -w $Arq ] then echo Voc no tem permisso U de escrita em $Arq exit 2 # J que est tudo OK, vou # salvar a cpia e chamar o vi cp -f $Arq $Arq~ vi $Arq exit 0

www.linuxmagazine.com.br

edio 05

89


LINUX USER Papo de Botequim
que uma instruo intrnseca do Shell (isso signica que o cdigo fonte do comando faz parte do cdigo fonte do Shell, ou seja, em bom programs um built-in), muito mais poderoso que os seus correlatos das outras linguagens. Vamos entender a sua sintaxe, primeiro em portugus e, depois, como funciona pra valer. Olhe s: Ento vamos execut-lo:
$ testefor1 ArqDoDOS.txt1:confuso:incusu: logado:musexc:musicas:musinc: muslist:$ $ echo $IFS | od -h 0000000 0920 0a0a 0000004

Como voc viu, o Shell transformou o asterisco (que odeia ser chamado de asterstico) em uma lista de arquipara var em val1 val2 ... valn vos separados por espaos em branco. faa Quando o for viu aquela lista, disse: cmd1 Opa, listas separadas por espaos cmd2 comigo mesmo! cmdn O bloco de comandos a ser executado feito era somente o echo, que com a opo -n listou a varivel $Arq seguida de doisOnde 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 para cada um desses valores, executa o prompt, que permaneceu na mesma bloco de comandos formado por cmd1, linha tambm em funo da opo -n. cmd2 e cmdn. Agora que j vimos o Outro exemplo simples (por enquanto): signicado da instruo em portugus, vejamos a sintaxe correta: $ cat testefor2
for var in val1 val2 ... valn do cmd1 cmd2 cmdn done #!/bin/bash # 2o. Programa didtico para # entender o for for Palavra in Linux Magazine U do Brasil do echo $Palavra done

Isto , mandei a varivel (protegida da interpretao do Shell pelas aspas) para um dump hexadecimal (od -h). O resultado pode ser interpretado com a tabela abaixo:

Tabela 1: Resultado do od -h
Valor Hexadecimal 09 20 0a Signicado <TAB> <ESPAO> <ENTER>

O ltimo 0a foi proveniente do <ENTER> dado ao nal do comando. Para melhorar a explicao, vamos ver isso de outra forma:
$ echo :$IFS: | cat -vet : ^I$ :$

Vamos aos exemplos, para entender direito o funcionamento deste comando. Vamos escrever um script para listar todos os arquivos do diretrio, separados por dois-pontos, mas antes veja isso:
$ echo * ArqDoDOS.txt1 confuso incusu logado musexc musicas musinc muslist

E executando temos:
$ testefor2 Linux Magazine do Brasil

Isto , o Shell viu o asterisco (*), expandiu-o com o nome de todos os arquivos do diretrio e o comando echo jogou-os para a tela separados por espaos em branco. Visto isso, vamos resolver o problema a que nos propusemos:
$ cat testefor1 #!/bin/bash # 1o. Programa didtico para # entender o for for Arq in * do echo -n $Arq: done

Como voc viu, esse exemplo to bobo e simples como o anterior, mas serve para mostrar o comportamento bsico do for. Veja s a fora do comando: ainda estamos na primeira possibilidade de sintaxe e j estou mostrando novas formas de us-lo. L atrs eu havia falado que o for usava listas separadas por espaos em branco, mas isso uma meia-verdade, s para facilitar a compreenso. Na verdade, as listas no so obrigatoriamente separadas por espaos. Mas antes de prosseguir, preciso te mostrar como se comporta uma varivel do sistema chamada de IFS, ou Inter Field Separator Veja no exemplo a seguir seu contedo:

No comando cat, a opo -e representa o <ENTER> como um cifro ($) e a opo -t representa o <TAB> como um ^I. Usei os dois-pontos (:) para mostrar o incio e o m do echo. E dessa forma, pudemos notar que os trs caracteres esto presentes naquela varivel. Agora veja voc: traduzindo, IFS signica separador entre campos. Uma vez entendido isso, eu posso a rmar que o comando for no usa apenas listas separadas por espaos em branco, mas sim pelo contedo da varivel $IFS, cujo valor padro so os caracteres que acabamos de ver. Para comprovarmos isso, vamos continuar mexendo em nossa CDTeca, escrevendo um script que recebe o nome do artista como parmetro e lista as msicas que ele toca. Mas primeiramente vamos ver como est o nosso arquivo musicas:
$ cat musicas album 1^Artista1~Musica1: U Artista2~Musica2 album 2^Artista3~Musica3: U Artista4~Musica4 album 3^Artista5~Musica5: U Artista6~Musica6 album 4^Artista7~Musica7: U Artista1~Musica3

90
edio 05
www.linuxmagazine.com.br


Papo de Botequim LINUX USER
album 5^Artista9~Musica9: U Artista10~Musica10

Em cima desse leiaute desenvolvemos o script a seguir:


$ cat listartista #!/bin/bash # Dado um artista, mostra as # suas msicas if [ $# -ne 1 ] then echo Voc deveria ter U passado um parmetro exit 1 IFS= : for ArtMus in $(cut -f2 -d^ U musicas) do echo $ArtMus | grep $1 && U echo $ArtMus | cut -f2 -d~ done

o $1 seria Perereca e o resto desse lindo nome seria ignorado na pesquisa. Para que isso no ocorra, eu deveria passar o nome do artista entre aspas () ou trocar $1 por $* (que representa todos os parmetros passados), que a melhor soluo, mas nesse caso eu teria que modicar a crtica dos parmetros e o grep. A nova verso no seria se eu passei um parmetro, mas sim se passei pelo menos um parmetro. Quanto ao grep, veja s o que aconteceria aps a substituio do $* pelos parmetros:
echo $ArtMus | grep perereca U & peteleca

$ listartista Artista1 Musica1 Musica3

Veja uma segunda sintaxe para o for:


for var do cmd1 cmd2 cmdn done

Isso gera um erro. O correto :


echo $ArtMus | grep -i U perereca & peteleca

U, sem o in, como ele vai saber que valor assumir? Pois , n? Esta construo, primeira vista, parece esquisita, mas bastante simples. Neste caso, var assumir um a um cada parmetro passado para o programa. Como exemplo para entender melhor, vamos fazer um script que receba como parmetro um monte de msicas e liste seus autores:
$ cat listamusica #!/bin/bash # Recebe parte dos nomes de # msicas como parmetro e # lista os intrpretes. Se o # nome for composto, deve # ser passado entre aspas. # ex. Eu no sou cachorro no # Churrasquinho de Me # if [ $# -eq 0 ] then echo Uso: $0 musica1 U [musica2] ... [musican] exit 1 IFS= : for Musica do echo $Musica Str=$(grep -i $Musica U musicas) || { echo No U encontrada continue } for ArtMus in $(echo $Str U | cut -f2 -d^) do echo $ArtMus | U grep -i $Musica | cut -f1 -d~ done done

O script, como sempre, comea testando se os parmetros foram passados corretamente, em seguida o IFS foi congurado para <ENTER> e dois-pontos (:) (como demonstram as aspas em linhas diferentes), porque ele quem separa os blocos Artistan~Musicam. Desta forma, a varivel $ArtMus ir receber cada um desses blocos do arquivo (repare que o for j recebe os registros sem o lbum em virtude do cut na sua linha). Caso encontre o parmetro ($1) no bloco, o segundo cut listar somente o nome da msica. Vamos executar o programa:
$ listartista Artista1 Artista1~Musica1 Musica1 Artista1~Musica3 Musica3 Artista10~Musica10 Musica10

Aqui adicionamos a opo -i para que a pesquisa ignorasse maisculas e minsculas. As aspas foram inseridas para que o nome do artista fosse visto como uma s cadeia de caracteres. Falta consertar o erro dele ter listado o Artista10. O melhor dizer ao grep que a cadeia de caracteres est no incio (^) de $ArtMus e que logo aps vem um til (~). preciso redirecionar a sada do grep para / dev/null para que os blocos no sejam listados. Veja a nova cara do programa:
$ cat listartista #!/bin/bash # Dado um artista, mostra as # suas musicas # Versao 2 if [ $# -eq 0 ] then echo Voce deveria ter U passado pelo menos um parametro exit 1 IFS= : for ArtMus in $(cut -f2 -d^ U musicas) do echo $ArtMus | grep -i U ^$*~ > /dev/null && echo U $ArtMus | cut -f2 -d~ done

pa! Aconteceram duas coisas indesejveis: os blocos tambm foram listados, e a Musica10 idem. Alm do mais, o nosso arquivo de msicas est muito simples: na vida real, tanto a msica quanto o artista tm mais de um nome. Suponha que o artista fosse uma dupla sertaneja chamada Perereca & Peteleca (no gosto nem de dar a idia com receio que isso se torne realidade). Nesse caso,

O resultado :

www.linuxmagazine.com.br

edio 05

91


LINUX USER Papo de Botequim
Da mesma forma que os outros, comeamos o exerccio com uma crtica sobre os parmetros recebidos, em seguida fizemos um for em que a varivel $Musica receber cada um dos parmetros passados, colocando em $Str todos os lbuns que contm as msicas desejadas. Em seguida, o outro for pega cada bloco Artista~Musica nos registros que esto em $Str e lista cada artista que toca aquela msica. Vamos executar o programa para ver se funciona mesmo:
$ listamusica musica3 Musica4 U Eginha Pocot musica3 Artista3 Artista1 Musica4 Artista4 Eginha Pocot No encontrada

Ou na forma mais completa do seq:


for i in $(seq 0 3 9) do echo -n $i done 0 3 6 9

Repare que o incremento saiu do corpo do for e passou para o bloco de cdigo; repare tambm que, quando usei o let, no foi necessrio inicializar a varivel $i. Veja s os comandos a seguir, digitados diretamente no prompt, para demonstrar o que acabo de falar:
$ echo $j $ let j++ $ echo $j 1

A outra forma de fazer isso com uma sintaxe muito semelhante ao for da linguagem C, como vemos a seguir:
for ((var=ini; cond; incr)) do cmd1 cmd2 cmdn done

Ou seja, a varivel $j sequer existia e no primeiro let assumiu o valor 0 (zero) para, aps o incremento, ter o valor 1. Veja s como as coisas cam simples:
for arq in * do let i++ echo $i -> $Arq done 1 -> ArqDoDOS.txt1 2 -> confuso 3 -> incusu 4 -> listamusica 5 -> listartista 6 -> logado 7 -> musexc 8 -> musicas 9 -> musinc 10 -> muslist 11 -> testefor1 12 -> testefor2

A listagem cou feinha porque ainda no sabemos formatar a sada; mas qualquer dia desses, quando voc souber posicionar o cursor, trabalhar com cores etc., faremos esse programa novamente usando todas essas perfumarias. A esta altura dos acontecimentos, voc deve estar se perguntando: E aquele for tradicional das outras linguagens em que ele sai contando a partir de um nmero, com um determinado incremento, at alcanar uma condio?. E a que eu te respondo: Eu no te disse que o nosso for mais porreta que o dos outros? Para fazer isso, existem duas formas. Com a primeira sintaxe que vimos, como no exemplo:
for i in $(seq 9) do echo -n $i done 1 2 3 4 5 6 7 8 9

Onde var=ini signica que a varivel var comear de um valor inicial ini; cond signica que o loop ou lao for ser executado enquanto var no atingir a condio cond e incr signica o incremento que a varivel var sofrer a cada passada do loop. Vamos aos exemplos:
for ((i=1; i<=9; i++)) do echo -n $i done 1 2 3 4 5 6 7 8 9

for i in $(seq 4 9) do echo -n $i done 4 5 6 7 8 9

for ((; i<=9;)) do let i++ echo -n $i done 1 2 3 4 5 6 7 8 9

92
edio 05
www.linuxmagazine.com.br

SOBRE O AUTOR

A varivel i assumiu os valores inteiros entre 1 a 9 gerados pelo comando seq e a opo -n do echo foi usada para no saltar uma linha a cada nmero listado. Ainda usando o for com seq:

A varivel i partiu do valor inicial 1, o bloco de cdigo (aqui somente o echo) ser executado enquanto i for menor ou igual (<=) a 9 e o incremento de i ser de 1 a cada passada do loop. Repare que no for propriamente dito (e no no bloco de cdigo) no coloquei um cifro ($) antes do i e a notao para incrementar (i++) diferente do que vimos at agora. O uso de parnteses duplos (assim como o comando let) chama o interpretador aritmtico do Shell, que mais tolerante. S para mostrar como o let funciona e a versatilidade do for, vamos fazer a mesma coisa, mas omitindo a ltima parte do escopo do for, passando-a para o bloco de cdigo:

Pois amigo, tenho certeza que voc j tomou um xarope do comando for. Por hoje chega, na prxima vez em que nos encontrarmos falaremos sobre outras instrues de loop, mas eu gostaria que at l voc zesse um pequeno script para contar a quantidade de palavras de um arquivo texto, cujo nome seria recebido como parmetro. Essa contagem tem que ser feita com o comando for, para se habituar ao seu uso. No vale usar o wc -w. A Chico! Traz a saideira!
Julio Cezar Neves Analista de Suporte de Sistemas desde 1969 e trabalha com Unix desde 1980, quando participou do desenvolvimento do SOX, um sistema operacional similar ao Unix produzido pela Cobra Computadores. Pode ser contatado no e-mail julio.neves@gmail.com


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

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? Claro! T empolgado com essa linguagem! Eu z da forma que voc pediu, olha s... pa! Pera que eu t sequinho pra tomar um chope. A Chico, traz dois por favor. Um sem colarinho! Como eu ia dizendo, olha como eu z. muito fcil...
$ cat contpal.sh #!/bin/bash # Script meramente pedaggico # cuja funo contar a # quantidade de palavras de # um arquivo. Supe-se que as # palavras esto separadas # entre si por espaos, <TAB> # ou <ENTER>. if [ $# -ne 1 ] then echo uso: $0 /caminho/do/ U arquivo exit 2 Cont=0 for Palavra in $(cat $1) do Cont=$((Cont+1)) done echo O arquivo $1 tem $Cont U palavras.

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 padro branco, TAB e ENTER, que exatamente o que desejamos para separar as palavras), incrementando a varivel $Cont. Vamos relembrar como o arquivo ArqDoDOS.txt.
$ cat ArqDoDOS.txt Este arquivo foi gerado pelo DOS/Rwin e foi baixado por um ftp mal feito.

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 forma $((expresso)) ou let expresso. No ltimo loop for usei a expanso aritmtica das duas formas, mas no podemos seguir adiante sem saber que a expresso pode ser uma das listadas na tabela 1. Mas voc pensa que o papo de loop (ou lao) se encerra no comando for? Ledo engano, amigo, vamos a partir de agora ver mais dois comandos.

Agora vamos testar o programa passando esse arquivo como parmetro:


$ contpal.sh ArqDoDOS.txt O arquivo ArqDoDOS.txt tem 14 palavras.

Todos os programadores conhecem este comando, porque comum a todas as linguagens. Nelas, o que normalmente ocorre que um bloco de comandos executado, enquanto (enquanto, em ingls, while) uma determinada condio for verdadeira.

O comando while

Funcionou legal! Se voc se lembra, em nossa ltima aula mostramos o loop for a seguir:
for ((; i<=9;)) do let i++ echo -n "$i " done

Tabela 1: Expresses no Shell


++id --id id++ id-Expresso pr-incremento e pr-decremento de variveis ps-incremento e ps-decremento de variveis Resultado

||

&&

== !=

<= >= < >

+-

*/%

**

OU lgico

E lgico

igualdade, desigualdade

comparao

adio, subtrao

multiplicao, diviso, resto da diviso (mdulo)

exponenciao

86
edio 06
www.linuxmagazine.com.br


Papo de Botequim LINUX USER
Pois bem, isso o que acontece nas linguagens caretas! Em programao Shell, o bloco de comandos executado enquanto um comando for verdadeiro. E claro, se quiser testar uma condio, use o comando while junto com o comando test, exatamente como voc aprendeu a fazer no if, lembra? Ento a sintaxe do comando ca assim:
while comando do cmd1 cmd2 ... cmdn done $ logaute.sh xefe pts/0 (10.2.4.144) xefe pts/0 (10.2.4.144) ... xefe pts/0 (10.2.4.144) Jan Jan 4 08:46 U 4 08:46 U $cat monbg.sh #!/bin/bash # Executa e monitora um # processo em background $1 & # Coloca em backgroud while ps | grep -q $! do sleep 5 done echo Fim do Processo $1

Jan

4 08:46 U

Isto , a cada 30 segundos a sada do comando grep seria enviada para a tela, o que no legal, j que poluiria a tela do meu micro e a mensagem to esperada poderia passar despercebida. Para evitar isso, j sabemos que a sada do pipeline tem que ser redirecionada para o dispositivo /dev/null.
$ cat logaute.sh #!/bin/bash # Espero que a Xuxa no tenha # copyright de xefe e xato :) while who | grep xefe > /dev/null do sleep 30 done echo O xato se mandou, no U hesite, d exit e v a luta

e dessa forma, o bloco formado pelas instrues cmd1, cmd2,... e cmdn executado enquanto a execuo da instruo comando for bem sucedida. Suponha a seguinte cena: tinha uma tremenda gata me esperando e eu estava preso no trabalho sem poder sair porque o meu chefe, que um p no saco (alis chefe-chato uma redundncia, n?), ainda estava na sala dele, que ca bem na minha passagem para a rua. Ele comeou a car cabreiro depois da quinta vez que passei pela sua porta e olhei para ver se j havia ido embora. Ento voltei para a minha mesa e z, no servidor, um script assim:
$ cat logaute.sh #!/bin/bash # Espero que a Xuxa no tenha # copyright de xefe e xato :) while who | grep xefe do sleep 30 done echo O xato se mandou, no U hesite, d exit e v luta

Agora quero montar um script que receba o nome (e eventuais parmetros) de um programa que ser executado em background e que me informe do seu trmino. Mas, para voc entender este exemplo, primeiro tenho de mostrar uma nova varivel do sistema. Veja estes comandos executados diretamente no prompt:
$ sleep 10& [1] 16317 $ echo $! 16317 [1]+ Done $ echo $! 16317

Esse script bastante similar ao anterior, mas tem uns macetes a mais, veja s: ele tem que ser executado em background para no prender o prompt mas o $! ser o do programa passado como parmetro, j que ele foi colocado em background aps o monbg.sh propriamente dito. Repare tambm na opo -q (quiet) do grep, que serve para faz-lo trabalhar em silncio. O mesmo resultado poderia ser obtido com a linha: while ps | grep $! > /dev/null, como nos exemplos que vimos at agora. Vamos melhorar o nosso velho musinc, nosso programa para incluir registros no arquivo musicas, mas antes preciso te ensinar a pegar um dado da tela, e j vou avisando: s vou dar uma pequena dica do comando read (que quem pega o dado da tela), que seja o suciente para resolver este nosso problema. Em uma outra rodada de chope vou te ensinar tudo sobre o assunto, inclusive como formatar tela, mas hoje estamos falando sobre loops. A sintaxe do comando read que nos interessa por hoje a seguinte:
$ read -p "prompt de leitura" var

sleep 10

Neste scriptzinho, o comando while testa o pipeline composto pelos comandos who e grep, que ser verdadeiro enquanto o grep localizar a palavra xefe na sada do comando who. Desta forma, o script dormir por 30 segundos enquanto o chefe estiver logado (Argh!). Assim que ele se desconectar do servidor, o uxo do script sair do loop e te mostrar a to ansiada mensagem de liberdade. Mas quando executei o script, adivinha o que aconteceu?

Onde prompt de leitura o texto que voc quer que aparea escrito na tela. Quando o operador teclar tal dado, ele ser armazenado na varivel var. Por exemplo:
$ read -p "Ttulo do lbum: " Tit

Isto , criei um processo em background que dorme por 10 segundos, somente para mostrar que a varivel $! guarda o PID (Process ID) do ltimo processo em background. Mas observe a listagem e repare, aps a linha do Done, que a varivel reteve o valor mesmo aps o trmino desse processo. Bem, sabendo isso, j ca mais fcil monitorar qualquer processo em background. Veja s como:

Bem, uma vez entendido isso, vamos especificao do nosso problema: faremos um programa que inicialmente ler o nome do lbum e em seguida far um loop de leitura, pegando o nome da msica e o artista. Esse loop termina quando for informada uma msica com nome vazio, isto , quando o operador

www.linuxmagazine.com.br

edio 06

87


LINUX USER Papo de Botequim
Leitura de arquivo signica ler um a um todos os registros, o que sempre uma operao lenta. Fique atento para no usar o while quando for desnecessrio. O Shell tem ferramentas como o sed e a famlia grep, que vasculham arquivos de forma otimizada sem que seja necessrio o uso do while para faz-lo registro a registro.

Dica

until comando do cmd1 cmd2 ... cmdn done

$cat chegada.sh #!/bin/bash until who | grep julio do sleep 30 done echo $(date "+ Em %d/%m s U %H:%Mh") > relapso.log

der um simples <ENTER>. Para facilitar a vida do operador, vamos oferecer como default o mesmo nome do artista da msica anterior (j que normal que o lbum seja todo do mesmo artista) at que ele deseje alter-lo. Veja na listagem 1 como cou o programa. Nosso exemplo comea com a leitura do ttulo do lbum. Caso ele no seja informado, terminamos a execuo do programa. Em seguida um grep procura, no incio (^) de cada registro de msicas, o ttulo informado seguido do separador (^) (que est precedido de uma contrabarra [\] para proteg-lo da interpretao do Shell). Para ler os nomes dos artistas e as msicas do lbum, foi montado um loop while simples, cujo nico destaque o fato de ele armazenar o nome do intrprete da msica anterior na varivel $oArt, que s ter o seu contedo alterado quando algum dado for informado para a varivel $Art, isto , quando no for teclado um simples ENTER para manter o artista anterior. O que foi visto at agora sobre o while foi muito pouco. Esse comando muito utilizado, principalmente para leitura de arquivos, porm ainda nos falta bagagem para prosseguir. Depois que aprendermos mais sobre isso, veremos essa instruo mais a fundo.

e dessa forma o bloco de comandos formado pelas instrues cmd1, cmd2,... e cmdn executado at que a execuo da instruo comando seja bem sucedida. Como eu te disse, while e until funcionam de forma antagnica, e isso muito fcil de demonstrar: em uma guerra, sempre que se inventa uma arma, o inimigo busca uma soluo para neutraliz-la. Foi baseado nesse principio belicoso que meu chefe desenvolveu, no mesmo servidor em que eu executava o logaute.sh, um script para controlar o meu horrio de chegada. Um dia tivemos um problema na rede. Ele me pediu para dar uma olhada no micro dele e me deixou sozinho na sala. Resolvi bisbilhotar os arquivos guerra guerra e veja s o que descobri:

Olha que safado! O cara estava montando um log com os meus horrios de chegada, e ainda por cima chamou o arquivo de relapso.log! O que ser que ele quis dizer com isso? Nesse script, o pipeline who | grep julio, ser bem sucedido somente quando julio for encontrado na sada do comando who, isto , quando eu me logar no servidor. At que isso acontea, o comando sleep, que forma o bloco de instrues do until, colocar o programa em espera por 30 segundos. Quando esse loop encerrar-se, ser enviada uma mensagem para o arquivo relapso.log. Supondo que no dia 20/01 eu me loguei s 11:23 horas, a mensagem seria a seguinte:

Listagem 1
$ cat musinc.sh #!/bin/bash # Cadastra CDs (versao 4) # clear read -p "Ttulo do lbum: " Tit [ "$Tit" ] || exit 1 # Fim da execuo se ttulo vazio if grep "^$Tit\^" musicas > /dev/null then echo "Este lbum j est cadastrado" exit 1 Reg="$Tit^" Cont=1 oArt= while true do echo "Dados da trilha $Cont:" read -p "Msica: " Mus [ "$Mus" ] || break # Sai se vazio read -p "Artista: $oArt // " Art [ "$Art" ] && oArt="$Art" # Se vazio Art anterior Reg="$Reg$oArt~$Mus:" # Montando registro Cont=$((Cont + 1)) # A linha anterior tb poderia ser ((Cont++)) done echo "$Reg" >> musicas sort musicas -o musicas

Este comando funciona de forma idntica ao while, porm ao contrrio. Disse tudo mas no disse nada, n? o seguinte: ambos testam comandos; ambos possuem a mesma sintaxe e ambos atuam em loop; porm, o while executa o bloco de instrues do loop enquanto um comando for bem sucedido; j o until executa o bloco do loop at que o comando seja bem sucedido. Parece pouca coisa, mas a diferena fundamental. A sintaxe do comando praticamente a mesma do while. Veja:

O comando until

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 vamos cadastrar msicas seria ideal que pudssemos cadastrar diversos CDs de uma vez s. Na ltima verso do programa isso no ocorre: a cada CD cadastrado o programa termina. Veja na listagem 2 como melhor-lo. Nesta verso, um loop maior foi adicionado antes da leitura do ttulo, que s terminar quando a varivel $Para deixar de ser vazia. Caso o ttulo do lbum no seja informado, a varivel $Para receber um valor (coloquei 1, mas poderia ter colocado qualquer coisa) para sair desse loop, terminando o programa. No resto, o script idntico verso anterior.

$ cat musinc.sh #!/bin/bash # Cadastra CDs (versao 5) # Para= until [ "$Para" ] do clear read -p "Ttulo do lbum: " Tit if [ ! "$Tit" ] # Se titulo vazio... then Para=1 # Liguei ag de sada else if grep "^$Tit\^" musicas > /dev/null then echo "Este lbum j est cadastrado" exit 1 Reg="$Tit^" Cont=1 oArt=

Nem sempre um ciclo de programa, compreendido entre um do e um done, sai pela porta da frente. Em algu mas oportunidades, temos que colocar um comando que aborte de forma c ont rolada esse loop. De maneira Figura 1: A estrutura dos comandos break e continue, usados para controinversa, algumas lar o uxo de execuo em loops. vezes desejamos que o f lu xo de pectivamente os comandos break (que execuo do programa volte antes de j vimos rapidamente nos exemplos do chegar ao done. Para isso, temos res- comando while) e continue, que funcionam da forma mostrada na gura 1. O que eu no havia dito anteriorListagem 2 mente que nas suas sintaxes genricas eles aparecem da seguinte forma:
break [qtd loop]

e tambm:
continue [qtd loop]

while [ "$Tit" ] do echo Dados da trilha $Cont: read -p "Msica: " Mus [ "$Mus" ] || break # Sai se vazio read -p "Artista: $oArt // " Art [ "$Art" ] && oArt="$Art" # Se vazio Art anterior Reg="$Reg$oArt~$Mus:" # Montando registro Cont=$((Cont + 1)) # A linha anterior tb poderia ser ((Cont++)) done echo "$Reg" >> musicas sort musicas -o musicas done

Onde qtd loop representa a quantidade dos loops mais internos sobre os quais os comandos iro atuar. Seu valor por default 1. Duvido que voc nunca tenha apagado um arquivo e logo aps deu um tabefe na testa se xingando porque no devia t-lo removido. Pois , na dcima vez que z esta besteira, criei um script para simular uma lixeira, isto , quando mando remover um (ou vrios) arquivo(s), o programa nge que deletou, mas no duro o que ele fez foi mand-lo(s) para o diretrio /tmp/ LoginName_do_usuario. Chamei esse programa de erreeme e no arquivo /etc/ prole coloquei a seguinte linha, que cria um apelido para ele:
alias rm=erreeme

Veja o programa na listagem 3. Como voc pode ver, a maior parte do script formada por pequenas crticas aos parmetros informados, mas como o script pode ter recebido diversos arquivos a remover, a cada arquivo que no 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 #!/bin/bash # # Salvando cpia de um arquivo antes de remov-lo # Tem de ter um ou mais arquivos a remover if [ $# -eq 0 ] then echo "Erro -> Uso: erreeme arq [arq] ... [arq]" echo "O uso de metacaracteres e permitido. Ex.U erreeme arq*" exit 1 # Varivel do sistema que contm o nome do usurio. MeuDir="/tmp/$LOGNAME" # Se no existir o meu diretrio sob o /tmp... if [ ! -d $MeuDir ] then mkdir $MeuDir # Se no posso gravar no diretrio... if [ ! -w $MeuDir ] then echo "Impossivel salvar arquivos em $MeuDir. U Mude as permisses..." exit 2 # Varivel que indica o cod. de retorno do programa Erro=0 # Um for sem o in recebe os parametros passados for Arq do # Se este arquivo no existir... if [ ! -f $Arq ] # Vou cri-lo then echo "$Arq nao existe." Erro=3 continue # Volta para o comando for # Cmd. dirname informa nome do dir de $Arq DirOrig=`dirname $Arq` # Verica permisso de gravacao no diretrio if [ ! -w $DirOrig ] then echo "Sem permisso no diretorio de $Arq" Erro=4 continue # Volta para o comando for # Se estou "esvaziando a lixeira"... if [ "$DirOrig" = "$MeuDir" ] then echo "$Arq cara sem copia de seguranca" rm -i $Arq # Pergunta antes de remover # Ser que o usurio removeu? [ -f $Arq ] || echo "$Arquivo removido" continue # Guardo no m do arquivo o seu diretrio originalU para us-lo em um script de undelete cd $DirOrig pwd >> $Arq mv $Arq $MeuDir # Salvo e removo echo "$Arq removido" done # Passo eventual nmero do erro para o cdigo # de retorno exit $Erro

um continue, para que a seqncia volte para o loop do for de forma a receber outros arquivos. Quando voc est no Windows (com perdo da m palavra) e tenta remover aquele monte de lixo com nomes esquisitos como HD04TG.TMP, se der erro em um dos arquivos os outros no so removidos, no ? Ento, o continue foi usado para evitar que uma impropriedade dessas ocorra, isto , mesmo que d erro na remoo de um arquivo, o programa continuar removendo os outros que foram passados. Eu acho que a esta altura voc deve estar curioso para ver o programa que restaura o arquivo removido, no ? Pois ento a vai vai um desao:

faa-o em casa e me traga para disum email para julio.neves@gmail. cutirmos no nosso prximo encontro com. Agora chega de papo que eu j aqui no boteco. estou de goela seca de tanto falar. Me Poxa, mas nesse eu acho que vou danacompanha no prximo chope ou j ar, pois no sei nem como comear... vai sair correndo para fazer o script Cara, este programa como tudo que passei? o que se faz em Shell: extrema- Deixa eu pensar um pouco... mente fcil. para ser feito em, no Chico, traz mais um chope enquanto mximo, 10 linhas. No se esquea ele pensa! de que o arquivo est salvo em /tmp/ Julio Cezar Neves Analista de $LOGNAME e que sua ltima linha Suporte de Sistemas desde 1969 e trao diretrio em que ele residia antes balha com Unix desde 1980, quando de ser removido. Tambm no se participou do desenvolvimento do esquea de criticar se foi passado o SOX, um sistema operacional similar nome do arquivo a ser removido. ao Unix produzido pela Cobra Com eu vou tentar, mas sei no... putadores. Pode ser contatado no Tenha f, irmo, eu t te falando que e-mail julio.neves@gmail.com mole! Qualquer dvida s passar

90
edio 06
www.linuxmagazine.com.br

SOBRE O AUTOR

Linux User

Papo de botequim

Papo de Botequim
Curso de Shell Script
Dave Hamilton - www.sxc.hu

Parte VII

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

umequi, rapaz! Derreteu os pensamentos para fazer o scriptzinho que eu te pedi? , eu realmente tive de colocar muita pensao na tela preta, mas acho que nalmente consegui! Bem, pelo menos nos testes que z a coisa funcionou, mas voc tem sempre que botar chifres em cabea de cachorro! No bem assim. que programar em Shell Script muito fcil, mas o que realmente importante so as dicas e macetes que no so triviais. As correes que fao so justamente para mostr-los. Mas vamos pedir dois chopes enquanto dou uma olhadela no teu script l na listagem 1. A Chico, traz dois chopes! E no se esquea que um deles sem colarinho!

Pera, deixa eu ver se entendi o que voc fez: voc coloca na varivel Dir a ltima linha do arquivo a ser restaurado, em nosso caso /tmp/$LOGNAME/$1 (onde $LOGNAME o nome do usurio logado, e $1 o primeiro parmetro que voc passou ao script), j que foi l que armazenamos o nome e caminho originais do arquivo antes de mov-lo para o diretrio (de nido na varivel Dir). O comando grep -v apaga essa linha, restaurando o arquivo ao estado original, e o manda de volta pra onde ele veio. A ltima linha o apaga da lixeira. Sensacional! Impecvel! Nenhum erro! Viu? Voc j est pegando as manhas do shell! Ento vamos l, chega de lesco-lesco e bl-bl-bl, sobre o qu ns vamos falar hoje?

, t vendo que o bichinho do shell te pegou. Vamos ver como ler dados, mas antes vou te mostrar um comando que te d todas as ferramentas para formatar uma tela de entrada de dados.

O comando tput
O principal uso desse comando o posicionamento do cursor na tela. Alguns parmetros podem no funcionar se o modelo de terminal de nido pela varivel de ambiente $TERM no suport-los. A tabela 1 apresenta apenas os principais parmetros e os efeitos resultantes, mas existem muito mais deles. Para saber tudo sobre o tput, veja a referncia [1]. Vamos fazer um programa bem besta e fcil para ilustrar melhor o uso desse comando. uma verso do famigerado Al Mundo, s que dessa vez a frase ser escrita no centro da tela e em vdeo reverso. Depois disso, o cursor voltar para a posio original. Veja a listagem 2. Como o programa j est todo comentado, acho que a nica linha que precisa de explicao a 8, onde criamos a varivel Coluna. O estranho ali aquele nmero 9, que na verdade indica o tamanho da cadeia de caracteres que vou escrever na tela. Dessa forma, este programa somente conseguiria centralizar cadeias de 9 caracteres, mas veja isto:
$ var=Papo $ echo ${#var} 4 $ var="Papo de Botequim" $ echo ${#var} 16

Listagem 1 restaura.sh
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 #!/bin/bash # # Restaura arquivos deletados via erreeme # if [ $# -eq 0 ] then echo "Uso: $0 <Nome do arquivo a ser restaurado>" exit 1 fi # Pega nome do arquivo/diretrio original na ltima linha Dir='tail -1 /tmp/$LOGNAME/$1' # O grep -v exclui a ltima linha e recria o arquivo com o diretrio # e nome originalmente usados grep -v $Dir /tmp/$LOGNAME/$1 > $Dir/$1 # Remove o arquivo que j estava moribundo rm /tmp/$LOGNAME/$1

86

abril 2005

edio 07 www.linuxmagazine.com.br

Linux User

Papo de botequim

Tabela 1: Parmetros do tput


Parmetro
cup lin col bold rev smso smul blink sgr0 reset lines cols el ed il n dl n dch n sc rc

e a linha 12 (echo $1) passaria a ser:


echo $*

CUrsor Position Posiciona o cursor na linha lin e coluna col. A origem (0,0) ca no canto superior esquerdo da tela. Coloca a tela em modo negrito Coloca a tela em modo de vdeo reverso Idntico ao anterior Sublinha os caracteres Deixa os caracteres piscando Restaura a tela a seu modo normal Limpa o terminal e restaura suas denies de acordo com terminfo, ou seja, o terminal volta ao comportamento padro denido pela varivel de ambiente $TERM Informa a quantidade de linhas que compem a tela Informa a quantidade de colunas que compem a tela Erase Line Apaga a linha a partir da posio do cursor Erase Display Apaga a tela a partir da posio do cursor Insert Lines Insere n linhas a partir da posio do cursor Delete Lines Remove n linhas a partir da posio do cursor Delete CHaracters Apaga n caracteres a partir da posio do cursor Save Cursor position Salva a posio do cursor Restore Cursor position Coloca o cursor na posio marcada pelo ltimo sc essa construo devolve o nmero de caracteres do primeiro parmetro passado para o programa. Se o parmetro tivesse espaos em branco, seria preciso coloc-lo entre aspas, seno o $1 levaria em conta somente o pedao antes do primeiro espao. Para evitar este aborrecimento, s substituir o $1 por $*, que como sabemos o conjunto de todos os parmetros. Ento a linha 8 caria assim:
# Centralizando a mensagem na tela Coluna=`$(((Colunas - ${#*}) / 2))`

Efeito

Lendo dados da tela


Bem, a partir de agora vamos aprender tudo sobre leitura. S no posso ensinar a ler cartas e bzios porque se soubesse estaria rico, num pub Londrino tomando um scotch e no em um boteco tomando chope. Mas vamos em frente. Da ltima vez em que nos encontramos eu dei uma palhinha sobre o comando read. Antes de entrarmos em detalhes, veja s isso:
$ read var1 var2 var3 Papo de Botequim $ echo $var1 Papo $ echo $var2 de $ echo $var3 Botequim $ read var1 var2 Papo de Botequim $ echo $var1 Papo $ echo $var2 de Botequim

Ahhh, melhorou! Ento agora sabemos que a construo ${#variavel} devolve a quantidade de caracteres da varivel. Assim sendo, vamos otimizar o nosso programa para que ele escreva em vdeo reverso, no centro da tela (e independente do nmero de caracteres) a cadeia de caracteres passada como parmetro e depois retorne o cursor posio em que estava antes da execuo do script. Veja o resultado na listagem 3. Este script igual ao anterior, s que trocamos o valor xo na varivel Coluna (9) por ${#1}, onde esse 1 $1, ou seja,

Como voc viu, o read recebe uma lista de parmetros separada por espaos em branco e coloca cada item dessa lista em uma varivel. Se a quantidade de variveis for menor que a quantidade de itens, a ltima varivel recebe o restante deles. Eu disse lista separada por espaos em branco, mas agora que voc j conhece tudo sobre o $IFS (Inter Field Separator Separador entre campos), que

Listagem 2: alo.sh
01 02 03 04 05 06 07 08 09 10 11 12 13 14 #!/bin/bash # Script bobo para testar # o comando tput (versao 1) Colunas=`tput cols` Linhas=`tput lines` Linha=$((Linhas / 2)) Coluna=$(((Colunas - 9) tput sc tput cup $Linha $Coluna tput rev echo Al Mundo tput sgr0 tput rc # # # / # # # Salva a quantidade de colunas na tela Salva a quantidade linhas na tela Qual a linha central da tela? 2)) # Centraliza a mensagem na tela Salva a posio do cursor Posiciona o cursor antes de escrever Vdeo reverso 01 02 03 04 05 06 07 08 09 10 11 12 13 14

Listagem 3: alo.sh melhorado


#!/bin/bash # Script bobo para testar # o comando tput (verso 2.0) Colunas=`tput cols` # Salva a quantidade de colunas na tela

# Restaura o vdeo ao normal # Restaura o cursor posio original

Linhas=`tput lines` # Salva a quantidade de linhas na tela Linha=$((Linhas / 2)) # Qual a linha central da tela? Coluna=$(((Colunas - ${#1}) / 2)) # Centraliza a mensagem na tela tput sc # Salva a posicao do cursor tput cup $Linha $Coluna # Posiciona o cursor antes de escrever tput rev # Video reverso echo $1 tput sgr0 # Restaura o vdeo ao normal 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 comando for, ser que ainda acredita nisso? Vamos testar:
$ oIFS="$IFS" $ IFS=: $ read var1 var2 var3 Papo de Botequim $ echo $var1 Papo de Botequim $ echo $var2 $ echo $var3 $ read var1 var2 var3 Papo:de:Botequim $ echo $var1 Papo $ echo $var2 de $ echo $var3 Botequim $ IFS="$oIFS"

para que o \n fosse entendido como uma quebra de linha (new line) e no como um literal. Sob o Bash existem diversas opes do read que servem para facilitar a sua vida. Veja a tabela 2. E agora direto aos exemplos curtos para demonstrar estas opes. Para ler um campo Matrcula:
# -n no salta linha $ echo -n "Matricula: "; read Mat Matricula: 12345 $ echo $Mat 12345

$ read -t2 -p "Digite seu nome completo: U " Nom || echo 'Eita moleza!' Digite seu nome completo: Eita moleza! $ echo $Nom

Podemos simplicar as coisas usando a opo -p:


$ read -p "Matricula: " Mat Matricula: 12345 $ echo $Mat 12345

O exemplo acima foi uma brincadeira, pois eu s tinha 2 segundos para digitar o meu nome completo e mal tive tempo de teclar um J (aquele colado no Eita), mas ele serviu para mostrar duas coisas: P 1) O comando aps o par de barras verticais (o ou or lgico, lembra-se?) ser executado caso a digitao no tenha sido concluda no tempo estipulado; P 2) A varivel Nom permaneceu vazia. Ela s receber um valor quando o ENTER for teclado.
$ read -sp Senha: Senha: $ echo $REPLY segredo :)

Viu? eu estava furado! O read l uma lista, assim como o for, separada pelos caracteres da varivel $IFS. Veja como isso pode facilitar a sua vida:
$ grep julio /etc/passwd julio:x:500:544:Julio C. Neves - 7070:U /home/julio:/bin/bash $ oIFS="$IFS" $ IFS=: $ grep julio /etc/passwd | read lname U lixo uid gid coment home shell $ echo -e "$lname\n$uid\n$gid\n$comentU \n$home\n$shell" julio 500 544 Julio C. Neves - 7070 /home/julio /bin/bash $ IFS="$oIFS" # Restaura o IFS # Salva o IFS antigo.

E podemos ler apenas uma quantidade pr-determinada de caracteres:


$ read -n5 -p"CEP: " Num ; read -n3 U -p- Compl CEP: 12345-678$ $ echo $Num 12345 $ echo $Compl 678

Como voc viu, a sada do grep foi redirecionada para o comando read, que leu todos os campos de uma s tacada. A opo -e do echo foi usada

No exemplo acima executamos duas vezes o comando read: um para a primeira parte do CEP e outra para o seu complemento, deste modo formatando a entrada de dados. O cifro ($) logo aps o ltimo algarismo digitado necessrio porque o read no inclui por padro um caractere new line implcito, como o echo. Para ler s durante um determinado limite de tempo (tambm conhecido como time out):

Aproveitei um erro no exemplo anterior para mostrar um macete. Quando escrevi a primeira linha, esqueci de colocar o nome da varivel que iria receber a senha e s notei isso quando ia escrevla. Felizmente a varivel $REPLY do Bash contm a ltima seqncia de caracteres digitada e me aproveitei disso para no perder a viagem. Teste voc mesmo o que acabei de fazer. O exemplo que dei, na verdade, era para mostrar que a opo -s impede que o que est sendo digitado seja mostrado na tela. Como no exemplo anterior, a falta do new line fez com que o prompt de comando ($) permanecesse na mesma linha. Agora que sabemos ler da tela, vejamos como se lem os dados dos arquivos.

Lendo arquivos
Como eu j havia lhe dito, e voc deve se lembrar, o while testa um comando e executa um bloco de instrues enquanto esse comando for bem sucedido. Ora, quando voc est lendo um arquivo para o qual voc tem permisso de leitura, o read s ser mal sucedido quando alcanar o EOF (End Of File Fim do Arquivo). Portanto, podemos ler um arquivo de duas maneiras. A primeira redirecionando a entrada do arquivo para o bloco while, assim:
while read Linha do echo $Linha done < arquivo

Tabela 2: Opes do read


Opo
-p prompt -n num -t seg -s

Ao Escreve prompt na tela antes de fazer a leitura L at num caracteres Espera seg segundos para que a leitura seja concluda No exibe na tela os caracteres digitados.

abril 2005 www.linuxmagazine.com.br

edio 07

89

Linux User

Papo de botequim

Como voc viu, o script lista suas prprias linhas com um sinal de menos (-) antes e outro depois de cada uma e, no nal, exibe o contedo da varivel $Ultimo. cat arquivo | Repare, no entanto, que o contedo dessa while read Linha varivel permanece vazio. U, ser que do a varivel no foi atualizada? Foi, e isso echo $Linha pode ser comprovado porque a linha echo done "-$Ultimo-" lista corretamente as linhas. Ento por que isso aconteceu? Cada um dos processos tem suas vanComo eu disse, o bloco de instrues tagens e desvantagens. O primeiro mais redirecionado pelo pipe (|) executado rpido e no necessita de um subshell em um subshell e, l, as variveis so para assisti-lo mas, em contrapartida, o atualizadas. Quando esse subshell terredirecionamento ca pouco visvel em mina, as atualizaes das variveis vo um bloco de instrues grande, o que para as profundezas do inferno junto com por vezes prejudica a visualizao do ele. Repare que vou fazer uma pequena cdigo. O segundo processo traz a van- mudana no script, passando o arquivo tagem de que, como o nome do arquivo por redirecionamento de entrada (<), e est antes do while, a visualizao do as coisas passaro a funcionar na mais cdigo mais fcil. Entretanto, o Pipe perfeita ordem: (|) chama um subshell para interpret-lo, tornando o processo mais lento e pesado. $ cat redirread.sh Para ilustrar o que foi dito, veja os exem#!/bin/bash plos a seguir: # redirread.sh
# Exemplo de read passando o arquivo $ cat readpipe.sh #!/bin/bash # readpipe.sh # Exemplo de read passando um arquivo # por um pipe. Ultimo="(vazio)" # Passa o script ($0) para o while cat $0 | while read Linha do Ultimo="$Linha" echo "-$Ultimo-" done echo "Acabou, ltimo=:$Ultimo:" $ redirread.sh -#!/bin/bash-# redirread.sh-# Exemplo de read passando o arquivo $ readpipe.sh -#!/bin/bash-# readpipe.sh-# Exemplo de read passando um arquivo -# por um pipe.--Ultimo="(vazio)"-# Passa o script ($0) para o while-cat $0 | -while read Linha-do-Ultimo="$Linha"-echo "-$Ultimo-"-done-echo "Acabou, ltimo=:$Ultimo:"Acabou, ltimo=:(vazio): -# por um pipe.--Ultimo="(vazio)"-while read Linha-do-Ultimo="$Linha"-echo "-$Ultimo-"-# Passa o script ($0) para o while-done < $0-echo "Acabou, ltimo=:$Ultimo:"Acabou, ltimo=:echo "Acabou,U ltimo=:$Ultimo:": # por um pipe. Ultimo="(vazio)" # Passa o script ($0) para o while while read Linha do Ultimo="$Linha" echo "-$Ultimo-" done < $0 echo "Acabou, ltimo=:$Ultimo:"

A segunda redirecionando a sada de um cat para o while, da seguinte maneira:

que vou mostrar com um exemplo prtico. Suponha que voc queira listar um arquivo e quer que a cada dez registros essa listagem pare para que o operador possa ler o contedo da tela, e que ela s continue depois de o operador pressionar qualquer tecla. Para no gastar papel (da Linux Magazine), vou fazer essa listagem na horizontal. Meu arquivo (numeros) tem 30 registros com nmeros seqenciais. Veja:
$ seq 30 > numeros $ cat 10porpag.sh #!/bin/bash # Programa de teste para escrever # 10 linhas e parar para ler # Verso 1 while read Num do let ContLin++ echo -n "$Num " ((ContLin % 10)) > /dev/null || read done < numeros # Contando... # -n para no saltar linha

Na tentativa de fazer um programa genrico criamos a varivel $ContLin (na vida real, os registros no so somente nmeros seqenciais) e, quando testamos se o resto da diviso era zero, mandamos a sada para /dev/null, pra que ela no aparea na tela. Mas quando fui executar o programa descobri a seguinte zebra:
$ 10porpag.sh 1 2 3 4 5 6 7 8 9 10 12 13 14 15 16 17 U 18 19 20 21 23 24 25 26 27 28 29 30

Veja como ele roda perfeitamente:

Vamos ver o resultado de sua execuo:

Repare que faltou o nmero 11 e a listagem no parou no read. Toda a entrada do loop estava redirecionada para o arquivo numeros e a leitura foi feita em cima desse arquivo, perdendo o nmero 11. Vamos mostrar como deveria car o cdigo para que ele passe a funcionar a contento:
$ cat 10porpag.sh #!/bin/bash # Programa de teste para escrever # 10 linhas e parar para ler - Verso 2 while read Num do let ContLin++ echo -n "$Num " # Contando... # -n para no saltar linha ((ContLin % 10)) > /dev/null || read U < /dev/tty done < numeros

Bem, amigos da Rede Shell, para nalizar a aula sobre o comando read s falta mais um pequeno e importante macete
edio 07 www.linuxmagazine.com.br

90

abril 2005

Papo de botequim

Linux User

Repare que agora a entrada do read foi redirecionada para /dev/tty, que nada mais seno o terminal corrente, explicitando desta forma que aquela leitura seria feita do teclado e no do arquivo numeros. bom realar que isso no acontece somente quando usamos o redirecionamento de entrada; se tivssemos usado o redirecionamento via pipe (|), o mesmo teria ocorrido. Veja agora a execuo do script:
$ 10porpag.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

# 10 linhas e parar para ler # Verso 3 clear while read Num do # Contando... ((ContLin++)) echo "$Num" ((ContLin % (`tput lines` - 3))) || { # para ler qualquer caractere read -n1 -p"Tecle Algo " < /dev/tty # limpa a tela aps a leitura clear } done < numeros

Isso est quase bom, mas ainda falta um pouco para car excelente. Vamos melhorar o exemplo para que voc o reproduza e teste (mas, antes de testar, aumente o nmero de registros em numeros ou reduza o tamanho da tela, para que haja quebra de linha).
$ cat 10porpag.sh #!/bin/bash # Programa de teste para escrever

A mudana substancial feita neste exemplo com relao quebra de pgina, j que ela feita a cada quantidade-de-linhasda-tela (tput lines) menos (-) trs, isto , se a tela tem 25 linhas, o programa listar 22 registros e parar para leitura. No comando read tambm foi feita uma alterao, inserido o parmetro -n1 para ler somente um caractere qualquer, no necessariamente um ENTER, e a opo -p para exibir uma mensagem.

Bem meu amigo, por hoje s porque acho que voc j est de saco cheio Num t no, pode continuar Se voc no estiver eu estou Mas j que voc est to empolgado com o shell, vou te deixar um servio bastante simples para voc melhorar a sua cdteca: Monte toda a tela com um nico echo e depois posicione o cursor frente de cada campo para receber o valor que ser digitado pelo operador. No se esqueam que, em caso de qualquer dvida ou falta de companhia para um chope s mandar um e-mail para julio.neves@gmail.com. Vou aproveitar tambm para fazer uma propaganda: digam aos amigos que quem estiver a m de fazer um curso porreta de programao em shell deve mandar um email para julio.neves@tecnohall.com.br para informar-se. At mais!

Informaes
[1] Pgina ocial do Tput: http://www.cs.utah.edu/ dept/old/texinfo/tput/tput.html#SEC4 [2] Pgina ocial do Bash: http://www.gnu.org/ software/bash/bash.html

abril 2005 www.linuxmagazine.com.br

edio 07

91

Linux User

Papo de botequim

Papo de Botequim
Curso de Shell Script
Dave Hamilton - www.sxc.hu

Parte VIII

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

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 passagem de parmetros para programas tambm valem para que voc vai querer molhar o bico primeiro, n? 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 gostaRepare 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 centralizadas 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 incor08 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 par10 do metros e as funes os recebem da 11 tput cup 5 38; tput el # Posiciona e limpa linha mesma forma, isto , so parmetros
maio 2005 edio 08 www.linuxmagazine.com.br

86

papo de botequim

Linux User

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

read Album [ ! $Album ] && # Operador deu <ENTER> { Msg=Deseja Terminar? (S/n) TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # Centraliza msg na linha tput cup $LinhaMesg $Col echo $Msg tput cup $LinhaMesg $((Col + TamMsg + 1)) read -n1 SN tput cup $LinhaMesg $Col; tput el # Apaga msg da tela [ $SN = N -o $SN = n ] && continue # $SN igual a N ou (-o) n? clear; exit # Fim da execuo } grep ^$Album\^ musicas > /dev/null && { Msg=Este lbum j est cadastrado TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # Centraliza msg na linha tput cup $LinhaMesg $Col echo $Msg read -n1 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela continue # Volta para ler outro lbum } Reg=$Album^ # $Reg receber os dados para gravao oArtista= # Varivel que guarda artista anterior while true do ((Faixa++)) tput cup 7 38 echo $Faixa tput cup 9 38 # Posiciona para ler msica read Musica [ $Musica ] || # Se o operador tiver dado <ENTER>... { Msg=Fim de lbum? (S/n) TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # Centraliza msg na linha tput cup $LinhaMesg $Col echo $Msg

53 tput cup $LinhaMesg $((Col + TamMsg + 1) 54 read -n1 SN 55 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela 56 [ $SN = N -o $SN = n ] && continue # $SN igual a N ou (-o) n? 57 break # Sai do loop para gravar 58 } 59 tput cup 11 38 # Posiciona para ler Artista 60 [ $oArtista ] && echo -n ($oArtista) # Artista anterior default 61 read Artista 62 [ $Artista ] && oArtista=$Artista 63 Reg=$Reg$oArtista~$Musica: # Montando registro 64 tput cup 9 38; tput el # Apaga Msica da tela 65 tput cup 11 38; tput el # Apaga Artista da tela 66 done 67 echo $Reg >> musicas # Grava registro no fim do arquivo 38 sort musicas -0 musicas # Classifica o arquivo 69 done

mesmo tempo, mostrar as opes disponveis e realar a resposta oferecida como padro. Quase no m da rotina, a resposta recebida ($SN) convertida para caixa baixa (minsculas) de forma que no corpo do programa no precisemos fazer esse teste. Veja na listagem 3 como caria a funo para exibir uma mensagem na tela. Essa uma outra forma de denir uma funo: no a chamamos, como no exemplo anterior, usando uma construo com a sintaxe nome_da_ funo (), mas sim como function nome_da_funo. Em nada mais ela difere da anterior, exceto que, como consta dos comentrios, usamos a varivel $* que, como j sabemos, representa o conjunto de todos os parmetros passados ao script, para que o programador no precise usar aspas envolvendo a mensagem que deseja passar funo. Para terminar com esse bl-bl-bl, vamos ver na listagem 4 as alteraes no programa quando usamos o conceito de funes: Repare que a estrutura do script segue a ordem Variveis Globais, Funes e Corpo do Programa. Esta estruturao se deve ao fato de Shell Script ser uma linguagem interpretada, em que o programa lido da esquerda para a direita e de cima para baixo. Para ser vista pelo script e suas funes, uma varivel deve ser declarada (ou inicializada, como preferem alguns) antes de qualquer outra coisa. Por sua vez, as funes devem ser declaradas antes do corpo do programa propriamente dito. A explicao simples: o interpretador de comandos do shell deve saber do que se trata a funo antes que ela seja chamada no programa principal. Uma coisa bacana na criao de funes faz-las to genricas quanto possvel, de forma que possam ser reutilizadas em outros scripts e aplicativos sem a necessidade de reinventarmos a roda. As duas funes que acabamos de ver so bons exemplos, pois difcil um script de entrada de dados que no use uma rotina como a MandaMsg ou que no interaja com o operador por meio de algo semelhante Pergunta.
maio 2005 edio 08

www.linuxmagazine.com.br

87

Linux User

Papo de botequim

Conselho de amigo: crie um arquivo e anexe a ele cada funo nova que voc criar. Ao nal de algum tempo voc ter uma bela biblioteca de funes que lhe poupar muito tempo de programao.

diretrio /home. S que assim que a execuo do script terminou, o sub-shell foi para o belelu e, com ele, todo o ambiente criado. Agora preste ateno no exemplo abaixo e veja como a coisa muda de gura:
$ source script_bobo jneves $ pwd /home juliana paula silvie

O comando source
Veja se voc nota algo de diferente na sada do ls a seguir:
$ ls -la .bash_profile -rw-r--r-- 1 Julio unknown 4511 Mar 18 17:45 .bash_profile

$ cd /home/jneves

$ . script_bobo No olhe a resposta no, volte a prestar ateno! Bem, j que jneves juliana paula silvie voc est mesmo sem saco de pensar e prefere ler a resposta, $ pwd vou te dar uma dica: acho que voc j sabe que o .bash_pro/home file um dos scripts que so automaticamente executados quando voc se loga (ARRGGHH! Odeio esse termo!) no sistema. Agora olhe novaListagem 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 exe03 # A funo recebe 3 parmetros na seguinte ordem: cutado durante o logon, mas repare que ele 04 # $1 - Mensagem a ser mostrada na tela no tem nenhuma permisso de execuo. 05 # $2 - Valor a ser aceito com resposta padro Isso acontece porque se voc o executasse 06 # $3 - O outro valor aceito como qualquer outro script careta, no m 07 # Supondo que $1=Aceita?, $2=s e $3=n, a linha a de sua execuo todo o ambiente por ele 08 # seguir colocaria em Msg o valor Aceita? (S/n) gerado morreria junto com o shell sob o 09 local Msg=$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`) qual ele foi executado (voc se lembra de 10 local TamMsg=${#Msg} que todos os scripts so executados em 11 local Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha sub-shells, n?). Pois para coisas assim 12 tput cup $LinhaMesg $Col que existe o comando source, tambm 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 $ cat script_bobo cd .. ls 19 20 } return # Sai da funo

Ele simplesmente deveria ir para o diretrio acima do diretrio atual. Vamos executar uns comandos envolvendo o script_bobo e analisar os resultados:
$ pwd /home/jneves $ script_bobo jneves $ pwd /home/jneves juliana paula silvie

Listagem 3: Funo MandaMsg


01 function MandaMsg 02 { 03 # A funo recebe somente um parmetro 04 # com a mensagem que se deseja exibir. 05 # para no obrigar o programador a passar 06 # a msg entre aspas, usaremos $* (todos 07 # os parmetros, lembra?) e no $1. 08 local Msg=$* 09 local TamMsg=${#Msg} 10 local Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha 11 tput cup $LinhaMesg $Col 12 echo $Msg 13 read -n1 14 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela 15 return # Sai da funo 16 }

Se eu mandei ele subir um diretrio, por que no subiu? Opa, pera que subiu sim! O sub-shell que foi criado para executar o script tanto subiu que listou os diretrios dos quatro usurios abaixo do

88

maio 2005

edio 08 www.linuxmagazine.com.br

papo de botequim

Linux User

Listagem 4: musinc6.sh
01 $ cat musinc6 02 #!/bin/bash 03 # Cadastra CDs (versao 6) 04 # 05 # rea de variveis globais 06 # Linha onde as mensagens sero exibidas 07 LinhaMesg=$((`tput lines` - 3)) 08 # Quantidade de colunas na tela (para enquadrar as mensagens) 09 TotCols=$(tput cols) 10 # rea de funes 11 Pergunta () 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 { # A funo recebe 3 parmetros na seguinte ordem: # $1 - Mensagem a ser dada na tela # $2 - Valor a ser aceito com resposta default # $3 - O outro valor aceito # Supondo que $1=Aceita?, $2=s e $3=n, a linha # abaixo colocaria em Msg o valor Aceita? (S/n) local Msg=$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`) local TamMsg=${#Msg} # Centraliza a mensagem na linha local Col=$(((TotCols - TamMsg) / 2)) tput cup $LinhaMesg $Col echo $Msg tput cup $LinhaMesg $((Col + TamMsg + 1)) read -n1 SN [ ! $SN ] && SN=$2 # Se vazia, coloca default em SN # Apaga msg da tela # Sai da funo echo $SN | tr A-Z a-z # A sada de SN ser em minsculas tput cup $LinhaMesg $Col; tput el return } { # A funo recebe somente um parmetro # com a mensagem que se deseja exibir; # para no obrigar o programador a passar # a msg entre aspas, usaremos $* (todos # os parmetro, lembra?) e no $1. local Msg=$* local TamMsg=${#Msg} # Centraliza mensagem na linha local Col=$(((TotCols - TamMsg) / 2)) tput cup $LinhaMesg $Col echo $Msg read -n1 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela return } # Sai da funo

52 echo Incluso de Msicas ======== == ======= Ttulo do lbum: Faixa: Nome da Msica: Intrprete: 53 while true 54 do 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 done # Grava registro no fim do arquivo echo $Reg >> musicas # Classifica o arquivo sort musicas -o musicas tput cup 5 38; tput el read Album [ ! $Album ] && { Pergunta Deseja Terminar s n # Agora s testo caixa baixa [ $SN = n ] && continue clear; exit } grep -iq ^$Album\^ musicas 2> /dev/null && { MandaMsg Este lbum j est cadastrado continue } Reg=$Album^ oArtista= while true do ((Faixa++)) tput cup 7 38 echo $Faixa tput cup 9 38 read Musica [ $Musica ] || { Pergunta Fim de lbum? s n # Agora s testo a caixa baixa [ $SN = n ] && continue break } tput cup 11 38 # Posiciona para ler Artista # O artista anterior o padro [ $oArtista ] && echo -n ($oArtista) read Artista [ $Artista ] && oArtista=$Artista Reg=$Reg$oArtista~$Musica: # Montando registro tput cup 9 38; tput el tput cup 11 38; tput el # Apaga Msica da tela # Apaga Artista da tela # Sai do loop para gravar dados # Se o operador teclou <ENTER>... # Posiciona para ler msica # $Reg receber os dados de gravao # Guardar artista anterior # Volta para ler outro lbum # Fim da execuo # Operador deu <ENTER> # Posiciona e limpa linha

32 function MandaMsg

49 # O corpo do programa propriamente dito comea aqui 50 clear 51 # A tela a seguir montada com um nico comando echo

98 done

maio 2005 www.linuxmagazine.com.br

edio 08

89

Linux User

Papo de botequim

Listagem 5: musinc7.sh
01 02 03 04 05 06 07 08 09 10 $ cat musinc7.sh #!/bin/bash # Cadastra CDs (versao 7) # # rea de variveis globais LinhaMesg=$((`tput lines` - 3)) # Linha onde sero mostradas as msgs para o operador TotCols=$(tput cols) # Qtd colunas da tela para enquadrar msgs # O corpo do programa propriamente dito comea aqui clear echo Incluso de Msicas ======== == ======= Ttulo do lbum: | Este campo foi Faixa: < criado somente para | orientar o preenchimento Nome da Msica: Intrprete: # Tela montada com um nico echo while true do tput cup 5 38; tput el # Posiciona e limpa linha read Album [ ! $Album ] && # Operador deu <ENTER> { source pergunta.func Deseja Terminar s n [ $SN = n ] && continue # Agora s testo a caixa baixa clear; exit # Fim da execuo } grep -iq ^$Album\^ musicas 2> /dev/null && { . mandamsg.func Este lbum j est cadastrado continue # Volta para ler outro lbum } Reg=$Album^ # $Reg receber os dados de gravao oArtista= # Guardar artista anterior while true do ((Faixa++)) tput cup 7 38 echo $Faixa tput cup 9 38 # Posiciona para ler msica read Musica [ $Musica ] || # Se o operador tiver dado <ENTER>... { . pergunta.func Fim de lbum? s n [ $SN = n ] && continue # Agora s testo a caixa baixa break # Sai do loop para gravar dados } tput cup 11 38 # Posiciona para ler Artista [ $oArtista ] && echo -n ($oArtista) # Artista anterior default read Artista [ $Artista ] && oArtista=$Artista Reg=$Reg$oArtista~$Musica: # Montando registro tput cup 9 38; tput el # Apaga Msica da tela tput cup 11 38; tput el # Apaga Artista da tela done echo $Reg >> musicas # Grava registro no fim do arquivo sort musicas -o musicas # Classifica o arquivo done

11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

Ahh! Agora sim! Quando passado como parmetro do comando source, o script foi executado no shell corrente, deixando nele todo o ambiente criado. Agora vamos rebobinar a ta at o incio da explicao sobre este comando. L falamos do .bash_profile e, a esta altura, voc j deve saber que sua incumbncia , logo aps o login, preparar o ambiente de trabalho para o usurio. Agora entendemos que por isso mesmo que ele executado usando esse artifcio. E agora voc deve estar se perguntando se s para isso que esse comando serve. Eu lhe digo que sim, mas isso nos traz um monte de vantagens e uma das mais usadas tratar funes como rotinas externas. Veja na listagem 5 uma outra forma de fazer o nosso programa para incluir CDs no arquivo musicas. Agora o programa deu uma boa encolhida e as chamadas de funo foram trocadas por arquivos externos chamados pergunta.func e mandamsg.func, que assim podem ser chamados por qualquer outro programa, dessa forma reutilizando o seu cdigo. Por motivos meramente didticos, as chamadas a pergunta.func e mandamsg.func esto sendo feitas por source e por . (ponto) indiscriminadamente, embora eu prera o source que, por ser mais visvel, melhora a legibilidade do cdigo e facilita sua posterior manuteno. Veja na listagem 6 como caram esses dois arquivos. Em ambos os arquivos, z somente duas mudanas, que veremos nas observaes a seguir. Porm, tenho mais trs observaes a fazer: 1. As variveis no esto sendo mais declaradas como locais, porque essa uma diretiva que s pode ser usada no corpo de funes e, portanto, essas variveis permanecem no ambiente do shell, poluindo-o; 2. O comando return no est mais presente, mas poderia estar sem alterar em nada a lgica do script, uma vez que s serviria para indicar um eventual erro por meio de um cdigo de retorno previamente estabelecido (por exemplo return 1, return 2, ), sendo que o return e return 0 so 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 cha05 # $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 roti13 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 comen16 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 manu18 $ 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 lem23 # 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 pas30 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 compado 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 $ cat listartista.sh #!/bin/bash # Dado um artista, mostra as suas musicas # versao 2 if [ $# -eq 0 ] then echo Voce deveria ter passado pelo menos um parametro exit 1 fi IFS= : for ArtMus in $(cut -f2 -d^ musicas) do echo $ArtMus | grep -i ^$*~ > /dev/null && echo $ArtMus | cut -f2 -d~ done

Informaes
[1] Bash, pgina ocial: http://www.gnu.org/software/bash/bash.html [2] Manual de referncia do Bash: http://www.gnu.org/software/bash/ manual/bashref.html

Julio Cezar Neves Analista de Suporte de Sistemas desde 1969 e trabalha com Unix desde 1980, quando participou do desenvolvimento do SOX, um sistema operacional similar ao Unix produzido pela Cobra Computadores. Pode ser contatado no e-mail julio.neves@gmail.com

Sobre o autor

maio 2005 www.linuxmagazine.com.br

edio 08

91

Linux User

Papo de Botequim

Papo de Botequim
Curso de Shell Script
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

Parte IX

Dave Hamilton - www.sxc.hu

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 Listagem 1: mandamsg.func e pergunta.func o programa listartista com a tela formatada e execuo 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. 03 # Para no obrigar o programador a passar usurio deveriam ser mostradas na antepenltima 04 # a msg entre aspas, usaremos $* (todos linha da tela, utilizando para isso as rotinas externas 05 # os parmetro, lembra?) e no $1. mandamsg.func e pergunta.func que desenvolvemos 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. pergunta.func Puxa, voc chegou com a corda toda! Gostei da 01 # A funo recebe 3 parmetros na seguinte ordem: forma como voc resolveu o problema e estruturou 02 # $1 - Mensagem a ser mostrada na tela o programa. Foi mais trabalhoso, mas a apresenta03 # $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`)"

Envenenando a escrita
Ufa! Agora voc j sabe tudo sobre leitura de dados, mas quanto escrita ainda est apenas engatinhando. J sei que voc vai me perguntar: Ora, no com o comando echo e com os redirecionamentos de sada que se escreve dados?.

08 09 10 11 12 13 14

TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # Centraliza msg na linha tput cup $LinhaMesg $Col read -n1 -p "$Msg " SN [ ! $SN ] && SN=$2 # Se vazia coloca default em SN SN=$(echo $SN | tr A-Z a-z) # A sada de SN ser em minscula tput cup $LinhaMesg $Col; tput el # Apaga msg da tela

84

junho 2005

edio 09 www.linuxmagazine.com.br

Papo de Botequim

Linux User

printf formato [argumento...]

Listagem 2: listaartista
$ cat listartista3.sh 01 #!/bin/bash 02 # Dado um artista, mostra as suas musicas 03 # versao 3 04 LinhaMesg=$((`tput lines` - 3)) # Linha onde as msgs sero mostradas 05 TotCols=$(tput cols) # Qtd de colunas na tela para enquadrar msgs 06 clear 07 echo " +-----------------------------------+ | Lista Todas as Msicas de um Determinado Artista | | ===== ===== == ======= == == =========== ======= | | | | Informe o Artista: | +-----------------------------------+" 08 while true 09 do 10 tput cup 5 51; tput ech 31 # ech=Erase chars (31 para no apagar barra vertical) 11 read Nome 12 if [ ! "$Nome" ] # $Nome esta vazio? 13 then 14 . pergunta.func "Deseja Sair?" s n 15 [ $SN = n ] && continue 16 break 17 fi 18 fgrep -iq "^$Nome~" musicas || # fgrep no interpreta ^ como expresso regular 19 { 20 . mandamsg.func "No existe msica desse artista" 21 continue 22 } 23 tput cup 7 29; echo '| |' 24 LinAtual=8 25 IFS=" 26 :" 27 for ArtMus in $(cut -f2 -d^ musicas) # Exclui nome do album 28 do 29 if echo "$ArtMus" | grep -iq "^$Nome~" 30 then 31 tput cup $LinAtual 29 32 echo -n '| " 33 echo $ArtMus | cut -f2 -d~ 34 tput cup $LinAtual 82 35 echo '|' 36 let LinAtual++ 37 if [ $LinAtual -eq $LinhaMesg ] 38 then 39 . mandamsg.func "Tecle Algo para Continuar..." 40 tput cup 7 0; tput ed # Apaga a tela a partir da linha 7 41 tput cup 7 29; echo '| |' 42 LinAtual=8 43 fi 44 fi 45 done 46 tput cup $LinAtual 29; echo '| |' 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 50 done

Onde formato uma cadeia de caracteres que contm trs tipos de objeto: caracteres simples, caracteres para especicao de formato (ou de controle) e seqncia de escape no padro da linguagem C. argumento a cadeia de caracteres a ser impressa sob o controle de formato. Cada um dos caracteres utilizados precedido pelo caracter % e, logo a seguir, vem a especicao de formato de acordo com a tabela 1. As seqncias de escape padro da linguagem C so sempre precedidas pelo caractere contra-barra (\). As reconhecidas pelo comando printf so as da tabela 2. No acaba por a no! Tem muito mais coisa sobre essa instruo, mas como esse um assunto muito cheio de detalhes e, portanto, chato para explicar e pior ainda para ler ou estudar, vamos passar direto aos exemplos com comentrios. Veja s:
$ printf "%c" "1 caracter" 1$

Errado! S listou 1 caractere e no saltou linha ao nal


$ printf "%c\n" "1 caracter" 1

Saltou linha mas ainda no listou a cadeia inteira


$ printf "%c caractere\n" 1 1 caractere

junho 2005 www.linuxmagazine.com.br

edio 09

85

Linux User

Papo de Botequim

Tabela 1: Formatos de caractere


Caractere c d e f g o s x % A expresso ser impressa como: Caractere simples Nmero no sistema decimal Notao cientca exponencial Nmero com ponto decimal (oat) O menor entre os formatos %e e %f com omisso dos zeros no signicativos Nmero no sistema octal Cadeia de caracteres Nmero no sistema hexadecimal Imprime um %. No h nenhum tipo de converso

(%) signica o tamanho que a cadeia ter aps a execuo do comando. Vamos ver a seguir mais alguns exemplos. Os comandos abaixo:
$ printf "%d\n" 32 32 $ printf "%10d\n" 32 32

O bc devolveu duas casas decimais e o printf colocou o zero direita. O comando a seguir:
$ printf "%o\n" 10 12

Converteu o valor 10 para base octal. Para melhorar experimente:


$ printf "%03o\n" 27 033

preenchem a string com espaos em branco esquerda (oito espaos mais dois caracteres, 10 dgitos), no com zeros. J no comando abaixo:
$ printf "%04d\n" 32 0032

Opa, essa a forma correta! O %c recebeu o valor 1, como queramos:


$ a=2 $ printf "%c caracteres\n" $a 2 caracteres

Assim a converso ca com mais jeito de octal, n?. O que este aqui faz?
$ printf "%s\n" Peteleca Peteleca $ printf "%15s\n" Peteleca Peteleca

O 04 aps % signica formate a string em quatro dgitos, com zeros esquerda se necessrio. No comando:
$ printf "%e\n" $(echo "scale=2 ; 100/6" | bc)

O %c recebeu o valor da varivel $a.


$ printf "%10c caracteres\n" $a 2 caracteres $ printf "%10c\n" $a caracteres 2 c $ printf "%.2e\n" `echo "scale=2 ; 100/6" | bc` 1.666000e+01

Imprime Peteleca com 15 caracteres. A cadeia de caracteres preenchida com espaos em branco esquerda. J no comando:
$ printf "%-15sNeves\n" Peteleca Peteleca Neves

O padro do %e seis casas decimais. J no comando:

Repare que, nos dois ltimos exemplos, em virtude do uso do %c, s foi listado um caractere de cada cadeia de caracteres passada como parmetro. O valor 10 frente do c no signica 10 caracteres. Um nmero seguindo o sinal de percentagem

1.67e+01

O .2 especicou duas casas decimais. Observe agora:


$ printf "%f\n" 32.3 32.300000

O menos (-) colocou espaos em branco direita de Peteleca at completar os 15 caracteres pedidos. E o comando abaixo, o que faz?
$ printf "%.3s\n" Peteleca Pet

Tabela 2: Seqncias de escape


a b f

Seqncia

n r t

Soa o beep Volta uma posio (backspace) Salta para a prxima pgina lgica ( form feed) Salta para o incio da linha seguinte (line feed) Volta para o incio da linha corrente (carriage return) Avana para a prxima marca de tabulao

Efeito

O padro do %f seis casas decimais. E no comando:


$ printf "%.2f\n" 32.3 32.30

O .3 manda truncar a cadeia de caracteres aps as trs primeiras letras. E o comando a seguir:
$ printf "%10.3sa\n" Peteleca Peta Pet

O .2 especicou duas casas decimais. Agora observe:


$ printf "%.3f\n" `echo "scale=2 ; 100/6" | bc` 33.330

Imprime a cadeia com 10 caracteres, truncada aps os trs primeiros, concatenada com o caractere a (aps o s). E 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 EXEMPLO b0b0

trabalho, principalmente em instalaes com estrutura de diretrios em mltiplos nveis. Veja o exemplo a seguir:
$ echo $CDPATH .:..:~:/usr/local $ pwd /home/jneves/LM $ cd bin $ pwd /usr/local/bin

Ele transformou o nmero 45232 para hexadecimal (b0b0), mas os zeros no combinam com o resto. Experimente:
$ printf EXEMPLO %X\n 45232 EXEMPLO B0B0

Assim disfarou melhor! (repare no X maisculo). Pra terminar, que tal o comando abaixo:
$ printf %X %XL%X\n 49354 192 10 C0CA C0LA

Este a no marketing e bastante completo, veja s como funciona: O primeiro %X converteu 49354 em hexadecimal, resultando em C0CA (leia-se c, zero, c e a). Em seguida veio um espao em branco seguido por outro %XL. O %X converteu o 192 dando como resultado C0 que com o L fez C0L. E nalmente o ltimo parmetro %X transformou o nmero 10 na letra A. Conforme vocs podem notar, a instruo bastante completa e complexa. Ainda bem que o echo resolve quase tudo... Acertei em cheio quando resolvi explicar o printf atravs de exemplos, pois no saberia como enumerar tantas regrinhas sem tornar a leitura enfadonha.

Como /usr/local estava na minha varivel $CDPATH e no existia o diretrio bin em nenhum dos seus antecessores (., .. e ~), o comando cd foi executado tendo como destino /usr/local/bin. HISTSIZE Limita o nmero de instrues que cabem dentro do arquivo de histrico de comandos (normalmente .bash_history, mas na verdade o que est indicado na varivel $HISTFILE). Seu valor padro 500. HOSTNAME O nome do host corrente (que tambm pode ser obtido com o comando uname -n). LANG Usada para determinar o idioma falado no pas (mais especicamente categoria do locale). Veja um exemplo:
$ date Thu Apr 14 11:54:13 BRT 2005 $ LANG=pt_BR date Qui Abr 14 11:55:14 BRT 2005 LINENO O nmero da linha do script ou funo que est sendo executada. Seu uso principal mostrar mensagens de erro juntamente com as variveis $0 (nome do programa) e $FUNCNAME (nome da funo em execuo). LOGNAME Esta varivel armazena o nome de login do usurio . MAILCHECK Especica, em segundos, a freqncia com que o Shell verica a presena de correspondncia nos arquivos indicados pela variveis $MAILPATH ou $MAIL. O tempo padro de 60 segundos

(1 minuto). A cada intervalo o Shell far a vericao antes de exibir o prximo prompt primrio ($PS1). Se essa varivel estiver sem valor ou com um valor menor ou igual a zero, a busca por novas mensagens no ser efetuada. PATH Caminhos que sero pesquisados para tentar localizar um arquivo especicado. Como cada script um arquivo, caso use o diretrio corrente (.) na sua varivel $PATH, voc no necessitar usar o comando ./scrp para que o script scrp seja executado. Basta digitar scrp. Este o modo que prero. PIPESTATUS uma varivel do tipo vetor (array) que contm uma lista de valores de cdigos de retorno do ltimo pipeline executado, isto , um array que abriga cada um dos $? de cada instruo do ltimo pipeline. Para entender melhor, veja o exemplo a seguir:
$ who jneves pts/0 Apr 11 16:26 (10.2.4.144) jneves pts/1 Apr 12 12:04 (10.2.4.144) $ who | grep ^botelho $ echo ${PIPESTATUS[*]} 0 1

Principais variveis do Shell


O Bash possui diversas variveis que servem para dar informaes sobre o ambiente ou alter-lo. So muitas e no pretendo mostrar todas elas, mas uma pequena parte pode lhe ajudar na elaborao de scripts. Veja a seguir as principais delas: CDPATH Contm os caminhos que sero pesquisados para tentar localizar um diretrio especicado. Apesar dessa varivel ser pouco conhecida, seu uso deve ser incentivado por poupar muito

Neste exemplo mostramos que o usurio botelho no estava logado, em seguida executamos um pipeline que procurava por ele. Usa-se a notao [*] em um array para listar todos os seus elementos; dessa forma, vimos que a primeira instruo (who) foi bem-sucedida (cdigo de retorno 0) e a seguinte (grep) no (cdigo de retorno 1). PROMPT_COMMAND Se esta varivel receber o nome de um comando, toda vez que voc teclar um [ENTER] sozinho no prompt principal ($PS1), esse comando ser executado. muito til quando voc precisa repetindo constantemente uma determinada instruo. PS1 o prompt principal. No Papo de Botequim usamos os padres $ para usurio comum e # para root, mas mui-

junho 2005 www.linuxmagazine.com.br

edio 09

87

Linux User

Papo de Botequim

to freqente que ele esteja personalizado. Uma curiosidade que existe at concurso de quem programa o $PS1 mais criativo. PS2 Tambm chamado prompt de continuao, aquele sinal de maior (>) que aparece aps um [ENTER] sem o comando ter sido encerrado. PWD Possui o caminho completo ($PATH) do diretrio corrente. Tem o mesmo efeito do comando pwd. RANDOM Cada vez que esta varivel acessada, devolve um inteiro aleatrio entre 0 e 32767. Para gerar um inteiro entre 0 e 100, por exemplo, digitamos:
$ echo $((RANDOM%101)) 73

Expanso de parmetros
Bem, muito do que vimos at agora so comandos externos ao Shell. Eles quebram o maior galho, facilitam a visualizao, manuteno e depurao do cdigo, mas no so to ecientes quanto os intrnsecos (built-ins). Quando o nosso problema for performance, devemos dar preferncia ao uso dos intrnsecos. A partir de agora vou te mostrar algumas tcnicas para o seu programa pisar no acelerador. Na tabela 3 e nos exemplos a seguir, veremos uma srie de construes chamadas expanso (ou substituio) de parmetros (Parameter Expansion), que substituem instrues como o cut, o expr, o tr, o sed e outras de forma mais gil. Vamos ver alguns exemplos: se em uma pergunta o S oferecido como valor default (padro) e a sada vai para a varivel SN, aps ler o valor podemos fazer:
SN=$(SN:-S}

$ cadeia="Papo de Botequim" $ echo ${cadeia#*' '} de Botequim $ echo "Conversa "${cadeia#*' '} Conversa de Botequim

No exemplo anterior foi suprimido esquerda tudo o que casa com a menor ocorrncia da expresso * , ou seja, todos os caracteres at o primeiro espao em branco. Esses exemplos tambm poderiam ser escritos sem proteger o espao da interpretao do Shell (mas prero proteg-lo para facilitar a legibilidade do cdigo). Veja s:
$ echo ${cadeia#* } de Botequim $ echo "Conversa "${cadeia#* } Conversa de Botequim

Ou seja, pegamos o resto da diviso do nmero randmico gerado por 101 porque o resto da diviso de qualquer nmero por 101 varia entre 0 e 100. REPLY Use esta varivel para recuperar o ltimo campo lido, caso ele no tenha nenhuma varivel associada. Exemplo:
$ read -p "Digite S ou N: " Digite S ou N: N $ echo $REPLY N SECONDS Esta varivel informa, em se-

Para saber o tamanho de uma cadeia:


$ cadeia=0123 $ echo ${#cadeia} 4

Repare que na construo de expr permitido o uso de metacaracteres. Utilizando o mesmo valor da varivel cadeia, observe como faramos para ter somente Botequim :
$ echo ${cadeia##*' '} Botequim $ echo "Vamos 'Chopear' no "${cadeia##*' '} Vamos 'Chopear' no Botequim

Para extrair dados de cadeia, da posio um at o nal fazemos:


$ cadeia=abcdef $ echo ${cadeia:1} bcdef

gundos, h quanto tempo o Shell corrente est de p. Use-a para demonstrar a estabilidade do Linux e esnobar usurios daquela coisa com janelinhas coloridas que chamam de sistema operacional, mas que necessita de reboots freqentes. TMOUT Se esta varivel contiver um valor maior do que zero, esse valor ser considerado o timeout padro do comando read. No prompt, esse valor interpretado como o tempo de espera por uma ao antes de expirar a sesso. Supondo que a varivel contenha o valor 30, o Shell encerrar a sesso do usurio (ou seja, far logout) aps 30 segundos sem nenhuma ao no prompt.

Desta vez suprimimos esquerda de


cadeia a maior ocorrncia da expresso expr. Assim como no caso anterior, o uso

Repare que a origem zero e no um. Vamos extrair 3 caracteres a partir da 2 posio da mesma varivel cadeia:
$ echo ${cadeia:2:3} cde

de metacaracteres permitido. Outro exemplo mais til: para que no aparea o caminho (path) completo do seu programa ($0) em uma mensagem de erro, inicie o seu texto da seguinte forma:
echo Uso: ${0##*/} texto da mensagem de erro

Repare que novamente a origem da contagem zero e no um. Para suprimir tudo esquerda da primeira ocorrncia de uma cadeia, faa:

Neste exemplo seria suprimido esquerda tudo at a ltima barra (/) do caminho, restando somente o nome do programa. O caractere % simtrico ao #, veja o exemplo:

88

junho 2005

edio 09 www.linuxmagazine.com.br

Papo de Botequim

Linux User

Tabela 3: Tipos de expanso de parmetros


${var:-padrao} ${#cadeia} ${cadeia:posicao} ${cadeia:posicao:tamanho} Se var vazia, o resultado da expresso padro Tamanho de $cadeia Extrai uma subcadeia de $cadeia a partir de posio. Origem zero Extrai uma subcadeia de $cadeia a partir de posio com tamanho igual a tamanho. Origem zero ${cadeia#expr} Corta a menor ocorrncia de $cadeia esquerda da expresso expr ${cadeia##expr} Corta a maior ocorrncia de $cadeia esquerda da expresso expr ${cadeia%expr} Corta a menor ocorrncia de $cadeia direita da expresso expr ${cadeia%%expr} Corta a maior ocorrncia de $cadeia direita da expresso expr ${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 ${cadeia/%subcad1/subcad2} Se subcad1 combina com o m de cadeia, ento trocado por subcad2

Expanso de parmetros Resultado esperado

H vrias formas de trocar uma subcadeia no incio ou no m de uma varivel. Para trocar no incio fazemos:
$ echo $Passaro quero quero $ echo Como diz o sulista - ${PassaroU /#quero/no} Como diz o sulista - no quero

Para trocar no nal fazemos:


$ echo Como diz o nordestino - U ${Passaro/%quero/no} Como diz o nordestino - quero no

$ echo $cadeia Papo de Botequim $ echo ${cadeia%' '*} Papo de $ echo ${cadeia%%' '*} Papo

$ echo ${cadeia/*po/Conversa} Conversa de Botequim $ echo ${cadeia/????/Conversa} Conversa de Botequim

Trocando todas as ocorrncias de uma subcadeia por outra. O comando:


$ echo ${cadeia//o/a} Papa de Batequim

Para trocar primeira ocorrncia de uma subcadeia em uma cadeia por outra:
$ echo $cadeia Papo de Botequim $ echo ${cadeia/de/no} Papo no Botequim $ echo ${cadeia/de /} Papo Botequim

Ordena a troca de todos as letras o por


a. Outro exemplo mais til para contar

a quantidade de arquivos existentes no diretrio corrente. Observe o exemplo:


$ ls | wc -l

Preste ateno quando for usar metacaracteres! Eles so gulosos e sempre combinaro com a maior possibilidade; No exemplo a seguir eu queria trocar Papo de Botequim por Conversa de Botequim:
$ echo $cadeia Papo de Botequim $ echo ${cadeia/*o/Conversa} Conversatequim

30

O wc pe um monte de espaos em branco antes do resultado. Para tir-los:


# QtdArqs recebe a sada do comando $ QtdArqs=$(ls | wc -l) $ echo ${QtdArqs/ * /} 30

Agora j chega, o papo hoje foi chato porque teve muita decoreba, mas o que mais importa voc ter entendido o que te falei. Quando precisar, consulte estes guardanapos onde rabisquei as dicas e depois guarde-os para consultas futuras. Mas voltando vaca fria: t na hora de tomar outro e ver o jogo do Mengo. Pra prxima vez vou te dar moleza e s vou cobrar o seguinte: pegue a rotina pergunta.func (da qual falamos no incio do nosso bate-papo de hoje, veja a listagem 1) e otimize-a para que a varivel $SN receba o valor padro por expanso de parmetros, como vimos. E no se esquea: em caso de dvidas ou falta de companhia para um (ou mais) chope s mandar um e-mail para julio. neves@gmail.com. E diga para os amigos que quem estiver a m de fazer um curso porreta de programao em Shell deve mandar um e-mail para julio.neves@tecnohall. com.br para informar-se. Valeu! Julio Cezar Neves Analista de Suporte de Sistemas desde 1969 e trabalha com Unix desde 1980, quando participou do desenvolvimento do SOX, um sistema operacional similar ao Unix produzido pela Cobra Computadores. Pode ser contatado no e-mail julio.neves@gmail.com

A idia era pegar tudo at o primeiro o, mas acabou sendo trocado tudo at o ltimo o. Isto poderia ser resolvido de diversas maneiras. Eis algumas:

Nesse exemplo, eu sabia que a sada era composta de brancos e nmeros, por isso montei essa expresso para trocar todos os espaos por nada. Note que antes e aps o asterisco (*) h espaos em branco.

Sobre o autor

junho 2005 www.linuxmagazine.com.br

edio 09

89

Linux User

Papo de Botequim

Papo de Botequim
Curso de Shell Script
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

Parte X

Dave Hamilton - www.sxc.hu

a amigo, te dei a maior moleza na ltima aula n? Um exerciciozinho muito simples , mas nos testes que eu z, e de acordo com o que voc ensinou sobre substituio de parmetros, achei que deveria fazer algumas alteraes nas funes que desenvolvemos para torn-las de uso geral, como voc disse que todas as funes deveriam ser. Quer ver? Claro, n, man, se te pedi para fazer porque estou a m de te ver aprender, mas pera, d um tempo. Chico! Manda dois, um sem colarinho! Vai, mostra a o que voc fez. Bem, alm do que voc pediu, eu reparei que o programa que chamava a funo teria de ter previamente de nidas a linha em que seria mostrada a mensagem e a quantidade de colunas. O que z foi incluir duas linhas nas quais empreguei substituio de parmetros para que, caso uma dessas variveis no fosse informada, ela recebesse um valor atribudo pela prpria funo. A linha de mensagem trs linhas antes do m da tela e o total de colunas obtido pelo comando tput cols. D uma olhada na listagem 1 e veja como cou: Gostei, voc j se antecipou ao que eu ia pedir. S pra gente encerrar esse papo de substituio de parmetros, repare que

a legibilidade do cdigo est horrorvel, mas o desempenho, isto , a velocidade de execuo, est timo. Como funes so coisas muito pessoais, j que cada um usa as suas e quase no h necessidade de manuteno, eu sempre opto pelo desempenho. Hoje vamos sair daquela chatura que foi o nosso ltimo papo e voltar lgica, saindo da decoreba. Mas volto a te lembrar: tudo que eu te mostrei da ltima vez aqui no Boteco do Chico vlido e quebra um galho. Guarde aqueles guardanapos que rabiscamos porque, mais cedo ou mais tarde, eles lhe vo ser muito teis.

O comando eval
Vou te dar um problema que eu duvido que voc resolva:
$ var1=3 $ var2=var1

Te dei essas duas variveis e quero que voc me diga como eu posso, me referindo apenas varivel a var2, listar o valor de var1 (que, no nosso caso, 3). Ah, isso mole, mole! s digitar esse comando aqui:
echo $`echo $var2`

Listagem 1: funo pergunta.func


01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 # A funo recebe 3 parmetros na seguinte ordem: # $1 - Mensagem a ser mostrada na tela # $2 - Valor a ser aceito com resposta padro # $3 - O outro valor aceito # Supondo que $1=Aceita?, $2=s e $3=n, a linha # abaixo colocaria em Msg o valor "Aceita? (S/n)" TotCols=${TotCols:-$(tput cols)} # Se no estava definido, agora est LinhaMesg=${LinhaMesg:-$(($(tput lines)-3))} # Idem Msg="$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)" TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # Para centralizar Msg na linha tput cup $LinhaMesg $Col read -n1 -p "$Msg " SN SN=${SN:-$2} # Se vazia coloca o padro em SN SN=$(echo $SN | tr A-Z a-z) # A sada de SN ser em minsculas tput cup $LinhaMesg $Col; tput el # Apaga Msg da tela

86

julho 2005

edio 10 www.linuxmagazine.com.br

Papo de Botequim

Linux User

Repare que eu coloquei o echo $var2 entre crases (`), porque dessa forma ele ter prioridade de execuo e resultar em var1. E echo $var1 produzir 3 Ah, ? Ento execute para ver se est correto.
$ echo $`echo $var2` $var1

$ var2=ls $ $var2 10porpag1.sh alo2.sh 10porpag3.sh confuso alo1.sh contpal.sh incusu listartista logado logaute.sh mandamsg.func 10porpag2.sh ArqDoDOS.txt1 listamusica

listartista3 monbg.sh

Agora vamos colocar em var2 o seguinte: ls $var1; e em U! Que foi que aconteceu? O meu raciocnio me parecia bastante lgico O seu raciocnio realmente foi lgico, o problema que voc esqueceu de uma das primeiras coisas de que te falei aqui no Boteco e que vou repetir. O Shell usa a seguinte ordem para resolver uma linha de comando: P Resolve os redirecionamentos; P Substitui as variveis pelos seus valores; P Resolve e substitui os meta caracteres; P Passa a linha j toda esmiuada para execuo. Dessa forma, quando o interpretador chegou na fase de resoluo de variveis, que como eu disse anterior execuo, a nica varivel existente era var2 e por isso a tua soluo produziu como sada $var1. O comando echo identicou isso como uma cadeia de caracteres e no como uma varivel. Problemas desse tipo so relativamente freqentes e seriam insolveis caso no existisse a instruo eval, cuja sintaxe eval cmd, onde cmd uma linha de comando qualquer, que voc poderia inclusive executar direto no prompt do terminal. Quando voc pe o eval na frente, no entanto, o que ocorre que o Shell trata cmd como um parmetro do eval e, em seguida, o eval executa a linha recebida, submetendo-a ao Shell. Ou seja, na prtica cmd analisado duas vezes. Dessa forma, se executssemos o comando que voc props colocando o eval na frente, teramos a sada esperada. Veja:
$ eval echo $`echo $var2` 3 var1 vamos colocar l*, vejamos o resultado: $ var2='ls $var1' $ var1='l*' $ $var2 ls: $var1: No such file or directory $ eval $var2 listamusica listartista listartista3 logado logaute.sh

Novamente, no tempo de substituio das variveis, $var1 ainda no havia se apresentado ao Shell para ser resolvida. Assim, s nos resta executar o comando eval para dar as duas passadas necessrias. Uma vez um colega da excelente lista de discusso groups.yahoo.com/ group/shell-script colocou uma dvida: queria fazer um menu que numerasse e listasse todos os arquivos com extenso .sh e, quando o operador escolhesse uma opo, o programa correspondente fosse executado. Veja minha proposta na listagem 2:

Listagem 2: fazmenu.sh
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 #!/bin/bash # # Lista que enumera os programas com extenso .sh no # diretrio corrente e executa o escolhido pelo operador # clear; i=1 printf "%11s\t%s\n\n" Opo Programa CASE='case $opt in' for arq in *.sh do printf "\t%03d\t%s\n" $i $arq CASE="$CASE "$(printf "%03d)\t %s;;" $i $arq) i=$((i+1)) done CASE="$CASE *) . erro;; esac" read -n3 -p "Informe a opo desejada: " opt echo eval "$CASE"

Esse exemplo tambm poderia ter sido feito de outra maneira. D s uma olhada:
$ eval echo \$$var2 3

Na primeira passada a contrabarra (\) seria retirada e $var2 seria resolvido produzindo var1. Na segunda passada teria sobrado echo $var1, que produziria o resultado esperado. Agora vou colocar um comando dentro de var2 e executar:

julho 2005 www.linuxmagazine.com.br

edio 10

87

Linux User

Papo de Botequim

Parece complicado porque usei muitos


printf para formatao da tela, mas na

verdade bastante simples: o primeiro printf foi colocado para imprimir o cabealho e logo em seguida comecei a montar dinamicamente a varivel $CASE, na qual ao nal ser feito um eval para execuo do programa escolhido. Repare no entanto que dentro do loop do for existem dois printf: o primeiro serve para formatar a tela e o segundo para montar o case (se antes do comando read voc colocar uma linha echo "$CASE", ver que o comando case montado dentro da varivel est todo indentado. Frescura, n?:). Na sada do for, foi adicionada uma linha varivel $CASE para, no caso de uma escolha invlida, ser executada uma funo externa para exibir mensagens de erro. Vamos executar o script para ver a sada gerada:
$ fazmenu.sh Opcao 001 002 003 004 005 006 007 008 009 010 011 Programa 10porpag1.sh 10porpag2.sh 10porpag3.sh alo1.sh alo2.sh contpal.sh fazmenu.sh logaute.sh monbg.sh readpipe.sh redirread.sh

dos por) processos em execuo. Vamos, "limpar a rea" ao seu trmino. Se seu de agora em diante, dar uma olhadinha encerramento ocorrer de forma prevista, nos sinais enviados aos processos e mais ou seja, se tiver um trmino normal, frente vamos dar uma passada rpida muito fcil fazer essa limpeza; porm, pelos sinais gerados pelos processos. se o seu programa tiver um m brusco, Para mandar um sinal a um processo, muita coisa ruim pode ocorrer: usamos normalmente o comando kill, P possvel que em um determinado escuja sintaxe : pao de tempo, o seu computador esteja cheio de arquivos de trabalho inteis $ kill -sig PID P Seu processador poder car atolado de processos zombies e defuncts geraOnde PID o identicador do procesdos por processos lhos que perderam so (Process Identication ou Process ID). os pais e esto rfos; Alm do comando kill, algumas seqn- P necessrio liberar sockets abertos para cias de teclas tambm podem gerar sinais. no deixar os clientes congelados; A tabela 1 mostra os sinais mais impor- P Seus bancos de dados podero car tantes para monitorarmos: corrompidos porque sistemas gerenciaAlm desses, existe o famigerado sidores de bancos de dados necessitam nal -9 ou SIGKILL que, para o processo de um tempo para gravar seus buffers que o est recebendo, equivale a meter em disco (commit). o dedo no boto de desligar do compuEnm, existem mil razes para no usar tador o que altamente indesejvel, um kill com o sinal -9 e para monitorar o j que muitos programas necessitam encerramento anormal de programas.

Listagem 3: Nova verso do fazmenu.sh


01 #!/bin/bash 02 # 03 # Lista enumerando os programas com extenso .sh no 04 # diretrio corrente; executa o escolhido pelo operador 05 # 06 clear; i=1 07 printf "%11s\t%s\n\n" Opo Programa 08 CASE='case $opt in' 09 for arq in *.sh 10 do 11 12 13 14 15 done 16 printf "\t%d\t%s\n\n" 999 "Fim do programa" # Linha includa 17 CASE="$CASE 18 19 20 esac" 21 read -n3 -p "Informe a opo desejada: " opt 22 echo 23 eval "$CASE" 999) *) exit;; ./erro;; # Linha alterada printf "\t%03d\t%s\n" $i $arq CASE="$CASE "$(printf "%03d)\t %s;;" $i $arq) i=$((i+1))

Informe a opo desejada:

Seria interessante incluir uma opo para terminar o programa e, para isso, seria necessria a incluso de uma linha aps o loop de montagem da tela e a alterao da linha na qual fazemos a atribuio nal do valor da varivel $CASE. Veja na listagem 3 como ele caria: Existe no Linux uma coisa chamada sinal (signal). Existem diversos sinais que podem ser mandados para (ou gera-

88

julho 2005

edio 10 www.linuxmagazine.com.br

Papo de Botequim

Linux User

O comando trap
Para fazer a monitorao de sinais existe o comando trap, cuja sintaxe pode ser uma das mostradas a seguir:
trap "cmd1; cmd2; cmdn" S1 S2 SN trap 'cmd1; cmd2; cmdn' S1 S2 SN

Caso a transferncia seja interrompida por um kill ou um [CTRL]+[C], certamente deixar lixo no disco. exatamente essa a forma mais comum de uso do comando trap. Como isso trecho de um script devemos, logo no incio dele, digitar o comando:
trap "rm -f /tmp/$$ ; exit" 0 1 2 3 15

Tabela 1: Principais sinais


Cdigo
0 1 Fim normal do programa Quando o programa recebe um kill -HUP SIGINT Interrupo pelo teclado. ([CTRL]+[C]) SIGQUIT Interrupo pelo teclado ([CTRL]+[\]) SIGTERM Quando o programa recebe um kill ou
kill -TERM

EXIT SIGHUP

Nome

Gerado por:

2 3 15

Onde os comandos cmd1, cmd2, cmdn sero executados caso o programa receba os sinais S1, S2 SN. As aspas (") ou as apstrofes (') s so necessrias caso o trap possua mais de um comando cmd associado. Cada uma delas pode ser tambm uma funo interna, uma externa ou outro script. Para entender o uso de aspas (") e apstrofes (') vamos recorrer a um exemplo que trata um fragmento de um script que faz uma transferncia de arquivos via FTP para uma mquina remota ($RemoComp), na qual o usurio $Fulano, sua senha $Segredo e o arquivo a ser enviado $Arq. Suponha ainda que essas quatro variveis foram recebidas por uma rotina anterior de leitura e que esse script seja muito usado por diversas pessoas. Vejamos o trecho de cdigo a seguir:
ftp -ivn $RemoComp << FimFTP >> /tmp/$$ U 2>> /tmp/$$ user $Fulano $Segredo binary get $Arq FimFTP

Repare que tanto as sadas dos dilogos do FTP como os erros encontrados esto sendo redirecionados para /tmp/$$, o que uma construo bastante comum para arquivos temporrios usados em scripts com mais de um usurio, porque $$ a varivel que contm o nmero do processo (PID), que nico. Com esse tipo de construo evita-se que dois ou mais usurios disputem a posse e os direitos sobre um arquivo.

Dessa forma, caso houvesse uma interrupo brusca (sinais 1, 2 , 3 ou 15) antes do programa encerrar (no exit dentro do comando trap), ou um m normal (sinal 0), o arquivo /tmp/$$ seria removido. Caso no houvesse a instruo exit na linha de comando do trap, ao nal da execuo dessa linha o uxo do programa retornaria ao ponto em que estava quando recebeu o sinal que originou a execuo desse trap. Note tambm que o Shell pesquisa a linha de comando uma vez quando o trap interpretado (e por isso que usual coloc-lo no incio do programa) e novamente quando um dos sinais listados recebido. Ento, no ltimo exemplo, o valor de $$ ser substitudo no momento em que o comando trap lido pela primeira vez, j que as aspas (") no protegem o cifro ($) da interpretao do Shell. Se voc quisesse fazer a substituio somente ao receber o sinal, o comando deveria ser colocado entre apstrofes ('). Assim, na primeira interpretao do trap, o Shell no veria o cifro ($), as apstrofes (') seriam removidas e, nalmente, o Shell poderia substituir o valor da varivel. Nesse caso, a linha caria assim:

1 Caso: O comando ftp encontra-se em script1. Nesse caso, o argumento do comando trap deveria vir entre aspas (") porque, caso ocorresse uma interrupo ([CTRL]+[C] ou [CTRL]+[\]) no script2, a linha s seria interpretada nesse momento e o PID do script2 seria diferente do encontrado em /tmp/$$ (no esquea que $$ a varivel que contm o PID do processo ativo); 2 Caso: O comando ftp encontra-se em script2. Nesse caso, o argumento do comando trap deveria estar entre apstrofes ('), pois caso a interrupo se desse durante a execuo de script1, o arquivo no teria sido criado; caso ela ocorresse durante a execuo de script2, o valor de $$ seria o PID desse processo, que coincidiria com o de /tmp/$$. O comando trap, quando executado sem argumentos, lista os sinais que esto sendo monitorados no ambiente, bem como a linha de comando que ser executada quando tais sinais forem recebidos. Se a linha de comandos do trap for nula (vazia), isso signica que os sinais especicados devem ser ignorados quando trap 'rm -f /tmp/$$ ; exit' 0 1 2 3 15 recebidos. Por exemplo, o comando trap "" 2 especica que o sinal de interrupSuponha dois casos: voc tem dois o ([CTRL]+[C]) deve ser ignorado. No scripts que chamaremos de script1, cuja ltimo exemplo, note que o primeiro arprimeira linha ser um trap, e script2, gumento deve ser especicado para que o colocado em execuo por script1. Por sinal seja ignorado e no equivalente a serem dois processos diferentes, tero escrever trap 2, cuja nalidade retornar dois PIDs distintos. o sinal 2 ao seu estado padro.
julho 2005 www.linuxmagazine.com.br edio 10

89

Linux User

Papo de Botequim

Se voc ignorar um sinal, todos os subshells iro ignor-lo. Portanto, se voc especicar qual ao deve ser tomada quando receber um sinal, todos os subshells iro tomar a mesma ao quando receberem esse sinal. Ou seja, os sinais so automaticamente exportados. Para o sinal mostrado (sinal 2), isso signica que os sub-shells sero encerrados. Suponha que voc execute o comando trap "" 2 e ento execute um sub-shell, que tornar a executar outro script como um sub-shell. Se for gerado um sinal de interrupo, este no ter efeito nem sobre o Shell principal nem sobre os sub-shell por ele chamados, j que todos eles ignoraro o sinal. Em korn shell (ksh) no existe a opo -s do comando read para ler uma senha. O que costumamos fazer usar usar o comando stty com a opo -echo, que inibe a escrita na tela at que se encontre um stty echo para restaurar essa escrita. Ento, se estivssemos usando o interpretador ksh, a leitura da senha teria que ser feita da seguinte forma:
echo -n "Senha: " stty -echo read Senha stty echo

Para terminar esse assunto, abra um console grco e escreva no prompt de comando o seguinte:
$ trap "echo Mudou o tamanho da janela" 28

cadeiadeopcoes deve ser abc. Se voc

Em seguida, pegue o mouse e arraste-o de forma a variar o tamanho da janela corrente. Surpreso? o Shell orientado a eventos Mais unzinho, porque no consigo resistir. Escreva isto:
$ trap "echo j era" 17

Em seguida digite:
$ sleep 3 &

Voc acabou de criar um sub-shell que ir dormir durante trs segundos em background. Ao m desse tempo, voc receber a mensagem j era, porque o sinal 17 emitido a cada vez em que um sub-shell termina a sua execuo. Para devolver esses sinais ao seu comportamento padro, digite: trap 17 28. Muito legal esse comando, n? Se voc descobrir algum material bacana sobre uso de sinais, por favor me informe por email, porque muito rara a literatura sobre o assunto.

O problema com esse tipo de construo que, caso o operador no soubesse a senha, ele provavelmente teclaria [CTRL]+[C] ou um [CTRL]+[\] durante a instruo read para descontinuar o programa e, caso agisse dessa forma, o seu terminal estaria sem echo. Para evitar que isso acontea, o melhor a fazer :
echo -n "Senha: " trap "stty echo exit" 2 3 stty -echo read Senha stty echo trap 2 3

Comando getopts
O comando getopts recupera as opes e seus argumentos de uma lista de parmetros de acordo com a sintaxe POSIX.2, isto , letras (ou nmeros) aps um sinal de menos (-) seguidas ou no de um argumento; no caso de somente letras (ou nmeros), elas podem ser agrupadas. Voc deve usar esse comando para "fatiar" opes e argumentos passados para o seu script. A sintaxe getopts cadeiadeopcoes nome. A cadeiadeopcoes deve explicitar uma cadeia de caracteres com todas as opes reconhecidas pelo script; assim, se ele reconhece as opes -a -b e c,

desejar que uma opo seja seguida por um argumento, ponha um sinal de dois pontos (:) depois da letra, como em a:bc. Isso diz ao getopts que a opo -a tem a forma -a argumento. Normalmente um ou mais espaos em branco separam o parmetro da opo; no entanto, getopts tambm manipula parmetros que vm colados opo como em -aargumento. cadeiadeopcoes no pode conter um sinal de interrogao (?). O nome constante da linha de sintaxe acima dene uma varivel que receber, a cada vez que o comando getopts for executado, o prximo dos parmetros posicionais e o colocar na varivel nome. getopts coloca uma interrogao (?) na varivel denida em nome se achar uma opo no denida em cadeiadeopcoes ou se no achar o argumento esperado para uma determinada opo. Como j sabemos, cada opo passada por uma linha de comandos tem um ndice numrico; assim, a primeira opo estar contida em $1, a segunda em $2 e assim por diante. Quando o getopts obtm uma opo, ele armazena o ndice do prximo parmetro a ser processado na varivel OPTIND. Quando uma opo tem um argumento associado (indicado pelo : na cadeiadeopcoes), getopts armazena o argumento na varivel OPTARG. Se uma opo no possuir argumento ou se o argumento esperado no for encontrado, a varivel OPTARG ser "apagada" (com unset). O comando encerra sua execuo quando: P Encontra um parmetro que no comea com um hfen (-). P O parmetro especial -- indica o m das opes. P Quando encontra um erro (por exemplo, uma opo no reconhecida). O exemplo da listagem 4 meramente didtico, servindo para mostrar, em um pequeno fragmento de cdigo, o uso pleno do comando.

90

julho 2005

edio 10 www.linuxmagazine.com.br

Papo de Botequim

Linux User

Para entender melhor, vamos executar o script:


$ getoptst.sh -h -Pimpressora arq1 arq2 getopts fez a variavel OPT_LETRA igual a 'h' OPTARG eh '' getopts fez a variavel OPT_LETRA igual a 'P' OPTARG eh 'impressora' Dispensando os primeiros $OPTIND-1 = 2 argumentos O que sobrou da linha de comandos foi 'arq1 arq2'

OPTARG eh 'impressora' Dispensando os primeiros $OPTIND-1 = 3 argumentos O que sobrou da linha de comandos foi 'arq1 arq2'

Repare, no exemplo a seguir, que se passarmos uma opo invlida a varivel $OPT_LETRA receber um ponto de interrogao (?) e a $OPTARG ser "apagada" (unset).
$ getoptst.sh -f -Pimpressora arq1 arq2 # A opo f no valida ./getoptst.sh: illegal option -- f getopts fez a variavel OPT_LETRA igual a '?' OPTARG eh '' getopts fez a variavel OPT_LETRA igual a 'P' OPTARG eh 'impressora' Dispensando os primeiros $OPTIND-1 = 2 argumentos O que sobrou da linha de comandos foi 'arq1 arq2'

Dessa forma, sem ter muito trabalho, separei todas as opes com seus respectivos argumentos, deixando somente os parmetros que foram passados pelo operador para posterior tratamento. Repare que, se tivssemos escrito a linha de comando com o argumento (impressora) separado da opo (-P), o resultado seria exatamente o mesmo, exceto pelo OPTIND, j que nesse caso ele identica um conjunto de trs opes (ou argumentos) e, no anterior, somente dois. Veja s:

Me diz uma coisa: voc no poderia ter usado um condicional com case para evitar o getopts? Poderia sim, mas para qu? Os comandos esto a para serem usados O exemplo foi didtico, mas imagine um programa que aceitasse muitas opes e cujos parmetros poderiam ou no estar colados s opes, sen$ getoptst.sh -h -P impressora arq1 arq2 do que as opes tambm poderiam ou no estar coladas: ia ser um case getopts fez a variavel OPT_LETRA igual a 'h' infernal! Com getopts, s seguir os passos acima. OPTARG eh '' Vendo dessa forma, acho que voc tem razo. porque eu j estou getopts fez a variavel OPT_LETRA igual a 'P' meio cansado com tanta informao nova na minha Listagem 4: getoptst.sh cabea. Vamos tomar a saideira ou voc ainda quer explicar alguma particularidade do Shell? 01 $ cat getoptst.sh Nem um nem outro, eu tambm j cansei mas 02 #!/bin/sh hoje no vou tomar a saideira porque estou indo 03 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 Soft07 # 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 # uma janela do terminal, no centro dela no aparece 10 # A cadeia 'P:h' diz que a opo -P uma opo complexa dinamicamente, em vdeo reverso, a quantidade de 11 # e requer um argumento e que h uma opo simples que no requer linhas e colunas? Ento! Eu quero que voc repro12 # argumentos. 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 compa17 echo " OPTARG eh '$OPTARG'" nhia para um chope s mandar um email para julio. 18 done neves@gmail.com. Vou aproveitar tambm para mandar 19 used_up=`expr $OPTIND 1` o meu jab: diga para os amigos que quem estiver a 20 echo "Dispensando os primeiros \$OPTIND-1 = $used_up argumentos" m de fazer um curso porreta de programao em 21 shift $used_up 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 www.linuxmagazine.com.br

edio 10

91

Linux User

Papo de Botequim

Papo de botequim
Curso de Shell Script
A conversa est boa, mas uma hora eles tem que sair do bar. Na ltima
por Jlio Cezar Neves

Dave Hamilton - www.sxc.hu

Parte nal

parte do nosso papo, falamos sobre pipes e sincronizao entre processos.

E
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16

a rapaz, tudo bom? Beleza. Voc se lembra de que da ltima vez voc me pediu para fazer um programa que imprimisse dinamicamente, no centro da tela, a quantidade de linhas e colunas de um terminal sempre que o tamanho da janela variasse? Pois , eu at que z, mas mesmo depois de quebrar muito a cabea a aparncia no cou igual.

Listagem 1: tamtela.sh
#!/bin/bash # # Coloca no centro da tela, em video reverso, # a quantidade de colunas e linhas # quando o tamanho da tela eh alterado. # trap Muda 28 # 28 = sinal gerado pela mudanca no tamanho # da tela e Muda eh a funcao que fara isso. Bold=$(tput bold) Rev=$(tput rev) Norm=$(tput sgr0) Muda () { clear # Modo de enfase # Modo de video reverso # Restaura a tela ao padrao default

No estou nem a para a aparncia, o que eu queria que voc exercitasse o que aprendemos. Me d a listagem 1 pra eu ver o que voc fez. Perfeito! Que se dane a aparncia, depois vou te ensinar uns macetes para melhor-la. O que vale que o programa est funcionando e est bem enxuto. Pxa, e eu que perdi o maior tempo tentando descobrir como aumentar a fonte Deixe isso para l! Hoje vamos ver umas coisas bastante interessantes e teis.

Dando nomes aos canos


Um outro tipo de pipe o named pipe, que tambm chamado de FIFO. FIFO um acrnimo de First In First Out que se refere propriedade em que a ordem dos bytes entrando no pipe a mesma que a da sada. O name em named pipe , na verdade, o nome de um arquivo. Os arquivos tipo named pipes so exibidos pelo comando ls como qualquer outro, com poucas diferenas, veja:
$ ls -l pipe1 prw-r-r-1 julio dipao 0 Jan 22 23:11 pipe1|

17 Cols=$(tput cols) 18 Lins=$(tput lines) 19 tput cup $(($Lins / 2)) $(((Cols - 7) / 2)) # Centro da tela 20 echo $Bold$Rev$Cols X $Lins$Norm 21 } 22 23 clear 24 read -n1 -p "Mude o tamanho da tela ou tecle algo para terminar "

o p na coluna mais esquerda indica que fifo1 um named pipe. O resto dos bits de controle de permisses, quem pode ler ou gravar o pipe, funcionam como um arquivo normal. Nos sistemas mais modernos uma barra vertical (|), ou pipe, no m do nome do arquivo outra dica e, nos sistemas LINUX, 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 utilitrio mknod, normalmente situado no diretrio /etc. Nos sistemas mais modernos, a mesma tarefa feita pelo mkfo, que recebe um ou mais nomes como argumento e cria pipes com esses nomes. Por exemplo, para criar um named pipe com o nome pipe1, digite:
$ mkfifo pipe1

Sincronizao de processos
Suponha que voc dispare paralelamente dois programas (processos), chamados programa1 e programa2, cujos diagramas de blocos de suas rotinas so como mostrado na tabela 1. Os dois processos so disparados em paralelo e, no bloco 1 do programa1, as trs classicaes so disparadas da seguinte maneira:
for Arq in BigFile1 BigFile2 BigFile3 do if sort $Arq then Manda=va else Manda=pare break fi done echo $Manda > pipe1 [ $Manda = pare ] && { echo Erro durante a classificao dos arquivos exit 1 }

Como sempre, a melhor forma de mostrar como algo funciona dando exemplos. Suponha que ns tenhamos criado o named pipe mostrado anteriormente. Vamos agora trabalhar com duas sesses ou dois consoles virtuais. Em um deles digite:
$ ls -l > pipe1

e em outro faa:
$ cat < pipe1

Voil! A sada do comando executado no primeiro console foi exibida no segundo. Note que a ordem em que os comandos 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 operacional suspendeu o primeiro processo at que o segundo "abrisse" o pipe. Para que um processo que usa pipe no que em modo de espera, necessrio que em uma ponta do pipe haja um processo "falante" e na outra um "ouvinte". No exemplo anterior, o ls era o "falante" e o cat era o "ouvinte". Um uso muito til dos named pipes permitir que programas sem nenhuma relao possam se comunicar entre si. Os named pipes tambm so usados para sincronizar processos, j que em um determinado ponto voc pode colocar um processo para "ouvir" ou para "falar" em um determinado named pipe e ele da s sair se outro processo "falar" ou "ouvir" aquele pipe. Voc j deve ter notado que essa ferramenta tima para sincronizar processos e fazer bloqueio em arquivos de forma a evitar perda/corrupo de dados devido a atualizaes simultneas (a famosa concorrncia). Vamos ver alguns exemplos para ilustrar estes casos.

Assim sendo, o comando if testa cada classicao que est sendo efetuada. Caso ocorra qualquer problema, as classicaes seguintes sero abortadas, uma mensagem contendo a string pare enviada pelo pipe1 e programa1 descontinuado com cdigo de sada sinalizando um encerramento anormal. Enquanto o programa1 executava o seu primeiro bloco (as classicaes), o programa2 executava o seu bloco 1, processando as suas rotinas de abertura e menu paralelamente ao programa1, ganhando dessa forma um bom tempo. O fragmento de cdigo do programa2 a seguir mostra a transio do seu bloco 1 para o bloco 2:
OK=`cat pipe1` if [ $OK = va ] then Rotina de impresso

Tabela 1
Bloco 1 Bloco 2
Rotina de classicao de trs grandes arquivos Acertos nais e encerramento

Programa1

Rotina de abertura e gerao de menus Impresso dos dados classicados pelo programa 1

Programa2

agosto 2005 www.linuxmagazine.com.br

edio 11

87

Linux User

Papo de Botequim

else exit 1 fi

Aps a execuo de seu primeiro bloco, o programa2 passar a "ouvir" o pipe1, cando parado at que as classicaes do Programa1 terminem, testando a seguir a mensagem passada pelo pipe1 para decidir se os arquivos esto ntegros para serem impressos ou se o programa dever ser descontinuado. Dessa forma possvel disparar programas de forma assncrona e sincroniz-los quando necessrio, ganhando bastante tempo de processamento.

Ento, o que fazer? Para resolver o problema de concorrncia, vamos utilizar um named pipe. Criamos o script na listagem 2 que ser o daemon que receber todos os pedidos para incrementar o contador. Note que ele vai ser usado por qualquer pgina no nosso site que precise de um contador. Como apenas este script altera os arquivos, no existe o problema de concorrncia. Este script ser um daemon, isto , rodar em segundo plano. Quando uma pgina sofrer um acesso, ela escrever a sua URL no pipe. Para testar, execute este comando:
echo "teste_pagina.html" > /tmp/pipe_contador

Bloqueio de arquivos
Suponha que voc tenha escrito um CGI (Common Gateway Interface) em Shell Script para contar quantos hits uma determinada URL recebe e a rotina de contagem est da seguinte maneira:
Hits="$(cat page.hits 2> /dev/null)" || Hits=0 echo $((Hits=Hits++)) > page.hits

Para evitar erros, em cada pgina a que quisermos adicionar o contador acrescentamos a seguinte linha:
<!--#exec cmd="echo $REQUEST_URI > /tmp/pipe_contador"-->

Dessa forma, se a pgina receber dois ou mais acessos simultneos, um ou mais poder ser perdido, bastando que o segundo acesso seja feito aps a leitura do arquivo page.hits e antes da sua gravao, isto , aps o primeiro acesso ter executado a primeira linha do script e antes de executar a segunda.

Note que a varivel $REQUEST_URI contm o nome do arquivo que o browser requisitou. Esse exemplo fruto de uma troca de idias com o amigo e mestre em Shell Thobias Salazar Trevisan, que escreveu o script e colocou-o em seu excelente site (www.thobias.org). Aconselho a todos os que querem aprender Shell a dar uma passada l. Voc pensa que o assunto named pipes est esgotado? Enganou-se. Vou mostrar um uso diferente a partir de agora.

Listagem 2: contahits.sh
01 #!/bin/bash 02 03 PIPE="/tmp/pipe_contador" # arquivo named pipe 04 # dir onde serao colocados os arquivos contadores de cada pagina 05 06 07 08 09 10 11 12 13 14 15 15 17 18 19 20 DIR="/var/www/contador" [ -p "$PIPE" ] || mkfifo "$PIPE" while : do for URL in $(cat < $PIPE) do FILE="$DIR/$(echo $URL | sed 's,.*/,,')" # quando rodar como daemon comente a proxima linha echo "arquivo = $FILE" n="$(cat $FILE 2> /dev/null)" || n=0 echo $((n=n+1)) > "$FILE" done done

Substituio de processos
Vou mostrar que o Shell tambm usa os named pipes de uma maneira bastante singular, que a substituio de processos (process substitution). Uma substituio de processos ocorre quando voc pe um < ou um > grudado na frente do parntese da esquerda. Digitar o comando:
$ cat <(ls -l)

Resultar no comando ls -l executado em um sub-shell, como normal, porm redirecionar a sada para um named pipe temporrio, que o Shell cria, nomeia e depois remove. Ento o cat ter um nome de arquivo vlido para ler (que ser este named pipe e cujo dispositivo lgico associado /dev/fd/63). O resultado a mesma sada que a gerada pelo ls -l, porm dando um ou mais passos que o usual. Pra que simplicar? 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) l-wx 1 jneves jneves 64 Aug 27 12:26 /dev/fd/63 -> pipe:[7050]

#!/bin/bash LIST="" ls | while read FILE do LIST="$FILE $LIST" done echo $LIST # Alterada dentro do subshell # Fim do subshell # Criada no shell principal # Inicio do subshell

Realmente um named pipe. Voc deve estar pensando que isto uma maluquice de nerd, n? Ento suponha que voc tenha dois diretrios, chamados dir e dir.bkp, e deseja saber se os dois so iguais. Basta comparar o contedo dos diretrios com o comando cmp:
$ cmp <(cat dir/*) <(cat dir.bkp/*) || echo backup furado

No incio deste exemplo eu disse que ele era meramente didtico porque existem formas melhores de fazer a mesma tarefa. Veja s estas duas:
$ ls | ln

ou, melhor ainda:


$ 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 serve como guia para outros. Eu quero gerar uma listagem dos meus arquivos, numerando-os, e ao nal mostrar o total de arquivos no diretrio corrente:
while read arq do ((i++)) # assim nao eh necessario inicializar i echo "$i: $arq" done < <(ls) echo "No diretorio corrente (`pwd`) existem $i arquivos" $ sort arq1 > /tmp/sort1 $ sort arq2 > /tmp/sort2 $ comm /tmp/sort1 /tmp/sort2 $ rm -f /tmp/sort1 /tmp/sort2 $ comm <(sort arq1) <(sort arq2) $ cat -n <(ls)

Um ltimo exemplo: voc deseja comparar arq1 e arq2 usando o comando comm, mas esse comando necessita que os arquivos estejam classicados. Ento a melhor forma de proceder :

Essa forma evita que voc faa as seguintes operaes:

T legal, eu sei que existem outras formas de executar a mesma tarefa. Usando o comando while, a forma mais comum de resolver esse problema seria:
ls | while read arq do ((i++)) # assim nao eh necessario inicializar i echo "$i: $arq" done echo "No diretorio corrente (`pwd`) existem $i arquivos"

Ao executar o script, tudo parece estar bem, porm no comando echo aps o done, voc ver que o valor de $i foi perdido. Isso deve-se ao fato desta varivel estar sendo incrementada em um sub-shell criado pelo pipe (|) e que terminou no comando done, levando com ele todas as variveis criadas no seu interior e as alteraes l feitas por variveis criadas externamente. Somente para te mostrar que uma varivel criada fora do sub-shell e alterada em seu interior perde as alteraes feitas quando o sub-shell se encerra, execute o script a seguir:

Pessoal, o nosso papo de botequim chegou ao m. Curti muito e recebi diversos elogios pelo trabalho desenvolvido ao longo de doze meses e, o melhor de tudo, z muitas amizades e tomei muitos chopes de graa com os leitores que encontrei pelos congressos e palestras que ando fazendo pelo nosso querido Brasil. Me despeo de todos mandando um grande abrao aos barbudos e beijos s meninas e agradecendo os mais de 100 emails que recebi, todos elogiosos e devidamente respondidos. sade de todos ns: Tim, Tim. - Chico, fecha a minha conta porque vou pra casa! Julio Cezar Neves Analista de Suporte de Sistemas desde 1969 e trabalha com Unix desde 1980, quando participou do desenvolvimento do SOX, um sistema operacional similar ao Unix produzido pela Cobra Computadores. autor do livro Programao Shell Linux, publicado pela editora Brasport. Pode ser contatado no e-mail julio.neves@gmail.com

Sobre o autor

agosto 2005 www.linuxmagazine.com.br

edio 11

89

Operadores
Operadores Aritmticos Operadores Relacionais

Variveis especiais
Varivel Parmetros Posicionais Sintaxe Descrio

Blocos e agrupamentos
Exemplo

+ * / % ** = += -= *= /= %= ++ --

Adio Subtrao Multiplicao Diviso Mdulo Exponenciao Atribui valor a uma varivel Incrementa a varivel por uma constante Decrementa a varivel por uma constante Multiplica a varivel por uma constante Divide a varivel por uma constante Resto da diviso por uma constante Incrementa em 1 o valor da varivel Decrementa em 1 o valor da varivel

== != > >= < <= << >> & | ^ ~ !

Igual Diferente Maior Maior ou Igual Menor Menor ou Igual Deslocamento esquerda Deslocamento direita E de bit (AND) OU de bit (OR) OU exclusivo de bit (XOR) Negao de bit NO de bit (NOT)

Operadores de Atribuio

Operadores de BIT

$0 $1 ... $9 ${10} ... $# $* $@ $$ $! $_ $?

Parmetro nmero 0 (nome do comando ou funo) Parmetro nmero 1 (da linha de comando ou funo) Parmetro nmero N ... Parmetro nmero 9 (da linha de comando ou funo) Parmetro nmero 10 (da linha de comando ou funo) Parmetro nmero NN ... Nmero total de parmetros da linha de comando ou funo Todos os parmetros, como uma string nica Todos os parmetros, como vrias strings protegidas Nmero PID do processo atual (do prprio script) Nmero PID do ltimo job em segundo plano ltimo argumento do ltimo comando executado Cdigo de retorno do ltimo comando executado

Varivel Miscelnia

"..." '...' $'...' `...` {...} (...) $(...) ((...)) $((...)) [...] [[...]]

Protege uma string, mas reconhece $, \ e ` como especiais "abc" Protege uma string, nenhum caractere especial 'abc' Protege uma string, mas interpreta \n, \t, \a, etc $'abc\n' Executa comandos numa subshell, retornando o resultado `ls` Agrupa comandos em um bloco { ls ; } Executa comandos numa subshell ( ls ) Executa comandos numa subshell, retornando o resultado $( ls ) Testa uma operao aritmtica, retornando 0 ou 1 ((5 > 3)) Retorna o resultado de uma operao aritmtica $((5+3)) Testa uma expresso, retornando 0 ou 1 (alias do comando 'test') [ 5 -gt 3 ] Testa uma expresso, retornando 0 ou 1 (podendo usar && e ||) [[ 5 > 3 ]]

Opes do comando test ou [


Testes em arquivos Testes em variveis

Operadores Lgicos

Operadores de BIT (atribuio)

Expanso de variveis
Sintaxe Expanso Condicional

&& E lgico (AND) || OU lgico (OR)

<<= >>= &= |= ^=

Deslocamento esquerda Deslocamento direita E de bit OU de bit OU exclusivo de bit

${var:-texto} ${var:=texto} ${var:?texto} ${var:+texto}


Sintaxe

Se var no est definida, retorna 'texto' Se var no est definida, defina-a com 'texto' Se var no est definida, retorna o erro 'texto' Se var est definida, retorna 'texto', seno retorna o vazio
Expanso de Strings

Redirecionamento
Operador Ao

< > 2> >> 2>> | 2>&1 >&2 >&2>&3<>arq <<FIM <<-FIM <(cmd) >(cmd)

Redireciona a entrada padro (STDIN) Redireciona a sada padro (STDOUT) Redireciona a sada de erro (STDERR) Redireciona a sada padro, anexando Redireciona a sada de erro, anexando Conecta a sada padro com a entrada padro de outro comando Conecta a sada de erro na sada padro Conecta a sada padro na sada de erro Fecha a sada padro Fecha a sada de erro Conecta o descritor de arquivos 3 ao arquivo 'arq' Alimenta a entrada padro (Here Document) Alimenta a entrada padro, cortando TABs A sada do comando 'cmd' um arquivo: diff <(cmd1) <(cmd2) A entrada do comando 'cmd' um arquivo: tar cf >(bzip2 -c >file.tbz) $dir

${var} ${#var} ${!var} ${!texto*} ${var:N} ${var:N:tam} ${var#texto} ${var##texto} ${var%texto} ${var%%texto} ${var/texto/novo} ${var//texto/novo} ${var/#texto/novo} ${var/%texto/novo}

o mesmo que $var, porm no ambguo Retorna o tamanho da string Executa o contedo de $var (igual 'eval \$$var') Retorna os nomes de variveis comeadas por 'texto' Retorna o texto partir da posio 'N' Retorna 'tam' caracteres partir da posio 'N' Corta 'texto' do incio da string Corta 'texto' do incio da string (* guloso) Corta 'texto' do final da string Corta 'texto' do final da string (* guloso) Substitui 'texto' por 'novo', uma vez Substitui 'texto' por 'novo', sempre Se a string comear com 'texto', substitui 'texto' por 'novo' Se a string terminar com 'texto', substitui 'texto' por 'novo'

-b -c -d -e -f -g -G -k -L -O -p -r -s -S -t -u -w -x -nt -ot -ef

um dispositivo de bloco um dispositivo de caractere um diretrio O arquivo existe um arquivo normal O bit SGID est ativado O grupo do arquivo o do usurio atual O sticky-bit est ativado O arquivo um link simblico O dono do arquivo o usurio atual O arquivo um named pipe O arquivo tem permisso de leitura O tamanho do arquivo maior que zero O arquivo um socket O descritor de arquivos N um terminal O bit SUID est ativado O arquivo tem permisso de escrita O arquivo tem permisso de execuo O arquivo mais recente (NewerThan) O arquivo mais antigo (OlderThan) O arquivo o mesmo (EqualFile)

Comparao Numrica -lt menor que (LessThan) -gt maior que (GreaterThan) -le menor igual (LessEqual) -ge maior igual (GreaterEqual) -eq igual (EQual) -ne diferente (NotEqual) Comparao de Strings = igual != diferente -n no nula -z nula Operadores Lgicos ! NO lgico (NOT) -a E lgico (AND) -o OU lgico (OR)

Canivete Suo do Shell (bash) Aurlio Marinho Jargas | www.aurelio.net | @oreio

Escapes do prompt (PS1)


Escape Lembrete Expande para... Formato

Formatadores do comando date


Descrio Letra Lembrete

Letras do comando ls -l
Tipos de Arquivo (primeiro caractere)

\a \d \e \h \H \j \l \n \r \s \t \T \@ \A \u \v \V \w \W \! \# \$ \nnn \\ \[ \]

Alerta Data Escape Hostname Hostname Jobs Tty Newline Return Shell Time Time At At Usurio Verso Verso Working Dir Working Dir Histrico Nmero ID Octal Backslash Escapes Escapes

Alerta (bipe) Data no formato "Dia-da-semana Ms Dia" (Sat Jan 15) Caractere Esc Nome da mquina sem o domnio (dhcp11) Nome completo da mquina (dhcp11.empresa) Nmero de jobs ativos Nome do terminal corrente (ttyp1) Linha nova Retorno de carro Nome do shell (basename $0) Horrio no formato 24 horas HH:MM:SS Horrio no formato 12 horas HH:MM:SS Horrio no formato 12 horas HH:MM am/pm Horrio no formato 24 horas HH:MM Login do usurio corrente Verso do Bash (2.00) Verso+subverso do Bash (2.00.0) Diretrio corrente, caminho completo ($PWD) Diretrio corrente, somente o ltimo (basename $PWD) Nmero do comando corrente no histrico Nmero do comando corrente Mostra "#" se for root, "$" se for usurio normal Caractere cujo octal nnn Barra invertida \ literal Inicia uma seqncia de escapes (tipo cdigos de cores) Termina uma seqncia de escapes

%a %A %b %B %c %y %Y %m %d %j %H %M %S %s %% %t %n

Nome do dia da semana abreviado (Dom..Sb) Nome do dia da semana (Domingo..Sbado) Nome do ms abreviado (Jan..Dez) Nome do ms (Janeiro..Dezembro) Data completa (Sat Nov 04 12:02:33 EST 1989) Ano (dois dgitos) Ano (quatro dgitos) Ms (01..12) Dia (01..31) Dia do ano (001..366) Horas (00..23) Minutos (00..59) Segundos (00..60) Segundos desde 1 de Janeiro de 1970 Um % literal Um TAB Uma quebra de linha

d l b c s p r w x X s S t T

Directory Link Block Char Socket Pipe Read Write eXecute eXecute Set ID Set ID sTicky sTicky

Arquivo normal Diretrio Link simblico Dispositivo de blocos (HD) Dispositivo de caracteres (modem serial) Socket mapeado em arquivo (comunicao de processos) FIFO ou Named Pipe (comunicao de processos)
Permisses do Arquivo (prximos nove caracteres)

Letra Lembrete

Formatadores do comando printf


Formato Descrio

Escapes do comando echo


Escape Lembrete Descrio

%d %o %x %X %f %e %E %s

Nmero decimal Nmero octal Nmero hexadecimal (a-f) Nmero hexadecimal (A-F) Nmero com ponto flutuante Nmero em notao cientfica (e+1) Nmero em notao cientfica (E+1) String

Permisso desativada Acesso de leitura Acesso de escrita Acesso de execuo (ou acesso ao diretrio) Acesso ao diretrio somente Usurio/grupo para execuo (SUID, SGID) permisso 'x' ativada Usurio/grupo para execuo (SUID, SGID) permisso 'x' desativada Usurios s apagam seus prprios arquivos permisso 'x' ativada Usurios s apagam seus prprios arquivos permisso 'x' desativada

Curingas para nomes de arquivo (glob)


Curinga Casa com... Exemplo

\a \b \c \e \f \n \r \t \v \\ \nnn \xnn

Alerta Backspace EOS Escape Form feed Newline Return Tab Vtab Backslash Octal Hexa

Alerta (bipe) Caractere Backspace Termina a string Caractere Esc Alimentao Linha nova Retorno de carro Tabulao horizontal Tabulao vertical Barra invertida \ literal Caractere cujo octal nnn Caractere cujo hexadecimal nn

* ? [...] [^...] {...}

Qualquer coisa Um caractere qualquer Qualquer um dos caracteres listados Qualquer um caractere, exceto os listados Qualquer um dos textos separados por vrgula

*.txt arquivo-??.zip [Aa]rquivo.txt [^A-Z]*.txt arquivo.{txt,html}

Curingas para o comando case


Curinga Casa com... Exemplo

* ? [...] [^...] ...|...

Qualquer coisa Um caractere qualquer Qualquer um dos caracteres listados Qualquer um caractere, exceto os listados Qualquer um dos textos separados por |

*.txt) echo ;; arquivo-??.zip) echo ;; [0-9]) echo ;; [^0-9]) echo ;; txt|html) echo ;;

Canivete Suo do Shell (bash) Aurlio Marinho Jargas | www.aurelio.net | @oreio

Sinais para usar com trap/kill/killall


# Linux Cygwin SystemV AIX HP-UX Solaris BSD/Mac if

if, for, select, while, until, case


for / select while / until case

Metacaracteres das expresses regulares


Meta Nome Descrio

1 HUP HUP HUP HUP 2 INT INT INT INT 3 QUIT QUIT QUIT QUIT 4 ILL ILL ILL ILL 5 TRAP TRAP TRAP TRAP 6 ABRT ABRT IOT LOST 7 BUS EMT EMT EMT 8 FPE FPE FPE FPE 9 KILL KILL KILL KILL 10 USR1 BUS BUS BUS 11 SEGV SEGV SEGV SEGV 12 USR2 SYS SYS SYS 13 PIPE PIPE PIPE PIPE 14 ALRM ALRM ALRM ALRM 15 TERM TERM TERM TERM 16 URG USR1 URG 17 CHLD STOP USR2 STOP 18 CONT TSTP CHLD TSTP 19 STOP CONT PWR CONT 20 TSTP CHLD WINCH CHLD 21 TTIN TTIN URG TTIN 22 TTOU TTOU IO TTOU 23 URG IO STOP IO 24 XCPU XCPU TSTP XCPU 25 XFSZ XFSZ CONT XFSZ 26 VTALRM VTALRM TTIN 27 PROF PROF TTOU MSG 28 WINCH WINCH VTALRM WINCH 29 IO LOST PROF PWR 30 PWR USR1 XCPU USR1 31 SYS USR2 XFSZ USR2 32 PROF 33 DANGER 34 VTALRM 35 MIGRATE 36 PRE 37 Como obter a listagem: trap -l, kill -l ou killall -l Veja tambm: man 7 signal

HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM USR1 USR2 CHLD PWR VTALRM PROF IO WINCH STOP TSTP CONT TTIN TTOU URG LOST -

HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM USR1 USR2 CHLD PWR WINCH URG IO STOP TSTP CONT TTIN TTOU VTALRM PROF XCPU XFSZ WAITING LWP FREEZE THAW CANCEL LOST

HUP if COMANDO for VAR in LISTA while COMANDO case $VAR in . Ponto Curinga de um caractere INT then do do txt1) ... ;; [] Lista Casa qualquer um dos caracteres listados QUIT ... ... ... txt2) ... ;; [^] Lista negada Casa qualquer caractere, exceto os listados ILL elif COMANDO done done txtN) ... ;; ? Opcional A entidade anterior pode aparecer ou no (opcional) TRAP then *) ... ;; * Asterisco A entidade anterior pode aparecer em qualquer quantidade ABRT ... esac + Mais A entidade anterior deve aparecer no mnimo uma vez EMT else ou: {,} Chaves A entidade anterior deve aparecer na quantidade indicada FPE ... for ((exp1;exp2;exp3)) ^ Circunflexo Casa o comeo da linha KILL fi $ Cifro Casa o fim da linha BUS \b Borda Limita uma palavra (letras, nmeros e sublinhado) Cdigos de retorno de comandos SEGV \ Escape Escapa um meta, tirando seu poder SYS | Ou Indica alternativas (usar com o grupo) Cdigo Significado Exemplo PIPE () Grupo Agrupa partes da expresso, quantificvel e multinvel 0 Nenhum erro, execuo terminou OK echo ALRM \1 Retrovisor Recupera o contedo do grupo 1 1 A maioria dos erros comuns na execuo echo $((1/0)) TERM \2 Retrovisor Recupera o contedo do grupo 2 (segue at o \9) 2 Erro de uso em algum 'builtin' do Shell URG .* Curinga Casa qualquer coisa, o tudo e o nada 126 Comando no executvel (sem permisso) touch a ; ./a STOP ?? Opcional NG Idem ao opcional comum, mas casa o mnimo possvel 127 Comando no encontrado ("command not found") echooo TSTP *? Asterisco NG Idem ao asterisco comum, mas casa o mnimo possvel 128 O parmetro para o 'exit' no um decimal exit 1.0 CONT +? Mais NG Idem ao mais comum, mas casa o mnimo possvel 128+n 128 + cdigo do sinal que o matou kill -9 $PPID #exit 137 CHLD {}? Chaves NG Idem s chaves comuns, mas casa o mnimo possvel 130 O programa interrompido com o Ctrl+C (128 + 2) TTIN 255 Parmetro para o 'exit' no est entre 0 e 255 exit -1 Metacaracteres nos aplicativos TTOU IO Programa Opcional Mais Chaves Borda Ou Grupo Cdigos de cores (ANSI) XCPU awk ? + | () Cor Letra Fundo Atributo Valor Exemplos: ESC [ <N>;<N> m XFSZ ed \? \+ \{,\} \b \| \(\) Preto 30 40 Reset 0 ESC[m texto normal (desliga cores) VTALRM Vermelho 31 egrep ? + {,} \b | () 41 Negrito 1 ESC[1m negrito PROF emacs ? + \b \| \(\) Verde 32 42 Sublinhado 4 ESC[33;1m amarelo WINCH expect ? + | () Amarelo 33 43 Piscando 5 ESC[44;37m fundo azul, letra cinza INFO find ? + \b \| \(\) Azul 34 44 Reverso 7 ESC[31;5m vermelho piscando USR1 gawk ? + {,} \<\> | () Rosa 35 45 Na linha de comando: USR2 grep \? \+ \{,\} \b \| \(\) Ciano 36 46 echo -e '\e[33;1m amarelo \e[m' mawk ? + | () Branco 37 47 echo -e '\033[33;1m amarelo \033[m' perl ? + {,} \b | () php ? + {,} \b | () python ? + {,} \b | () sed \? \+ \{,\} \<\> \| \(\) vim \= \+ \{,} \<\> \| \(\)

Canivete Suo do Shell (bash) Aurlio Marinho Jargas | www.aurelio.net | @oreio

Cdigos prontos
Condicionais com o IF

if [ -f "$arquivo" ]; then echo 'Arquivo encontrado'; fi if [ ! -d "$dir" ]; then echo 'Diretrio no encontrado'; fi if [ $i -gt 5 ]; then echo 'Maior que 5'; else echo 'Menor que 5'; fi if [ $i -ge 5 -a $i -le 10 ]; then echo 'Entre 5 e 10, incluindo'; fi if [ $i -eq 5 ]; then echo '=5'; elif [ $i -gt 5 ]; then echo '>5'; else echo '<5'; fi if [ "$USER" = 'root' ]; then echo 'Oi root'; fi if grep -qs 'root' /etc/passwd; then echo 'Usurio encontrado'; fi
Condicionais com o E (&&) e OU (||)

[ -f "$arquivo" ] && echo 'Arquivo encontrado' [ -d "$dir" ] || echo 'Diretrio no encontrado' grep -qs 'root' /etc/passwd && echo 'Usurio encontrado' cd "$dir" && rm "$arquivo" && touch "$arquivo" && echo 'feito!' [ "$1" ] && param=$1 || param='valor padro' [ "$1" ] && param=${1:-valor padro} [ "$1" ] || { echo "Uso: $0 parmetro" ; exit 1 ; }
Adicionar 1 varivel $i

(echo 50; sleep 2; echo 100) | dialog --gauge 'abc' 8 40 0 dialog --infobox 'abc' 0 0 dialog --inputbox 'abc' 0 0 dialog --passwordbox 'abc' 0 0 dialog --menu 'abc' 0 0 0 item1 'desc1' item2 'desc2' dialog --msgbox 'abc' 8 40 dialog --radiolist 'abc' 0 0 0 item1 'desc1' on item2 'desc2' off dialog --tailbox /tmp/arquivo.txt 0 0 dialog --textbox /tmp/arquivo.txt 0 0 dialog --timebox 'abc' 0 0 23 59 00 dialog --yesno 'abc' 0 0 Dica1: dialog ... && echo 'Apertou OK/Yes' || echo 'Apertou Cancel/No' Dica2: resposta=$(dialog --stdout --TIPODACAIXA 'abc' ...)

Atalhos da linha de comando (set -o emacs)


Atalho Descrio Tecla Similar

i=$(expr $i + 1) i=$((i+1)) let i=i+1 let i+=1 let i++


Loop de 1 10

for i in 1 2 3 4 5 6 7 8 9 10; do echo $i; done for i in $(seq 10); do echo $i; done for ((i=1;i<=10;i++)); do echo $i; done i=1 ; while [ $i -le 10 ]; do echo $i ; i=$((i+1)) ; done i=1 ; until [ $i -gt 10 ]; do echo $i ; i=$((i+1)) ; done
Loop nas linhas de um arquivo ou sada de comando

cat /etc/passwd | while read LINHA; do echo "$LINHA"; done grep 'root' /etc/passwd | while read LINHA; do echo "$LINHA"; done while read LINHA; do echo "$LINHA"; done < /etc/passwd while read LINHA; do echo "$LINHA"; done < <(grep 'root' /etc/passwd)
Curingas nos itens do comando case

case "$dir" in /home/*) echo 'dir dentro do /home';; esac case "$user" in root|joao|maria) echo "Oi $user";; *) echo "No te conheo";; esac case "$var" in ?) echo '1 letra';; ??) echo '2 letras';; ??*) echo 'mais de 2';; esac case "$i" in [0-9]) echo '1 dgito';; [0-9][0-9]) echo '2 dgitos';; esac
Caixas do Dialog

Ctrl+A Ctrl+B Ctrl+C Ctrl+D Ctrl+E Ctrl+F Ctrl+H Ctrl+I Ctrl+J Ctrl+K Ctrl+L Ctrl+N Ctrl+P Ctrl+Q Ctrl+R Ctrl+S Ctrl+T Ctrl+U Ctrl+V Ctrl+W Ctrl+X Ctrl+Y

Move o cursor para o incio da linha Move o cursor uma posio esquerda Envia sinal EOF() para o sistema Apaga um caractere direita Move o cursor para o fim da linha Move o cursor uma posio direita Apaga um caractere esquerda Completa arquivos e comandos Quebra a linha Recorta do cursor at o fim da linha Limpa a tela (igual ao comando clear) Prximo comando Comando anterior Destrava a shell (veja Ctrl+S) Procura no histrico de comandos Trava a shell (veja Ctrl+Q) Troca dois caracteres de lugar Recorta a linha inteira Insere caractere literal Recorta a palavra esquerda Move o cursor para o incio/fim da linha (2x) Cola o trecho recortado

Home <Delete End -> Backspace TAB Enter

echo find fmt grep head od paste printf rev sed seq sort tac tail tee tr uniq wc xargs

Mostra texto Encontra arquivos Formata pargrafo Encontra texto Mostra Incio Mostra Caracteres Paraleliza arquivos Mostra texto Inverte texto Edita texto Conta Nmeros Ordena texto Inverte arquivo Mostra Final Arquiva fluxo Transforma texto Remove duplicatas Conta Letras Gerencia argumentos

-e, -n -name, -iname, -type f, -exec, -or -w, -u -i, -v, -r, -qs, -n, -l, -w -x, -A -B -C -n, -c -a, -c, -o, -x -d, -s nenhuma nenhuma -n, -f, s/isso/aquilo/, p, d, q, N -s, -f -n, -f, -r, -k -t, -o nenhuma -n, -c, -f -a -d, -s, A-Z a-z -i, -d, -u -c, -w, -l, -L -n, -i

Caracteres ASCII imprimveis (ISO-8859-1)


32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 ! " # $ % & ' ( ) * + , . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 161 ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255

Home/End

Caixa de ferramentas do shelleiro


Comando Funo Opes teis

dialog --calendar 'abc' 0 0 31 12 1999 dialog --checklist 'abc' 0 0 0 item1 'desc1' on item2 'desc2' off dialog --fselect /tmp 0 0

cat cut date diff

Mostra arquivo Extrai campo Mostra data Compara arquivos

-n, -s -d -f, -c -d, +'...' -u, -Nr, -i, -w

Canivete Suo do Shell (bash) Aurlio Marinho Jargas | www.aurelio.net | @oreio