Curso de Shell Script

Embed Size (px)

Citation preview

LINUX USER

Papo de Botequim

Curso de Shell Script

Papo de BotequimVoc 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 NEVESporque, 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 ShellBom 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?.

D

O ambiente LinuxPara 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 ShellBourne 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 ShellO 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 VariveisNeste 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 MetaCaracteresSe 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.

AtribuioSe 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 comandoNeste 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 kernelCompletadas 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 RedirecionamentosAps 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

ComandoQuando 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 AtribuioJamais faa:

$ valor = 1000 bash: valor: not foundNeste 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 RosetaPara 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 PadroPara 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 redirecionamentoA 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 PadroAssim 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 PerigosoDesta 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 [email protected] < U arquivocommailparaochefe

Etiquetas ErradasUm 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 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 especiaisVarivel 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 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 IIIaron! 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

G

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 cutA 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

86Outubro 2004www.linuxmagazine.com.br

Papo de Botequim LINUX USERMas 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 trSignicado 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 2Seqncia \t \n \v \f \r \\ Signicado Tabulao Nova linha Tabulao Vertical Nova Pgina Incio da linha 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

88Outubro 2004www.linuxmagazine.com.br

Papo de Botequim LINUX USERLembra 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

E

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

TestesTodos 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

84edio 04www.linuxmagazine.com.br

Papo de Botequim LINUX USER

Tabela 2 Opes do test para cadeias de caracteresOpo -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 nmerosOpo 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 4Operador 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 Botequimsobre 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 VerdadeCombinao 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

86edio 04www.linuxmagazine.com.br

Papo de Botequim LINUX USER

Tabela 6Caractere * 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. [email protected]

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 ... .

88edio 04www.linuxmagazine.com.br

SOBRE O AUTOR

Papo de Botequim LINUX USER

Curso de Shell Script

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

so o tema do ms em mais uma lio de nosso curso de Shell Script. Garom, salta uma boa redondinha, que t a m de refrescar o pensamento! POR JULIO CEZAR NEVES

F

ala cara! E as idias esto em 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 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 Botequimque 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 testefor2for 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 -hValor Hexadecimal 09 20 0a Signicado

O ltimo 0a foi proveniente do 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 como um cifro ($) e a opo -t representa o 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

90edio 05www.linuxmagazine.com.br

Papo de Botequim LINUX USERalbum 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 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 BotequimDa 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 relapso.log

der um simples . 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

88edio 06www.linuxmagazine.com.br

Papo de Botequim LINUX USEREm 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 [email protected] mole! Qualquer dvida s passar

90edio 06www.linuxmagazine.com.br

SOBRE O AUTOR

Linux User

Papo de botequim

Papo de BotequimCurso de Shell ScriptDave 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!

C

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 tputO 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.sh01 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 " 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 tputParmetrocup 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 telaBem, 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.sh01 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="