Anda di halaman 1dari 88

Papo de Botequim

Shell Script

Papo de Botequim - Parte I


Dilogo entreouvido entre um Linuxer e em empurrador de mouse: O Ambiente Linux O Ambiente Shell Uma Rapidinha nos Principais Sabores de Shell Bourne Shell (sh) Korn Shell (ksh) Boune Again Shell (bash) C Shell (csh) Explicando o funcionamento do Shell Exame da Linha de Comandos Atribuio Comando Resoluo de Redirecionamentos Substituio de Variveis Substituio de Meta Caracteres Passa Linha de Comando para o kernel Decifrando a Pedra da Roseta Caracteres para remoo do significado Apstrofo ou plic (') Contrabarra ou Barra Invertida (\) Aspas (") Caracteres de redirecionamento Redirecionamento da Sada Padro Redirecionamento da Sada de Erro Padro Redirecionamento da Entrada Padro Here Document Redirecionamento de Comandos Caracteres de Ambiente

Dilogo entreouvido entre um Linuxer e em empurrador de mouse:


- Quem o Bash? - O Bash o filho mais novo 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 se decidiu a usar aquele sistema operacional que voc tem que dar dez boots por dia e no tem domnio nenhum sobre o que esta acontecendo no seu computador. Mas deixa isso pr l, vou te explicar o que Shell e os componentes de sua famlia e ao final da explanao 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 abaixo:

Neste grfico d para ver que a camada de hardware a mais profunda e formada pelos componentes fsicos do seu computador. Envolvendo esta, vem a camada do kernel que o cerne do Linux, seu ncleo, e quem bota o hardware para funcionar, fazendo seu gerenciamento e controle. Os programas e comandos que envolvem o kernel, dele se utilizam para realizar as tarefas aplicativas para

que foram desenvolvidos. Fechando tudo isso vem o Shell que leva este nome 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 esquecer disto e para permitir o acesso de determinados usurios e barrar a entrada de outros, existe um arquivo chamado /etc/passwd que alm fornecer dados para esta funo de "leode-chcara" do Linux, tambm prov informaes para o login daqueles que passaram por esta primeira barreira. O ltimo campo de seus registros informa ao sistema qual Shell a pessoa vai receber ao se "logar" (ARGH!!!). Quando eu disse que o ltimo campo do /etc/passwd informa ao sistema qual o Shell que o usurio vai receber ao se "logar", para ser interpretado ao p-da-letra, isto , se neste campo do seu registro estiver prog, a pessoa ao acessar o sistema receber a tela de execuo do programa prog e ao terminar a sua execuo ganhar imediatamente um logout. Imagine o quanto se pode incrementar a segurana com este simples artifcio. Lembra que eu te falei de Shell, famlia, irmo? Pois , vamos comear a entender isto: o Shell, que que se vale da imagem de uma concha envolvendo o sistema operacional propriamente dito, o nome genrico para tratar os filhos desta idia que, ao longo dos anos de existncia do sistema operacional Unix foram aparecendo. Atualmente existem diversos sabores de Shell, dentre estes eu destaco o sh (Bourne Shell), o ksh (Korn Shell), bash (Bourne Again Shell) e o csh (C Shell).

Uma Rapidinha nos Principais Sabores de Shell


Bourne Shell (sh)
Desenvolvido por Stephen Bourne da Bell Labs (da AT&T onde tambm foi desenvolvido o Unix), este foi durante muitos anos o Shell default do sistema operacional Unix. tambm chamado de Standard Shell por ter sido durante vrios anos o nico e at hoje o mais utilizado at porque ele foi portado para todos os ambientes Unix e distros Linux.

Korn Shell (ksh)


Desenvolvido por David Korn, tambm da Bell Labs, um superset 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)


Este o Shell mais moderno e cujo nmero de adeptos mais cresce em todo o mundo, seja por ser o Shell default do Linux, seu sistema operacional hospedeiro, seja por sua grande diversidade de comandos, que incorpora inclusive diversos instrues caractersticas do C Shell.

C Shell (csh)
Desenvolvido por Bill Joy da Berkley University o Shell mais utilizado em ambientes *BSD e Xenix. 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 contigo somente sobre os trs primeiros, tratando-os genericamente por Shell e assinalando as especificidades de cada um que porventura hajam.

Explicando o funcionamento do Shell


O Shell o primeiro programa que voc ganha ao se "logar" no Linux. ele que vai resolver um monte de coisas de forma a no onerar o kernel com tarefas repetitivas, aliviando-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 que so teclados e examina as suas sintaxes, passando-os esmiuados para execuo. - pa! Esse negcio de interpreta comando no tem nada a haver com interpretador no, n? - Tem sim, na verdade o Shell um interpretador (ou ser intrprete) que traz consigo uma poderosa linguagem com comandos de alto nvel, que permite construo de loops (laos), de tomadas de deciso e de armazenamento de valores em variveis, como vou te mostrar. Vou te explicar as principais tarefas que o Shell cumpre, na sua ordem de execuo. Preste ateno nesta ordem porque ela

fundamental para o entendimento do resto do nosso bate papo.

Exame da Linha de Comandos


Neste exame o Shell identifica os caracteres especiais (reservados) que tm significado para interpretao da linha, logo aps verifica se a linha passada uma atribuio ou um comando. Atribuio Se o Shell encontra dois campos separados por um sinal de igual (=) sem espaos em branco entre eles, identifica esta seqncia como uma atribuio. Exemplos $ ls linux linux Neste exemplo o Shell identificou o ls como um programa e o linux como um parmetro passado para o programa ls. $ valor=1000 Neste caso, por no haver espaos em branco (j d para notar que o branco um dos caracteres reservados) o Shell identificou uma atribuio e colocou 1000 na varivel valor. Jamais Faa: $ valor = 1000 bash: valor: not found Neste caso, o Bash achou a palavra valor isolada por brancos e julgou que voc estivesse mandando executar um programa chamado valor, para o qual estaria passando dois parmetros: = e 1000. Comando Quando uma linha digitada no prompt do Linux, ela dividida em pedaos separados por espao em branco: o primeiro pedao o nome do programa que ter sua existncia pesquisada; identifica em seguida, nesta ordem, opes/parmetros, redirecionamentos e variveis. Quando o programa identificado existe, o Shell verifica as permisses dos arquivos envolvidos (inclusive o prprio programa), dando um erro caso voc no esteja credenciado a executar esta tarefa.
Resoluo de Redirecionamentos

Aps identificar os componentes da linha que voc teclou, o Shell parte para a resoluo de redirecionamentos. O Shell tem incorporado ao seu elenco de vantagens o que chamamos de redirecionamento, que pode ser de entrada (stdin), de sada (stdout) ou dos erros (stderr), conforme vou te explicar a seguir.
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.
Substituio de Meta Caracteres

Se algum metacaractere (*, ? ou []) foi encontrado na linha de comando, neste ponto ele ser substitudo por seus possveis valores. Supondo que o nico arquivo no seu diretrio corrente comeado pela letra n seja um diretrio chamado nomegrandeprachuchu, se voc fizer: $ cd n* Como at aqui quem esta trabalhando a sua linha o Shell e o comando (programa) cd ainda no foi executado, o Shell transforma o n* em nomegrandeprachuchu e o comando cd ser executado com sucesso.
Passa Linha de Comando para o kernel

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

Decifrando a Pedra da Roseta


Para tirar aquela sensao que voc tem quando v um script Shell, que mais parece uma sopa de letrinhas ou um hierglifo vou lhe

mostrar os principais caracteres especiais para que voc saia por ai como o Jean-Franois Champollion decifrando a Pedra da Roseta (d uma "googlada" para descobrir quem este cara, acho que vale a pena).

Caracteres para remoo do significado


isso mesmo, quando no desejamos que o Shell interprete um caractere especial, devemos "escond-lo" dele. Isso pode ser feito de trs formas distintas: Apstrofo ou plic (') Quando o Shell v uma cadeia de caracteres entre apstrofos ('), ele tira os apstrofos da cadeia e no interpreta seu contedo. $ ls linux* linuxmagazine $ ls 'linux*' bash: linux* no such file or directory 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 linux*. 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) que alguns sabores de Unix permitem - e deseja remov-lo. Se voc fizesse: $ rm * Voc estaria fazendo a maior encrenca, pois o rm removeria todos os arquivos do diretrio corrente. A melhor forma de fazer o pretendido : $ rm \* Desta forma, o Shell no interpretaria o asterisco, e em conseqncia no faria a sua expanso. Faa a seguinte experincia cientfica: $ cd /etc $ echo '*' $ echo \* $ echo * Viu a diferena? Ento no precisa explicar mais. Aspas (") Exatamente igual 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 o significado de cada um.

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 default o teclado do terminal. Analogamente, a sada do comando chamada Sada Padro ou stdout e seu default a tela do terminal. Para a tela tambm so enviadas por default as mensagens de erro oriundas do comando que neste caso a chamada Sada de Erro Padro ou stderr. Veremos agora como alterar este estado de coisas. Vamos fazer um programa gago. Para isto faa: $ cat O cat uma instruo que lista o contedo do arquivo especificado para a Sada Padro (stdout). Caso a entrada no seja definida, ele espera os dados da stdin. Ora como eu no especifiquei a entrada, ele est esperando-a pelo teclado (Entrada Padro) e como tambm no citei a sada, o que eu teclar ir para a tela (Sada Padro) fazendo desta forma, como eu havia proposto um programa gago. Experimente! Redirecionamento da Sada Padro Para especificarmos a sada de um programa usamos o > (maior que) ou o >> (maior, maior) seguido do nome do arquivo para o qual se deseja mandar a sada. Vamos transformar o programa gago em um editor de textos (que pretenso heim!).

$ cat > Arq 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 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 Como j havia lhe 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 "esvazia" este arquivo e depois manda o comando para execuo, desta forma voc acabou de perder o contedo do seu querido arquivo. Com isso d para notar que o >> (maior maior) serve para inserir texto no final do arquivo. Redirecionamento da Sada de Erro Padro Assim como o default do Shell receber os dados do teclado e mandar as sadas para a tela, os erros tambm sero enviados para a tela se voc no especificar para onde devero 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. 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. Isso importante! Suponha 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$$. Para no ficar sujeira no seu disco, ao final do script voc colocaria uma linha: $ rm /tmp/seraqueexiste$$ Caso o arquivo no existisse seria enviado para a tela uma mensagem de erro. Para que isso no acontea deve-se fazer: $ rm /tmp/seraqueexiste$$ 2> /dev/null Sobre o exemplo que acabamos de ver tenho duas dicas a dar: Dica # 1 O $$ contm o PID, isto , o nmero do seu processo. Como o Linux multiusurio, bom anexar sempre o $$ ao nome dos dos arquivos que sero usados por vrias pessoas para no haver problema de propriedade, isto , caso voc batizasse o seu arquivo simplesmente como seraqueexiste, o primeiro que o usasse (criando-o ento) seria o seu dono e todos os outros ganhariam um erro quando tentassem gravar algo nele. 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 or directory $ ls naoexiste 2> arquivodeerros $ $ cat arquivodeerros bash: naoexiste no such file or directory Neste exemplo, vimos que quando fizemos um ls em naoexiste, ganhamos uma mensagem de erro. Aps, redirecionarmos a Sada de Erro Padro para arquivodeerros e executarmos 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. Faa este teste ai. Dica # 2 - Quem esse tal de /dev/null? - Em Unix existe um arquivo fantasma. Chama-se /dev/null. Tudo que mandado 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. interessante notar que estes caracteres de redirecionamento so cumulativos, isto , se no exemplo anterior fizssemos: $ ls naoexiste 2>> arquivodeerros a mensagem de erro oriunda do ls seria anexada ao final de arquivodeerros. Redirecionamento da Entrada Padro Para fazermos o redirecionamento da Entrada Padro usamos o < (menor que).

- E pr que serve isso? - voc vai me perguntar. - Deixa eu te dar um exemplo que voc vai entender rapidinho. Suponha que voc queira mandar um mail para o seu chefe. Para o chefe ns caprichamos, n? ento ao invs de sair redigindo o mail direto no prompt da tela de forma a tornar impossvel a correo de uma frase anterior onde, sem querer, 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 < arquivocommailparaochefe O teu chefe ento receber o contedo do arquivocommailparaochefe. Here Document Um outro tipo de redirecionamento muito louco que o Shell te permite o chamado here document. Ele representado por << (menor menor) 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 $Usurio $Senha binary get arquivoremoto fimftp Neste pedacinho de programa temos um monte de detalhes interessantes: 1. As opes que usei para o ftp (-ivn) servem para ele ir listando tudo que est acontecendo (v de verbose), para no perguntar se voc tem certeza de que deseja transmitir cada arquivo (i de interactive), e finalmente a opo n serve para dizer ao ftp para ele no solicitar o usurio e sua senha, pois esses sero informados pela instruo especfica (user); 2. Quando eu usei o << fimftp, estava dizendo o seguinte para o intrprete: - Olhe aqui Shell, no se meta em nada a partir daqui at encontrar o label fimftp. Voc no entenderia nada, j que so instrues especficas do comando ftp e voc no entende nada de ftp. Se fosse s isso seria simples, mas pelo prprio exemplo d para ver que existem duas variveis ($Usurio e $Senha), que o Shell vai resolver antes do redirecionamento. Mas a grande vantagem desse tipo de construo que ela permite que comandos tambm sejam interpretados dentro do escopo do here document, o que tambm contraria o que acabei de dizer. Logo a seguir explico como esse negcio funciona. Agora ainda no d, est faltando ferramenta. 3. 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 esse fragmento de cdigo e colocados respectivamente nas duas variveis: $Usurio e $Senha. 4. 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 interpretado para saber se est em ASCII, EBCDIC, ... 5. O get arquivoremoto diz ao ftp para pegar esse arquivo em hostremoto e traz-lo para o nosso host local. Se fosse para mandar o arquivo, usaramos o comando put. Um erro muito freqente 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. - Est bem, est bem! Eu sei que dei uma viajada e entrei pelos comandos do ftp, fugindo ao nosso assunto que Shell, mas como sempre bom aprender e raro as pessoas estarem disponveis para ensinar... Redirecionamento de Comandos Os redirecionamentos que falamos at aqui 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 quebra os maiores galhos. Seu nome pipe (que em ingls significa tubo, j que ele encana a sada de um comando para a entrada de outro) e sua representao uma barra vertical (|). $ ls | wc -l 21 O comando ls passou a lista de arquivos para o comando wc, que quando est com a opo l conta a quantidade de linha que recebeu. Desta forma, podemos afirmar categoricamente que no meu diretrio existiam 21 arquivos. $ cat /etc/passwd |sort | lp Esta linha de comandos manda a listagem do arquivo /etc/passwd para a entrada do comando sort. Este a classifica e manda-a para o lp que o gerenciador do spool de impresso.

Caracteres de Ambiente
Quando quer priorizar uma expresso voc coloca-a 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 de uso das crases para voc entender melhor. Eu quero saber quantos usurios esto "logados" no computador que eu administro. Eu posso fazer: $ who | wc -l 8 O comando who passa a lista de usurios conectados para o comando wc l que conta quantas linhas recebeu e lista a resposta na tela. Pois bem, mas ao invs de ter um oito solto na tela, o que eu quero que ele esteja no meio de uma frase. Ora para mandar frases para a tela eu uso o comando echo, ento vamos ver como que fica: $ echo "Existem who | wc -l usurios conectados" Existem who | wc -l usurios conectados 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 esta segunda parte do comando com o uso de crases, fazendo assim: $ echo "Existem `who | wc -l` usurios conectados" Existem 8 usurios conectados Para eliminar esse monte de brancos antes do 8 que o wc -l produziu, basta tirar as aspas. Assim: $ echo Existem `who | wc -l` usurios conectados Existem 8 usurios conectados Como eu disse antes, as aspas protegem tudo que esta dentro dos seus limites, da interpretao do Shell. 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. Antes de falar sobre o uso dos parnteses deixa eu mandar uma rapidinha sobre o uso de ponto-e-vrgula (;). Quando estiver no Shell, voc deve sempre dar um comando em cada linha. Para agrupar comandos em uma mesma linha teremos que separ-los por ponto-evrgula. Ento: $ pwd ; cd /etc; pwd; cd -; pwd /home/meudir /etc/ /home/meudir 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-evrgula (;) de todas as formas possveis para mostrar que no importa se existe espaos em branco antes ou aps este caractere. Finalmente vamos ver o caso dos parnteses. Veja s o caso a seguir, bem parecido com o exemplo anterior: $ (pwd ; cd /etc ; pwd;) /home/meudir /etc/ $ pwd /home/meudir - 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 /home/meudir, como se eu nunca houvesse sado de l! - Ih! Ser que tem coisa de mgico a? - T me estranhando, rapaz? No nada disso! O interessante do uso de parnteses que ele invoca um novo Shell para executar os comandos que esto no seu interior. Desta forma, realmente fomos para o diretrio /etc, porm quando todos os comandos dentro dos parnteses foram executados, o novo Shell que estava no diretrio /etc morreu e voltamos ao Shell anterior cujo diretrio corrente era /home/meudir. Faa outros testes usando cd, e ls para voc firmar o conceito. Agora que j conhecemos estes conceitos veja s este exemplo a seguir: $ mail suporte << FIM > Ola suporte, hoje as date "+%H:%M" > ocorreu novamente aquele problema > que eu havia reportado por > telefone. Conforme seu pedido > ai vai uma listagem dos arquivos > do diretorio: > ls -l > Abracos a todos. > FIM Finalmente agora temos conhecimento para mostrar o que havamos conversado sobre here document. Os comandos entre crases (`)

sero priorizados e portanto o Shell os executar antes da instruo mail. Quando o suporte receber o e-mail, ver que os comandos date e ls foram executados imediatamente antes do comando mail, recebendo ento uma fotografia do ambiente no momento em que a correspondncia foi enviada. O prompt primrio default do Shell, como vimos, o cifro ($), porm o Shell usa o conceito de prompt secundrio, ou de continuao de comando, que enviado para a tela quando h uma quebra de linha e a instruo no terminou. Esse prompt, representado por um sinal de maior (>), que vemos precedendo a partir da 2 linha do exemplo. Para finalizar e bagunar tudo, devo dizer que existe uma construo mais moderna que vem sendo utilizada como forma de priorizao de execuo de comandos, tal qual as crases (`). So as construes do tipo $(cmd), onde cmd um (ou vrios) comando que ser(o) executado(s) com prioridade em seu contexto. Assim sendo, o uso de crases (`) ou construes do tipo $(cmd) servem para o mesmo fim, porm para quem trabalha com sistemas operacionais de diversos fornecedores (multiplataforma), aconselho o uso das crases, j que o $(cmd) no foi portado para todos os sabores de Shell. Aqui dentro do Botequim, usarei ambas as formas, indistintamente. Vejamos novamente o exemplo dado para as crases sob esta nova tica: $ echo Existem $(who | grep wc -l) usurios conectados Existem 8 usurios conectados Veja s este caso: $ Arqs=ls $ echo $Arqs ls Neste exemplo eu fiz uma atribuio (=) e executei uma instruo. O que eu queria era que a varivel $Arqs, recebesse a sada do comando ls. Como as instrues de um script so interpretadas de cima para baixo e da esquerda para a direita, a atribuio foi feita antes da execuo do ls. Para fazer o que desejamos necessrio que eu priorize a execuo deste comando em detrimento da atribuio e isto pode ser feito de qualquer uma das maneiras a seguir: $ Arqs=`ls` ou: $ Arqs=$(ls) Para encerrar este assunto vamos ver s mais um exemplo. Digamos que eu queira colocar dentro da varivel $Arqs a listagem longa (ls -l) de todos os arquivos comeados por arq e seguidos de um nico caractere (?). Eu deveria fazer: $ Arqs=$(ls -l arq?) ou: $ Arqs=`ls -l arq?` Mas veja: $ echo $Arqs -rw-r--r-- 1 jneves jneves 19 May 24 19:41 arq1 -rw-r--r-- 1 jneves jneves 23 May 24 19:43 arq2 -rw-r--r-- 1 jneves jneves 1866 Jan 22 2003 arql - P, saiu tudo embolado! - Pois cara, como eu j te disse, se voc deixar o Shell ver os espaos em branco, sempre que houver diversos espaos juntos, eles sero trocados por apenas um. Para que a listagem saia bonitinha, necessrio proteger a varivel da interpretao do Shell, assim: $ echo "$Arqs" -rw-r--r-- 1 jneves jneves 19 May 24 19:41 arq1 -rw-r--r-- 1 jneves jneves 23 May 24 19:43 arq2 -rw-r--r-- 1 jneves jneves 1866 Jan 22 2003 arql - Olhe, amigo, v treinando esses exemplos, porque, quando nos encontrarmos novamente, vou lhe explicar uma srie de instrues tpicas de programao Shell. Tchau! Ahh! S mais uma coisinha que eu ia esquecendo de lhe dizer. Em Shell, o "jogo da velha" (#) usado quando desejamos fazer um comentrio. $ exit # pede a conta ao garcon Qualquer dvida ou falta de companhia para um chope ou at para falar mal dos polticos s mandar um e-mail para julio.neves@gmail.com. Vou aproveitar tambm para mandar o meu jab: diga para os amigos que quem estiver afim de fazer um curso porreta de programao em Shell que mande um e-mail para julio.neves@uniriotec.br para informar-se. Valeu!

Papo de Botequim Parte II

Eu fico com o grep, voc com a gripe A famlia grep

Vamos Montar uma "cdteca" Passando parmetros Macetes paramtricos

- Garom! Traz um "chops" e dois "pastel". O meu amigo hoje no vai beber por que ele finalmente est 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 estas ferramentas que vimos para montar programas estruturados, que o Shell permite. 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 Global Regular Expression Print, que usa expresses regulares para pesquisar a ocorrncia de cadeias de caracteres na entrada definida (se bem que h uma lenda sobre como este comando foi nomeado: no editor de textos "ed", o av do "vim", o comando usado para buscas era g/_expressao regular_/p, ou no ingls g/_re_/p.). Por falar em expresses regulares (ou regexp), o Aurlio Marinho Jargas tem todas as dicas em sua pgina (inclusive tutorias) que abordam o tema. Se voc est mesmo a fim de aprender a programar em Shell, Perl, Python, ... Acho bom voc ler estes artigos para te ajudar no que est para vir.

Eu fico com o grep, voc com a gripe


Esse negcio de gripe brincadeira! s um pretexto para pedir umas caipirinhas. Mas voltando vaca fria, eu te falei que o grep procura cadeia 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. Vejamos: Pesquisando em um arquivo: $ grep rafael /etc/passwd Pesquisando em vrios arquivos: $ grep grep *.sh Pesquisando na saida de comando: $ who | grep Pelegrino No 1 exemplo, o mais simples, procurei a palavra rafael em qualquer lugar do arquivo /etc/passwd. Se quisesse procur-la como um login name, isto , somente no incio dos registros deste arquivo, eu deveria fazer: $ grep '^rafael' /etc/passwd E para que serve este circunflexo e os apstrofos, voc vai me perguntar. O circunflexo (^), se voc tivesse lido os artigos anteriores sobre expresses regulares que te falei, saberia que servem para limitar a pesquisa ao incio de cada linha, e os apstrofos (') servem para o Shell no interpretar este circunflexo, deixando-o passar inclume para o comando grep. Olha que legal! O grep aceita como entrada, a sada de outro comando redirecionado por um pipe (isto muito comum em Shell e um tremendo acelerador de execuo de comando j que atua como se a sada de um programa fosse guardada em disco e o segundo programa lesse este arquivo gerado), desta 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 Pelegrino estava trabalhando ou "coando".

A famlia grep
Este comando grep muito conhecido, pois usado com muita freqncia, o que muitas pessoas desconhecem que existem trs comandos na famlia grep, que so:

grep egrep fgrep O grep pode ou no usar expresses regulares simples, porm no caso de no us-las, o fgrep melhor, por ser mais rpido; O egrep ("e" de extended, extendido) muito poderoso no uso de expresses regulares. Por ser o mais lento da famlia, s deve ser usado quando for necessria a elaborao de uma expresso regular no aceita pelo grep; O fgrep ("f" de fast, rpido, ou de "file", arquivo) como o nome diz o rapidinho da famlia, executa o servio de forma muito veloz (por vezes cerca de 30% mais veloz que o grep e 50% mais que o egrep), porm no permite o uso de expresses regulares na pesquisa.

A principais caractersticas diferenciais entre os 3 so:


Tudo que foi dito acima sobre velocidade, s se aplica famlia de comandos grep do Unix. No Linux o grep sempre mais veloz, j que os outros dois (fgrep e egrep) so scripts em Shell que chamam o primeiro e, j vou adiantando, no gosto nem um pouquinho desta soluo. - 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?

10

- Eu achei que o fgrep resolveria o teu problema de forma mais veloz do que o grep. - Perfeito! T vendo que voc est atento! Est entendendo tudo que estou te explicando! Ento vamos ver mais exemplos para clarear de vez as diferenas de uso dos membros da famlia. Exemplos Eu sei que em um arquivo existe um texto falando sobre Linux s no tenho certeza se est escrito com L maisculo ou l minsculo. Posso fazer de duas formas: $ egrep (Linux | linux) arquivo.txt ou $ grep [Ll]inux arquivo.txt No primeiro caso, a expresso regular complexa "(Linux | linux)" usa os parnteses para agrupar as opes e a barra vertical (|) como um "ou" lgico, isto , estou procurando Linux ou linux. No segundo, a expresso regular [Ll]inux significa: comeado por L ou l seguido de inux. Por esta expresso ser mais simples, o grep consegue resolv-la, portanto acho 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: $ ls -l | grep '^d' drwxr-xr-x 3 root root 4096 Dec 18 2000 doc drwxr-xr-x 11 root root 4096 Jul 13 18:58 freeciv drwxr-xr-x 3 root root 4096 Oct 17 2000 gimp drwxr-xr-x 3 root root 4096 Aug 8 2000 gnome drwxr-xr-x 2 root root 4096 Aug 8 2000 idl drwxrwxr-x 14 root root 4096 Jul 13 18:58 locale drwxrwxr-x 12 root root 4096 Jan 14 2000 lyx drwxrwxr-x 3 root root 4096 Jan 17 2000 pixmaps drwxr-xr-x 3 root root 4096 Jul 2 20:30 scribus drwxrwxr-x 3 root root 4096 Jan 17 2000 sounds drwxr-xr-x 3 root root 4096 Dec 18 2000 xine No exemplo que acabamos de ver, o circunflexo (^) serviu para limitar a pesquisa primeira posio da sada do ls longo. Os apstrofos foram colocados para o Shell no "ver" o circunflexo (^). Vamos ver mais um. Sabemos que as quatro primeiras posies possveis de um ls -l de um arquivo comum (arquivo comum! No diretrio, nem link, nem ...) devem ser: Posio 1 Valores Possveis 2 r 3 w 4 x s (suid) Assim sendo, para descobrir todos os arquivos executveis em um determinado diretrio eu deveria fazer: $ ls -la | egrep '^-..(x|s)' -rwxr-xr-x 1 root root 2875 Jun 18 19:38 rc -rwxr-xr-x 1 root root 857 Aug 9 22:03 rc.local -rwxr-xr-x 1 root root 18453 Jul 6 17:28 rc.sysinit Onde novamente usamos o circunflexo (^) para limitar a pesquisa ao incio de cada linha, ento as linhas listadas sero as que comeam por um trao (-), seguido de qualquer coisa (o ponto quando usado como uma expresso regular significa qualquer coisa), novamente seguido de qualquer coisa, vindo a seguir um x ou um s. Obteramos o mesmo resultado se fizssemos: $ ls -la | grep '^-..[xs]' e agilizaramos a pesquisa.

Vamos Montar uma "cdteca"


Vamos comear a desenvolver programas, acho que a montagem de um banco de dados de msicas bacana para efeito didtico (e til nesses tempos de downloads de 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 com os CDs de software que vm com a Linux Magazine e outros que voc compra ou queima, disponibilizando este banco de software para todos que trabalham com voc (o Linux multiusurio, e como tal deve ser explorado), desta forma ganhando muitos pontos com seu adorado chefe. - Pra ai! De onde eu vou receber os dados dos CDs? - Inicialmente, vou lhe mostrar como o seu programa pode receber parmetros de quem o estiver executando e em breve, ensinarei a ler os dados pela tela ou de um arquivo.

11

Passando parmetros
O layout do arquivo musicas ser o seguinte: nome do lbum^intrprete1~nome da msica1:..:intrprete~nome da msica isto , o nome do lbum ser separado por um circunflexo (^) 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 internamente, o intrprete ser separado por um til (~) do nome da msica. Eu quero escrever um programa que chamado musinc, que incluir registros no meu arquivo musicas. Eu passarei o contedo de cada lbum como parmetro na chamada do programa fazendo assim: $ musinc "lbum^interprete~musica:interprete~musica:..." Desta forma o programa 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 (nome numrico fica muito esquisito, n? O que quis dizer que seus nomes so formados por um e somente um algarismo), isto $1, $2, $3, ..., $9. Vamos, antes de tudo, fazer um teste: Exemplos $ cat teste #!/bin/bash # Programa para testar passagem de parametros echo "1o. parm -> $1" echo "2o. parm -> $2" echo "3o. parm -> $3" Vamos execut-lo: $ teste passando parametros para testar bash: teste: cannot execute Ops! Esqueci-me de torn-lo executvel. Vou faz-lo de forma a permitir que todos possam execut-lo e em seguida vou test-lo: $ chmod 755 teste $ teste passando parametros para testar 1o. parm -> passando 2o. parm -> parametros 3o. parm -> para Repare que a palavra testar, que seria o quarto parmetro, no foi listada. Isto deu-se justamente porque o programa teste s listava os trs primeiros parmetros. Vamos execut-lo de outra forma: $ teste "passando parametros" para testar 1o. parm -> passando parametros 2o. parm -> para 3o. parm -> testar As aspas no deixaram o Shell ver o espao em branco entre as palavras e considerou-as um nico parmetro.

Macetes paramtricos
J que estamos falando em passagem de parmetros deixa eu te dar mais umas dicas: Significado das Principais Variveis Referentes aos Parmetros Varivel $0 $# $* Exemplos Vamos alterar o programa teste para usar as variveis que acabamos de ver. Vamos faz-lo assim: $ cat teste #!/bin/bash # Programa para testar passagem de parametros (2a. Versao) echo O programa $0 recebeu $# parametros echo "1o. parm -> $1" echo "2o. parm -> $2" echo "3o. parm -> $3" echo Todos de uma s \"tacada\": $* Repare que antes das aspas eu usei uma barra invertida para o escond-las da interpretao do Shell (se no usasse as contrabarras as Significado Contm o nome do programa Contm a quantidade de parmetros passados Contm o conjunto de todos os parmetros (muito parecido com $@)

12

aspas no apareceriam). Vamos execut-lo: $ teste passando parametros para testar O programa teste recebeu 4 parametros 1o. parm -> passando 2o. parm -> parametros 3o. parm -> para Todos de uma s "tacada": passando parametros para testar Conforme eu disse, os parmetros recebem nmeros de 1 a 9, mas isso no significa que no posso usar mais de 9 parmetros significa somente que s posso enderear 9. Vamos testar isso: Exemplo: $ cat teste #!/bin/bash # Programa para testar passagem de parametros (3a. Versao) echo O programa $0 recebeu $# parametros echo "11o. parm -> $11" shift echo "2o. parm -> $1" shift 2 echo "4o. Parm -> $1" Vamos execut-lo: $ teste passando parametros para testar O programa teste recebeu 4 parametros que so: 11o. parm -> passando1 2o. parm -> parametros 4o. parm -> testar Duas coisas muito interessantes neste script: 1. Para mostrar que os nomes dos parmetros variam de $1 a $9 eu fiz um echo $11 e o que aconteceu? O Shell interpretou como sendo $1 seguido do algarismo 1 e listou passando1; 2. O comando shift cuja sintaxe shift n, podendo o n assumir qualquer valor numrico (porm seu default 1 como no exemplo dado), despreza os n primeiros parmetros, tornando o parmetro de ordem n+1, o primeiro ou seja, o $1. Bem, agora que voc j sabe mais sobre passagem de parmetros do que eu, vamos voltar nossa "cdteca" para fazer o script de incluso de CDs no meu banco chamado musicas. O programa muito simples (como tudo em Shell) e vou list-lo para voc ver: Exemplos $ cat musinc #!/bin/bash # Cadastra CDs (versao 1) # echo $1 >> musicas O script fcil e funcional, limito-me a anexar ao fim do arquivo musicas o parmetro recebido. Vamos cadastrar 3 lbuns para ver se funciona (para no ficar "enchendo lingia", vou supor que em cada CD s existem 2 msicas): $ musinc "album 3^Artista5~Musica5:Artista6~Musica5" $ musinc "album 1^Artista1~Musica1:Artista2~Musica2" $ musinc "album 2^Artista3~Musica3:Artista4~Musica4" Listando o contedo de musicas. $ cat musicas album 3^Artista5~Musica5:Artista6~Musica6 album 1^Artista1~Musica1:Artista2~Musica2 album 2^Artista3~Musica3:Artista4~Musica4 No est funcional como achava que deveria ficar... podia ter ficado melhor. Os lbuns esto fora de ordem, dificultando a pesquisa. Vamos alterar nosso script e depois test-lo novamente: $ cat musinc #!/bin/bash # Cadastra CDs (versao 2) # echo $1 >> musicas sort musicas -o musicas Vamos cadastrar mais um: $ musinc "album 4^Artista7~Musica7:Artista8~Musica8" Agora vamos ver o que aconteceu com o arquivo musicas: $ cat musicas album 1^Artista1~Musica1:Artista2~Musica2 album 2^Artista3~Musica3:Artista4~Musica4

13

album 3^Artista5~Musica5:Artista6~Musica5 album 4^Artista7~Musica7:Artista8~Musica8 Simplesmente inseri uma linha que classifica o arquivo musicas dando a sada nele mesmo (para isso serve a opo -o), aps cada lbum ser anexado. Oba! Agora est legal e quase funcional. Mas ateno, no se desespere! Esta no a verso final. O programa ficar muito melhor e mais amigvel, em uma nova verso que desenvolveremos aps aprendermos a adquirir os dados da tela e formatar a entrada. Exemplos Ficar listando com o comando cat no est com nada, vamos ento fazer um programa chamado muslist para listar um lbum cujo nome ser passado como parmetro: $ cat muslist #!/bin/bash # Consulta CDs (versao 1) # grep $1 musicas Vamos execut-lo, procurando pelo album 2. Como j vimos antes, para passar a cadeia album 2 necessrio proteg-la da interpretao do Shell, para que ele no a interprete como dois parmetros. Vamos fazer assim: $ muslist "lbum 2" grep: can't open 2 musicas: album 1^Artista1~Musica1:Artista2~Musica2 musicas: album 2^Artista3~Musica3:Artista4~Musica4 musicas: album 3^Artista5~Musica5:Artista6~Musica6 musicas: album 4^Artista7~Musica7:Artista8~Musica8 Que lambana! Onde est o erro? Eu tive o cuidado de colocar o parmetro passado entre aspas, para o Shell no dividi-lo em dois! , mas repare como est o grep 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, transformou-se em dois argumentos. Desta forma o contedo final da linha, que o comando grep executou foi o seguinte: grep album 2 musicas Como a sintaxe do grep : =grep [arq1, arq2, ..., arqn]= o grep entendeu que deveria procurar a cadeia de caracteres album nos arquivos 2 e musicas, Por no existir o arquivo 2 gerou o erro, e por encontrar a palavra album em todos os registros de musicas, listou a todos. Sempre que a cadeia de caracteres a ser passada para o comando grep possuir brancos ou TAB, mesmo que dentro de variveis, coloque-a sempre entre aspas para evitar que as palavras aps o primeiro espao em branco ou TAB sejam interpretadas como nomes de arquivos. Por outro lado, melhor ignorarmos maisculas e minsculas na pesquisa. Resolveramos os dois problemas se o programa tivesse a seguinte forma: $ cat muslist #!/bin/bash # Consulta CDs (versao 2) # grep -i "$1" musicas Neste 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:Artista4~Musica4 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 ou at por um pedao de qualquer um destes. Quando conhecermos os comandos condicionais, montaremos uma nova verso de muslist que permitir especificar por qual campo pesquisar. A voc vai me dizer: - Poxa, mas um saco ter que colocar o argumento de pesquisa entre aspas na hora de passar o nome do lbum. Esta forma no nem um pouco amigvel! - Tem razo, e por isso vou te mostrar uma outra forma de fazer o que voc pediu: $ cat muslist

14

#!/bin/bash # Consulta CDs (versao 3) # grep -i "$*" musicas $ muslist album 2 album 2^Artista3~Musica3:Artista4~Musica4 Desta forma, o $*, que significa todos os parmetros, ser substitudo pela cadeia album 2 (de acordo com o exemplo anterior, fazendo o que voc queria. No se esquea o problema do Shell no se voc pode ou no fazer uma determinada coisa. O problema decidir qual a melhor forma de faz-la, j que para desempenhar qualquer tarefa, a quantidade de opes enorme. Ah! Em um dia de vero voc foi praia, esqueceu o CD no carro, aquele "solzinho" de 40 graus empenou o seu CD 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. Vejamos: Exemplos $ grep -v "album 2" musicas album 1^Artista1~Musica1:Artista2~Musica2 album 3^Artista5~Musica5:Artista6~Musica6 album 4^Artista7~Musica7:Artista8~Musica8 Conforme eu expliquei antes, o grep do exemplo listou todos os registros de musicas exceto o referente a album 2, porque atendia ao argumento do comando. Estamos ento prontos para desenvolver o script para remover aquele CD empenado da sua "CDteca". Ele tem a seguinte cara: $ cat musexc #!/bin/bash # Exclui CDs (versao 1) # grep -v "$1" musicas > /tmp/mus$$ mv -f /tmp/mus$$ musicas Na primeira linha mandei para /tmp/mus$$ o arquivo musicas, sem os registros que atendessem a consulta feita pelo comando grep. Em seguida, movi (que, no duro, equivale a renomear) /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 (Process Identification ou identificao do processo) e desta forma cada um que editar o arquivo musicas o far em um arquivo de trabalho diferente, desta forma evitando colises no uso. - A cara, estes programas que fizemos at aqui esto muito primrios em virtude da falta de ferramentas que ainda temos. Mas bom, enquanto eu tomo mais um chope, voc ir para casa praticar em cima dos exemplos dados porque, eu prometo, chegaremos a desenvolver um sistema bacana para controle dos seus CDs. - Quando nos encontrarmos da prxima vez, vou te ensinar como funcionam os comandos condicionais e aprimoraremos mais um pouco estes scripts. - Por hoje chega! J falei demais e preciso molhar a palavra porque estou de goela seca! - Garom! Mais um sem colarinho! No se esquea, qualquer dvida ou falta de companhia para um chope ou at para falar mal dos polticos s mandar um e-mail para julio.neves@gmail.com.

Papo de Botequim Parte III

Trabalhando com cadeias O Comando cut (que no a central de trabalhadores) O comando cut com a opo -c O comando cut com a opo -f Se tem cut tem paste Quem est em p, deita Usando separadores O Comando tr Trocando caracteres com tr Removendo caracteres com tr Xpremendo com tr Comandos Condicionais O Comando if

- Garon, traga dois chopes por favor que hoje eu vou ter que falar muito.

15

Trabalhando com cadeias


Pelo ttulo acima no pense voc que vou lhe ensinar a ser carcereiro! Estou me referindo a cadeia de caracteres!

O Comando cut (que no a central de trabalhadores)


Primeiro quero te mostrar, de forma eminentemente prtica uma instruo simples de usar e muito til: o comando cut, Esta instruo usada para cortar um determinado pedao de um arquivo e tem duas formas distintas de uso

O comando cut com a opo -c


Com esta opo, o comando tem a seguinte sintaxe: cut -c PosIni-PosFim [arquivo] Onde: PosIni = Posio inicial PosFim = Posio final $ 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 Como d para ver, no duro mesmo existem quatro sintaxes distintas: na primeira (-c 1-5), eu especifiquei uma faixa, na segunda (-c -6), especifiquei tudo at uma posio, na terceira (-c 4-) de uma determinada posio em diante e na quarta (-c 1,3,5,7,9), determinadas posies. A ltima (-c -3,5,8-) foi s para mostrar que podemos misturar tudo.

O comando cut com a opo -f


Mas no pense voc que acabou por a! Como voc deve ter percebido esta forma de cut til para arquivos com campos de tamanho fixo, mas atualmente o que mais existe so arquivos com campos de tamanho variveis, onde cada campo termina com um delimitador. Vamos dar uma olhada no arquivo musicas que comeamos a preparar no nosso papo na ltima vez que viemos aqui no botequim. $ cat musicas album 1^Artista1~Musica1:Artista2~Musica2 album 2^Artista3~Musica3:Artista4~Musica4 album 3^Artista5~Musica5:Artista6~Musica5 album 4^Artista7~Musica7:Artista8~Musica8 Ento, recapitulando, o seu "leiaute" o seguinte: nome do album^interprete1~nome da musica1:...:interpreten~nome da musican

16

isto , o nome do lbum ser separado por um circunflexo (^) 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 internamente, o nome do intrprete ser separado por um til (~) do nome da msica. Ento para pegarmos os dados referentes a todas as segundas msicas do arquivo musicas, devemos fazer: $ cut -f2 -d: musicas Artista2~Musica2 Artista4~Musica4 Artista6~Musica5 Artista8~Musica8 Ou seja, cortamos o segundo campo (-f de field em ingls) delimitado (-d) por dois-pontos (:). Mas, se quisermos somente os intrpretes, devemos fazer: $ cut -f2 -d: musicas | cut -f1 -d~ Artista2 Artista4 Artista6 Artista8 Para entender isso, vamos pegar a primeira linha de musicas: $ head -1 musicas album 1^Artista1~Musica1:Artista2~Musica2 Ento observe o que foi feito: Delimitador do primeiro cut (:) album 1^Artista1~Musica1:Artista2~Musica2 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: Novo delimitador (~) 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 primeira linha for aplicado no restante do arquivo, chegaremos resposta anteriormente dada.

Se tem cut tem paste


Como j era de se esperar, o comando paste serve para colar, s que aqui no Shell o que ele cola so arquivos. S para comear a entend-lo, vamos fazer assim:: paste arq1 arq2 Desta forma ele mandar para a sada padro (stdout) cada um dos registros de arq1 ao lado dos registros de arq2 correspondentes e caso nenhum delimitador seja especificado, ele usar por default o <TAB>. O paste um comando pouco usado por sua sintaxe ser pouco conhecida. Vamos brincar com 2 arquivos criados da seguinte forma: $ seq 10 > inteiros $ seq 2 2 10 > pares Para ver o contedo dos arquivos criados, vamos usar o paste na sua forma careta que mostramos acima: $ paste inteiros pares 1 2 2 4 3 6 4 8 5 10 6 7 8 9 10

Quem est em p, deita


Agora vamos transformar a coluna do pares em linha:

17

$ paste -s pares 2 4 6 8 10

Usando separadores
Como j foi dito, o separador default do paste o <TAB>, mas isso pode ser alterado com a opo -d. Ento para calcular a soma do contedo de pares primeiramente faramos: $ paste -s -d'+' pares # tambm poderia ser -sd'+' 2+4+6+8+10 e depois passaramos esta linha para a calculadora (bc) e ento ficaria: $ paste -sd'+' pares | bc 30 Assim sendo, para calcular o fatorial do nmero contido em $Num, basta: $ seq $Num | paste -sd'*' | bc Com o comando paste voc tambm pode montar formataes exticas como esta a seguir: $ ls | paste -s -d'\t\t\n' arq1 arq2 arq3 arq4 arq5 arq6 O que aconteceu foi o seguinte: foi especificado para o comando paste que ele transformaria linhas em colunas (pela opo -s) e que os seus separadores (...! Ele aceita mais de um, mas somente um aps cada coluna criada pelo comando) seriam uma <TAB>, outra <TAB> e um <ENTER>, gerando desta forma a sada tabulada em 3 colunas. Agora que voc j entendeu isto, veja como fazer a mesma coisa, porm de forma mais fcil e menos bizarra e tosca, usando o mesmo comando mas com a seguinte sintaxe: $ ls | paste - - arq1 arq2 arq3 arq4 arq5 arq6 E isto acontece porque se ao invs de especificarmos os arquivos colocarmos o sinal de menos (-), o comando paste os substitui pela sada ou entrada padro conforme o caso. No exemplo anterior os dados foram mandados para a sada padro (stdout), porque o pipe (|) estava desviando a sada do ls para a entrada padro (stdin) do paste, mas veja o exemplo a seguir: $ cat arq1 predisposio privilegiado profissional $ cat arq2 encher mrio motor $ cut -c-3 arq1 | paste -d "" - arq2 preencher primrio promotor Neste caso, o cut devolveu as trs primeiras letras de cada registro de arq1, o paste foi montado para no ter separador (-d"") e receber a entrada padro (desviada pelo pipe) no trao (-) gerando a sada juntamente com arq2.

O Comando tr
Outro comando muito interessante o tr que serve para substituir, comprimir ou remover caracteres. Sua sintaxe segue o seguinte padro: tr [opes] cadeia1 [cadeia2] O comando tr 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 remove caracteres da cadeia1. As principais opes do comando so: Principais Opes do comando tr Opo -s -d Significado Comprime n ocorrncias de cadeia1 em apenas uma Remove os caracteres de cadeia1

18

Trocando caracteres com tr


Primeiro vou te dar um exemplo bem bobo: $ echo bobo | tr o a baba Isto , troquei todas as ocorrncias da letra o pela letra a. Suponha que em um determinado ponto do meu script eu pea ao operador para teclar s ou n (sim ou no), e guardo sua resposta na varivel $Resp. Ora o contedo de $Resp pode estar com letra maiscula ou minscula, 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) e aps este comando eu teria certeza que o contedo de $Resp seria um s ou um n. Se o meu arquivo ArqEnt est todo escrito com letras maisculas e desejo pass-las para minsculas eu fao: $ tr A-Z a-z < ArqEnt > /tmp/$$ $ mv -f /tmp/$$ ArqEnt Note que neste caso usei a notao A-Z para no escrever ABCD...YZ. Outro tipo de notao que pode ser usada so as escape sequences (prefiro escrever no bom e velho portugus, mas nesse caso 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 significado voc ver a seguir: Escape Sequences Seqncia \t \n \v \f \r \\ Significado Tabulao Nova linha Tabulao Vertical Nova Pgina Incio da linha <^M> Uma barra invertida Octal \011 \012 \013 \014 \015 \0134

Removendo caracteres com tr


Ento deixa eu te contar um "causo": um aluno que estava danado comigo, resolveu complicar a minha vida e em um exerccio prtico valendo nota que passei para ser feito no computador, me entregou o script com todos os comandos separados por ponto-e-vrgula (lembra que eu disse que o ponto-e-vrgula servia para separar diversos comandos em uma mesma linha?). Vou dar um exemplo simplificado e idiota de uma "tripa" assim: $ cat confuso echo leia Programao Shell Linux do Julio Cezar Neves > livro;cat livro;pwd;ls;rm -f lixo 2>/dev/null;cd ~ Eu executava o programa e ele funcionava: $ confuso leia Programao Shell Linux do Julio Cezar Neves /home/jneves/LM confuso livro musexc musicas musinc muslist numeros 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 executei o seguinte comando: $ tr ";" "\n" < confuso echo leia Programao Shell Linux do Julio Cezar Neves pwd ls rm -f lixo 2>/dev/null cd ~ O cara ficou muito desapontado, porque em 2 ou 3 segundos eu desfiz a gozao que ele perdera horas para fazer. Mas preste ateno! Se eu estivesse em uma mquina com Unix, eu teria feito: $ tr ";" "\012" < confuso

Xpremendo com tr
Agora veja a diferena entre os dois comandos date: o que fiz hoje e outro que foi executado h duas semanas: $ date # Hoje

19

Sun Sep 19 14:59:54 2004 $ date # H duas semanas Sun Sep 5 10:12:33 2004 Para pegar a hora eu deveria fazer: $ date | cut -f 4 -d ' ' 14:59:54 Mas duas semanas antes ocorreria o seguinte: $ date | cut -f 4 -d ' ' 5 Mas observe porque: $ date # H duas semanas Sun Sep 5 10:12:33 2004 Como voc pode notar, existem 2 caracteres em branco antes do 5 (dia), o que estraga tudo porque o terceiro pedao est vazio e o quarto o dia (5). Ento o ideal seria comprimir os espaos em brancos sucessivos em somente um espao para poder tratar as duas cadeias resultantes do comando date da mesma forma, e isso se faz assim: $ date | tr -s " "a Sun Sep 5 10:12:33 2004 Como voc pode ver no existem mais os dois espaos, ento agora eu poderia cortar: $ date | tr -s " " | cut -f 4 -d " " 10:12:33 Olha s como o Shell j est quebrando o galho. Veja este arquivo que foi baixado de uma mquina com aquele sistema operacional que pega vrus: $ cat -ve ArqDoDOS.txt Este arquivo^M$ foi gerado pelo^M$ DOS/Rwin e foi^M$ baixado por um^M$ ftp mal feito.^M$ E agora eu quero te dar duas dicas: Dica #1 - 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 final da linha como um cifro ($). Dica #2 - Isto ocorre porque no formato DOS (ou rwin), o fim dos registros formado por um carriagereturn (\r) e um line-feed (\n). No Linux porm o final do registro tem somente o line-feed. Vamos ento limpar este arquivo. $ tr -d '\r' < ArqDoDOS.txt > /tmp/$$ $ mv -f /tmp/$$ ArqDoDOS.txt 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.$ Bem a opo -d do tr remove o caractere especificado de todo o arquivo. Desta forma eu removi os caracteres indesejados salvando em um arquivo de trabalho e posteriormente renomeei-o para a sua designao original. Obs: No Unix eu deveria fazer: $ tr -d '\015' < ArqDoDOS.txt > /tmp/$$ Isto aconteceu porque o ftp foi feito do modo binrio (ou image), isto , sem a interpretao do texto. Se antes da transmisso do arquivo tivesse sido estipulada a opo ascii do ftp, isto no teria ocorrido. - Olha, depois desta dica t comeando a gostar deste tal de Shell, mas ainda tem muita coisa que no consigo fazer. - Pois , ainda no te falei quase nada sobre programao em Shell, ainda tem muita coisa para aprender, mas com o que aprendeu, j d para resolver muitos problemas, desde que voc adquira o modo Shell de pensar. Voc seria capaz de fazer um script para me dizer quais so as pessoas que esto logadas h mais de um dia no seu servidor? - Claro que no! Para isso seria necessrio eu conhecer os comandos condicionais que voc ainda no me explicou como funcionam. - Deixa eu tentar mudar um pouco a sua lgica e traz-la para o modo Shell de pensar, mas antes melhor tomarmos um chope... Chico, traz mais dois... - Agora que j molhei a palavra, vamos resolver o problema que te propus. Repare como funciona o comando who:

20

$ who jneves pts/1 Sep rtorres pts/0 Sep rlegaria pts/1 Sep lcarlos pts/3 Sep E veja tambm o date:

18 20 20 20

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

$ date Mon Sep 20 10:47:19 BRT 2004 Repare que o ms e o dia esto no mesmo formato em ambos os comandos. Algumas vezes um comando tem a sada em portugus e o outro em ingls. Quando isso ocorrer, voc pode usar o seguinte artifcio: $ date Mon Sep 20 10:47:19 BRT 2004 $ LANG=pt_BR date Seg Set 20 10:47:19 BRT 2004 Desta forma passando a sada do comando date para portugus. Ora, se em algum registro do who eu no encontrar a data de hoje, sinal que o cara est "logado" h mais de um dia, j que ele no pode ter se "logado" amanh... Ento vamos guardar o pedao que importa da data de hoje para procur-la na sada do who: $ Data=$(date | cut -c 5-10) Eu usei a construo $(...), para priorizar a execuo dos comandos antes de atribuir a sua sada varivel $Data. Vamos ver se funcionou: $ echo $Data Sep 20 Beleza! Agora, o que temos que fazer procurar no comando who os registros que no possuem esta data. - Ah! Eu acho que estou entendendo! Voc falou em procurar e me ocorreu o comando grep, estou certo? - Certssimo! S que eu tenho que usar o grep com aquela opo que ele s lista os registros nos quais ele no encontrou a cadeia. Voc se lembra que opo essa? - Claro, a opo -v... - Isso! T ficando bo! Ento vamos ver: $ who | grep -v "$Data" jneves pts/1 Sep 18 13:40 - E se eu quisesse mais um pouco de perfumaria eu faria assim: $ who | grep -v "$Data" | cut -f1 -d ' ' jneves - Viu? No foi necessrio usar nenhum comando condicional, at porque o nosso mais usado comando condicional, o famoso if, no testa condio, mas sim instrues, como veremos agora.

Comandos Condicionais
Veja as linhas de comando a seguir: $ ls musicas musicas $ echo $? 0 $ ls ArqInexistente ls: ArqInexistente: No such file or directory $ echo $? 1 $ who | grep jneves jneves pts/1 Sep 18 13:40 (10.2.4.144) $ echo $? 0 $ who | grep juliana $ echo $? 1 - O que esse $? faz a? Comeando 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.

21

O Comando if
O que o nosso comando condicional if faz testar a varivel $?. Ento vamos ver a sua sintaxe: if cmd then cmd1 cmd2 cmdn else cmd3 cmd4 cmdm fi 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 executados sero os do bloco opcional do else (cmd3, cmd4 e cmdm), terminando com um fi. Vamos ver na prtica como isso funciona usando um scriptizinho que serve para incluir usurios no /etc/passwd: $ cat incusu #!/bin/bash # Verso 1 if grep ^$1 /etc/passwd then echo Usuario \'$1\' j existe else if useradd $1 then echo Usurio \'$1\' includo em /etc/passwd else echo "Problemas no cadastramento. Voc root?" fi fi Repare que o if est testando direto o comando grep e esta a sua finalidade. 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 somente o echo) e caso contrrio, as instrues do bloco do else que sero executadas, quando um novo if testa se o comando useradd foi executado a contento, criando o registro do usurio em /etc/passwd, ou no quando dar a mensagem de erro. Vejamos sua execuo, primeiramente passando um usurio j cadastrado: $ incusu jneves jneves:x:54002:1001:Julio Neves:/home/jneves:/bin/bash Usuario 'jneves' ja existe Como j vimos diversas vezes, mas sempre bom insistir no tema para que voc j fique precavido, no exemplo dado surgiu uma linha indesejada, ela a sada do comando grep. Para evitar que isso acontea, devemos desviar a sada desta instruo para /dev/null, ficando assim: $ cat incusu #!/bin/bash # Verso 2 if grep ^$1 /etc/passwd > /dev/null # ou: if grep -q ^$1 /etc/passwd then echo Usuario \'$1\' j existe else if useradd $1 then echo Usurio \'$1\' includo em /etc/passwd else echo "Problemas no cadastramento. Voc root?" fi fi Agora vamos test-lo como usurio normal (no root): $ incusu ZeNinguem ./incusu[6]: useradd: not found Problemas no cadastramento. Voc root? Epa, aquele erro no era para acontecer! Para evitar que isso acontea devemos mandar tambm a sada de erro (strerr, lembra?) do useradd para /dev/null, ficando na verso final assim:

22

$ cat incusu #!/bin/bash # Verso 3 if grep ^$1 /etc/passwd > /dev/null then echo Usuario \'$1\' j existe else if useradd $1 2> /dev/null then echo Usurio \'$1\' includo em /etc/passwd else echo "Problemas no cadastramento. Voc root?" fi fi Depois destas alteraes e de fazer um su (me tornar root) vejamos o seu comportamento: $ incusu botelho Usurio 'botelho' incluido em /etc/passwd E novamente: $ incusu botelho Usurio 'botelho' j existe Lembra que eu falei que ao longo dos nossos papos e chopes os nossos programas iriam se aprimorando? Ento vejamos agora como poderamos melhorar o nosso programa para incluir msicas: $ cat musinc #!/bin/bash # Cadastra CDs (versao 3) # if grep "^$1$" musicas > /dev/null then echo Este lbum j est cadastrado else echo $1 >> musicas sort musicas -o musicas fi Como voc viu, uma pequena evoluo da verso anterior, assim, antes de incluir um registro (que pela verso anterior poderia ser duplicado), testamos se o registro comeava (^) e terminava ($) igual ao parmetro passado ($1). O uso do circunflexo (^) no incio da cadeia e cifro ($) no fim, so para testar se o parmetro passado (o lbum e seus dados) so exatamente iguais a algum registro anteriormente cadastrado e no somente igual a um pedao de algum dos registros. Vamos execut-lo passando um lbum j cadastrado: $ musinc "album 4^Artista7~Musica7:Artista8~Musica8" Este lbum j est cadastrado E agora um no cadastrado: $ musinc "album 5^Artista9~Musica9:Artista10~Musica10" $ cat musicas album 1^Artista1~Musica1:Artista2~Musica2 album 2^Artista3~Musica3:Artista4~Musica4 album 3^Artista5~Musica5:Artista6~Musica5 album 4^Artista7~Musica7:Artista8~Musica8 album 5^Artista9~Musica9:Artista10~Musica10 - Como voc viu, o programa melhorou um pouquinho, mas ainda no est pronto. medida que eu for te ensinando a programar em shell, nossa CDteca ir ficando 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. - Cara, para isso existe o comando test, ele que testa condies. O comando if testa o comando test. Mas isso est meio confuso e como j falei muito, estou precisando de uns chopes para molhar a palavra. Vamos parando 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 ficando zonzo e assim tenho tempo para praticar esse monte de coisas que voc me falou hoje. - Para fixar o que voc aprendeu, tente fazer um scriptizinho para informar se um determinado usurio, que ser passado como parmetro esta logado (arghh!) ou no. - A Chico, mais dois chopes por favor... No se esquea, qualquer dvida ou falta de companhia para um chope ou at para falar mal dos polticos s mandar um e-mail para

23

julio.neves@gmail.com. Vou aproveitar tambm para mandar o meu jab: diga para os amigos que quem estiver afim de fazer um curso porreta de programao em Shell que mande um e-mail para julio.neves@uniriotec.br para informar-se. Valeu!

Papo de Botequim Parte IV


O Comando test Querida, Encolheram o Comando Condicional E tome de test Acaso Casa com case

- E a cara, tentou fazer o exerccio que te pedi para revigorar as idias? - Claro, que sim! Em programao, se voc no treinar, no aprende. Voc me pediu para fazer um scriptizinho para informar se um determinado usurio, que ser passado como parmetro esta logado (arghh!) ou no. Eu fiz o seguinte: $ cat logado #!/bin/bash # Pesquisa se uma pessoa est logada ou no if who | grep $1 then echo $1 est logado else echo $1 no se encontra no pedao fi - 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! - Agora que j molhamos os nossos bicos, vamos dar uma olhadinha na execuo do seu bacalho: $ logado jneves jneves pts/0 Oct 18 12:02 (10.2.4.144) jneves est logado Realmente funcionou. Passei o meu login como parmetro e ele disse que eu estava logado, porm ele mandou uma linha que eu no pedi. Esta linha a sada do comando who, e para evitar que isso acontea s mand-la para o buraco negro que a esta altura voc j sabe que o /dev/null. Vejamos ento como ficaria: $ 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 se encontra no pedao fi Agora vamos aos testes: $ logado jneves jneves est logado $ logado chico chico no se encontra no pedao Ah, agora sim! Lembre-se desta pegadinha, a maior parte dos comandos tem uma sada padro e uma sada de erros (o grep uma das poucos excees, j que no d mensagem de erro quando no acha uma cadeia) e necessrio estarmos atentos para redirecion-las para o buraco negro quando necessrio. Bem, agora vamos mudar de assunto: na ltima vez que nos encontramos aqui no Botequim, eu estava te mostrando os comandos condicionais e, quando j estvamos de goela seca falando sobre o if, voc me perguntou como se testa condies. Vejamos ento o

O Comando test
Bem, todos estamos acostumados a usar o if testando condies, e estas so sempre, maior, menor, maior ou igual, menor ou igual, igual e diferente. Bem, em Shell para testar condies, usamos o comando test, s que ele muito mais poderoso que o que estamos habituados. Primeiramente vou te mostrar as principais opes (existem muitas outras) para testarmos arquivos em disco: Opes do Comando test para arquivos Opo Verdadeiro se:

24

-e arq -s arq -f arq -d arq -r arq -w arq -x arq

arq existe arq existe e tem tamanho maior que zero arq existe e um arquivo regular arq existe e um diretrio; arq existe e com direito de leitura arq existe e com direito de escrita arq existe e com direito de execuo

Veja agora as principais opes para teste de cadeias de caracteres: Opes do comando test para cadeias de caracteres Opo -z cadeia -n cadeia cadeia c1 = c2 Verdadeiro se: Tamanho de cadeia zero Tamanho de cadeia maior que zero A cadeia cadeia tem tamanho maior que zero Cadeia c1 e c2 so idnticas

E pensa que acabou? Engano seu! Agora que vem o que voc est mais acostumado, ou seja as famosas comparaes com numricos. Veja a tabela: Opes do comando test para nmeros Opo n1 -eq n2 n1 -ne n2 n1 -gt n2 n1 -ge n2 n1 -lt n2 n1 -le n2 Verdadeiro se: n1 e n2 so iguais 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 Operadores Operador Parnteses ( ) Exclamao ! -a -o Finalidade Agrupar Negar E lgico OU lgico Significado equal not equal greater than greater or equal less than less or equal

Alm de tudo, some-se a estas opes as seguintes facilidades:

Ufa! Como voc viu tem coisa pr chuchu, e como eu te disse no incio, o nosso if muito mais poderoso que o dos outros. Vamos ver em uns exemplos como isso tudo funciona, primeiramente testaremos a existncia de um diretrio: Exemplos: if test -d lmb then cd lmb else mkdir lmb cd lmb fi No exemplo, testei se existia um diretrio lmb definido, caso negativo (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-deespantao (!) como um negador do test. Veja s: if test ! -d lmb then mkdir lmb

25

fi cd lmb 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 fim da execuo deste fragmento de script, o programa estaria com certeza dentro do diretrio lmb. Vamos ver dois exemplos para entender a diferena comparao entre nmeros e entre cadeias. cad1=1 cad2=01 if test $cad1 = $cad2 then echo As variveis so iguais. else echo As variveis so diferentes. fi Executando o fragmento de programa acima vem: 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. fi E vamos execut-lo novamente: As variveis so iguais. Como voc viu nas duas execues obtive resultados diferentes porque a cadeia 01 realmente diferente da cadeia 1, porm, a coisa muda quando as variveis so testadas numericamente, j que o nmero 1 igual ao nmero 01. Exemplos: Para mostrar o uso dos conectores -o (OU) e -a (E), veja um exemplo animal feito direto no prompt (me desculpem os zologos, mas eu no entendendo nada de reino, filo, classe, ordem, famlia, gnero e espcie, desta forma o que estou chamando de famlia ou de gnero tem grande chance de estar incorreto): $ Familia=felinae $ Genero=gato $ if test $Familia = canidea -a $Genero = lobo -o $Familia = felina -a $Genero = leo > then > echo Cuidado > else > echo Pode passar a mo > fi Pode passar a mo 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. Os sinais de maior (>) no incio das linhas internas ao if so os prompts de continuao (que esto definidos na varivel $PS2) e quando o Shell identifica que um comando continuar na linha seguinte, automaticamente ele o coloca at que o comando seja encerrado. Vamos mudar o exemplo para ver se continua funcionando: $ Familia=felino $ Genero=gato $ if test $Familia = felino -o $Familia = canideo -a $Genero = ona -o $Genero = lobo > then > echo Cuidado > else > echo Pode passar a mo > fi Cuidado

26

Obviamente a operao redundou em erro, isto foi porque a opo -a tem precedncia sobre a -o, e desta forma o que primeiro foi avaliado foi a expresso: $Familia = canideo -a $Genero = ona Que foi avaliada como falsa, retornando o seguinte: $Familia = felino -o FALSO -o $Genero = lobo Que resolvida vem: VERDADEIRO -o FALSO -o FALSO Como agora todos conectores so -o, e para que uma srie de expresses conectadas entre si por diversos OU lgicos seja verdadeira, basta que uma delas seja, a expresso final resultou como VERDADEIRO e o then foi executado de forma errada. Para que isso volte a funcionar faamos o seguinte: $ if test \($Familia = felino -o $Familia = canideo\) -a \($Genero = ona -o $Genero = lobo\) > then > echo Cuidado > else > echo Pode passar a mo > fi Pode passar a mo Desta forma, com o uso dos parnteses agrupamos as expresses com o conector -o, priorizando as suas execues e resultando: VERDADEIRO -a FALSO Para que seja VERDADEIRO o resultado duas expresses ligadas pelo conector -a necessrio que ambas sejam verdadeiras, o que no o caso do exemplo acima. Assim o resultado final 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 lembrarmos que o bash nos d muito recursos, e isso poderia ser feito de forma muito mais simples com um nico comando grep, da seguinte maneira: $ grep Artista1 musicas | grep Artista2 Da mesma forma para escolhermos CDs que tenham a participao do Artista1 e do Artista2, no necessrio montarmos um if com o conector -o. O egrep (ou grep -E, sendo este mais aconselhvel) tambm resolve isso para ns. Veja como: $ egrep (Artista1|Artista2) musicas Ou (nesse caso especfico) o prprio grep puro e simples poderia nos quebrar o galho: $ grep Artista[12] musicas No egrep acima, foi usada uma expresso regular, onde 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 pra 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. Exemplos: Para isso vamos pegar aquele exemplo para fazer uma troca de diretrios, que era assim: if test ! -d lmb then mkdir lmb fi cd lmb e utilizando a nova sintaxe, vamos faz-lo assim: if [ ! -d lmb ] then mkdir lmb fi cd lmb Ou seja, o comando test pode ser substitudo por um par de colchetes ([ ]), separados por espaos em branco dos argumentos, o que aumentar enormemente a legibilidade, pois o comando if ir ficar com a sintaxe semelhante das outras linguagens e por isso

27

este ser o modo que o comando test ser usado daqui para a frente.

Querida, Encolheram o Comando Condicional


Se voc pensa que acabou, est muito enganado. Repare a tabela (tabela verdade) a seguir: Valores Booleanos VERDADEIRO-VERDADEIRO VERDADEIRO-FALSO FALSO-VERDADEIRO E VERDADEIRO FALSO FALSO OU VERDADEIRO VERDADEIRO VERDADEIRO

FALSO-FALSO FALSO FALSO Ou seja, quando o conector E e a primeira condio verdadeira, o resultado final pode ser VERDADEIRO ou FALSO, dependendo da segunda condio, j no conector OU, caso a primeira condio seja verdadeira, o resultado sempre ser VERDADEIRO e 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 disso, criaram uma forma abreviada de fazer testes. Batizaram o conector E de && e o OU de || e para ver como isso funciona, vamos us-los como teste no nosso velho exemplo de trocarmos de diretrio, que em sua ltima verso estava assim: if [ ! -d lmb ] then mkdir lmb fi cd lmb Isso tambm poderia ser escrito da seguinte maneira: [ ! -d lmb ] && mkdir lmb cd lmb Ou ainda retirando a negao (!): [ -d lmb ] || mkdir lmb cd lmb No primeiro caso, se o primeiro comando (o test que est representado pelos colchetes) for bem sucedido, isto , no existir o diretrio lmb, o mkdir ser efetuado 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: cd lmb || mkdir lmb Neste caso, se o cd fosse mal sucedido, seria criado o diretrio lmb mas no seria feito o cd para dentro dele. Para executarmos mais de um comando desta forma, necessrio fazermos um grupamento de comandos, e isso se consegue com o uso de chaves ({ }). Veja como seria o correto: cd lmb || { mkdir lmb cd lmb } Ainda no est legal, porque caso o diretrio no exista, o cd dar a mensagem de erro correspondente. Ento devemos fazer: cd lmb 2> /dev/null || { mkdir lmb cd lmb } Como voc viu o comando if nos permitiu fazer um cd seguro de diversas maneiras. sempre bom lembrarmos que o seguro a que me referi no tocante ao fato de que ao final da execuo voc sempre estar dentro de lmb, desde que voc tenha permisso entrar em lmb, permisso para criar um diretrio em ../lmb, haja espao em disco, ...

28

E tome de test
Ufa! Voc pensa que acabou? Ledo engano! Ainda tem uma forma de test a mais. Essa legal porque ela te permite usar padres para comparao. Estes padres atendem s normas de Gerao de Nome de Arquivos (File Name Generation, que so ligeiramente parecidas com as Expresses Regulares, mas no podem ser confundidas com estas). A diferena de sintaxe deste para o test que acabamos de ver que esse trabalha com dois pares de colchete da seguinte forma: [[ expresso ]] Onde expresso uma das que constam na tabela a seguir:

Expresses Condicionais Para Padres Expresso cadeia == padro cadeia1 = padrao cadeia1 != padrao cadeia1 < cadeia1 cadeia1 > cadeia1 expr1 && expr2 Retorna Verdadeiro se cadeia1 casa com padro Verdadeiro se cadeia1 no casa com padrao. Verdadeiro se cadeia1 vem antes de cadeia1 alfabeticamente. Verdadeiro se cadeia1 vem depois de cadeia1 alfabeticamente "E" lgico, verdadeiro se ambos expr1 e expr2 so verdadeiros

expr1 expr2 "OU" lgico, verdadeiro se expr1 ou expr2 for verdadeiro $ echo $H 13 $ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora invlida Hora invlida $H=12 $ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora invlida $ Neste exemplo, testamos se o contedo da varivel $H estava compreendido entre zero e nove ([0-9]) ou (||) se estava entre dez e doze (1[0-2]), dando uma mensagem de erro caso no fosse. Exemplos: Para saber se uma varivel tem o tamanho de um e somente um caractere, faa: $ var=a $ [[ $var == ? ]] && echo var tem um caractere var tem um caractere $ var=aa $ [[ $var == ? ]] && echo var tem um caractere $ Como voc pode imaginar, este uso de padres para comparao, aumenta muito o poderio do comando test. No incio deste papo, antes do ltimo chope, afirmamos que o comando if do interpretador Shell mais poderoso que o seu similar em outras linguagens. Agora que conhecemos todo o seu espectro de funes, diga-me: voc concorda ou no com esta assertiva?

Acaso Casa com case


Vejamos um exemplo didtico: dependendo do valor da varivel $opc o script dever executar uma uma das opes: incluso, excluso, alterao ou fim. Veja como ficaria este fragmento de script: 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 1 e 4

29

fi Neste exemplo voc viu o uso do elif com um else if, esta a sintaxe vlida e aceita, mas poderamos fazer melhor, e isto seria com o comando case, que tem a sintaxe a seguir: case $var in padrao1) cmd1 cmd2 cmdn ;; padrao2) cmd1 cmd2 cmdn ;; padraon) cmd1 cmd2 cmdn ;; esac Onde a varivel $var comparada aos padres padrao1, ..., padraon e caso um deles atenda, o bloco de comandos cmd1, ..., cmdn correspondente executado at encontrar um duplo ponto-e-vrgula (;;), quando o fluxo do programa se desviar para instruo imediatamente aps o esac. Na formao dos padres, so aceitos os seguintes caracteres: Caracteres Para Formao de Padres Caractere * ? [...] Significado Qualquer caractere ocorrendo zero ou mais vezes Qualquer caractere ocorrendo uma vez Lista de caracteres OU lgico

Para mostrar como fica melhor, vamos repetir o exemplo anterior, s que desta vez usaremos o case e no o if ... elif ... else ... fi. case $opc in 1) inclusao ;; 2) exclusao ;; 3) alteracao ;; 4) exit ;; *) echo Digite uma opo entre 1 e 4 esac Como voc deve ter percebido, eu usei o asterisco como a ltima opo, isto , se o asterisco atende a qualquer coisa, ento ele servir para qualquer coisa que no esteja no intervalo de 1 a 4. Outra coisa a ser notada que o duplo ponto-e-vrgula no necessrio antes do esac. Exemplos: Vamos agora fazer um script mais radical. Ele te dar bom dia, boa tarde ou boa noite dependendo da hora 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, mas ele tem diversas opes para seu mascaramento. Neste comando, a formatao comea com um sinal de mais (+) e os caracteres de formatao vm aps um sinal de percentagem (%), assim o %H significa a hora do sistema. Dito isso vamos ao exemplo: $ cat boasvindas.sh #!/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

30

;; esac exit Peguei pesado, n? Que nada vamos esmiuar a resoluo caso-a-caso (ou seria case-a-case?

0? | 1[01] - Significa zero seguido de qualquer coisa (?), ou (|) um seguido de zero ou um ([01]) ou seja, esta linha pegou 01, 02, ... 09, 10 e 11; 1[2-7] * - Significa um seguido da lista de dois a sete, ou seja, esta linha pegou 12, 13, ... 17; - Significa tudo 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 que nos encontrarmos aqui no botequim, t legal? - T, mas antes informe ao pessoal que est acompanhando este curso conosco como eles podem te encontrar para fazer crticas, contar piada, convidar para o chope, curso ou palestra ou at mesmo para falar mal dos polticos. - fcil, meu e-mail julio.neves@gmail.com, mas pare de me embromar que eu no vou esquecer de te passar o script para fazer. o seguinte: quero que voc faa um programa que receber como parmetro o nome de um arquivo e que quando executado salvar este arquivo com o nome original seguido de um til (~) e colocar este arquivo dentro do vi (o melhor editor que se tem notcia) para ser editado. Isso para ter sempre a ltima cpia boa deste arquivo caso o cara faa alteraes indevidas. Obviamente, voc far as crticas necessrias, como verificar se foi passado um parmetro, se o arquivo passado existe, ... Enfim, o que te der na telha e voc achar que deve constar do script. Deu pr entender? - Hum, hum... - Chico! Traz mais um sem colarinho que o cara aqui j est dando para entender! No se esquea, qualquer dvida ou falta de companhia para um chope ou at para falar mal dos polticos s mandar um e-mail para julio.neves@gmail.com. Vou aproveitar tambm para mandar o meu jab: diga para os amigos que quem estiver afim de fazer um curso porreta de programao em Shell que mande um e-mail para julio.neves@uniriotec.br para informar-se. Valeu! -- EvandroPastor - 19 Sep 2005

Papo de Botequim Parte V

Comandos de Loop (ou lao) O comando for Primeira sintaxe do comando for Segunda sintaxe do comando for Terceira sintaxe do comando for

- Fala cara! E as idias esto em ordem? J fundiu a cuca ou voc ainda aguenta mais Shell? - Guento! T gostando muito! Gostei tanto que at caprichei no exerccio que voc passou. Lembra que voc me pediu para fazer um programa que receberia como parmetro o nome de um arquivo e que quando executado salvaria este arquivo com o nome original seguido de um til (~) e colocaria este arquivo dentro do vi? - Claro que lembro, me mostre e explique como voc fez. $ cat vira #!/bin/bash # # vira - vi resguardando arquivo anterior # == = = # Verificando se foi passado 1 parametro if [ "$#" -ne 1 ] then echo "Erro -> Uso: $0 " exit 1 fi Arq=$1 # Caso o arquivo no exista, nao ha copia para ser salva if [ ! -f "$Arq" ] then vi $Arq exit 0 fi

31

# Se nao puder alterar o arquivo vou usar o vi para que? if [ ! -w "$Arq" ] then echo "Voce nao tem direito de gravacao em $Arq" exit 2 fi # Ja que esta tudo OK, vou salvar a copia e chamar o vi cp -f $Arq $Arq~ vi $Arq exit 0 - , beleza! Mas me diz uma coisa: porque voc terminou o programa com um exit 0? - Ahhh! Eu descobri que o nmero aps o exit resultar no cdigo de retorno do programa (o $?, lembra?), e desta forma, como foi tudo bem sucedido, ele encerraria com o $? = 0. Porm se voc observar, ver que caso o programa no tenha recebido o nome do arquivo ou caso o operador no tivesse direito de gravao sobre este arquivo, o cdigo de retorno ($?) seria diferente do zero. - Grande garoto, aprendeu legal, mas bom deixar claro que exit 0, simplesmente exit ou no colocar exit, produzem igualmente um cdigo de retorno ($?) igual a zero. Agora vamos falar sobre as instrues de loop ou lao, mas antes vou passar o conceito de bloco de programa. At agora j vimos alguns blocos de programa. Quando te mostrei um exemplo para fazer um cd para dentro de um diretrio que era assim: cd lmb 2> /dev/null || { mkdir lmb cd lmb } O fragmento contido entre as duas chaves ({}), forma um bloco de comandos. Tambm neste exerccio que acabamos de ver, em que salvamos o arquivo antes de edit-lo, existem vrios blocos de comandos compreendidos entre os then e os fi do if. Um bloco de comandos tambm pode estar dentro de um case, ou entre um do e um done. - Pera Julio, que do e done esse, no me lembro de voc ter falado nisso e olha estou prestando muita ateno... - Pois , ainda no havia falado porque no havia chegado o momento propcio. Todas as instrues de loop ou lao, executam os comandos do bloco compreendido entre o do e o done.

Comandos de Loop (ou lao)


As instrues de loop ou lao so o for, o while e o until que passarei a te explicar uma-a-uma a partir de agora.

O comando for
Se voc est habituado a programar, certamente j conhece o comando for, mas o que voc no sabe que o for, que uma instruo intrinseca do Shell (isto significa 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, primeiramente em portugus e depois como funciona no duro. para var em val1 val2 ... valn faa cmd1 cmd2 cmdn feito Onde a varivel var assume cada um dos valores da lista val1 val2 ... valn e para cada um desses valores executa o bloco de comandos formado por cmd1, cmd2 e cmdn Agora que j vimos o significado da instruo em portugus, vejamos a sintaxe correta:

Primeira sintaxe do comando for


for var in val1 val2 ... valn do

32

cmd1 cmd2 cmdn done Vamos direto para os exemplos, para entender direito o funcionamento deste comando. Vamos escrever um script para listar todos os arquivos do nosso diretrio separados por dois-pontos, mas primeiro veja: $ echo * ArqDoDOS.txt1 confuso incusu logado musexc musicas musinc muslist Isto , o Shell viu o asterisco (*) expandindo-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 ver como resolver o problema a que nos propuzemos: $ cat testefor1 #!/bin/bash # 1o. Prog didtico para entender o for for Arq in * do echo -n $Arq: # A opcao -n eh para nao saltar linha done Ento vamos execut-lo: $ testefor1 ArqDoDOS.txt1:confuso:incusu:logado:musexc:musicas:musinc:muslist:$ Como voc viu o Shell transformou o astersco (que odeia ser chamado de asterstico) em uma lista de arquivos separados por espaos em branco. quando o for viu aquela lista, ele disse: "Opa, lista separadas por espaos comigo mesmo!" O bloco de comandos a ser executado era somente o echo, que com a opo -n listou a varivel $Arq seguida de dois-pontos (:), sem saltar a linha. O cifro ($) do final da linha da execuo o prompt. que permaneceu na mesma linha tambm em funo da opo -n. Outro exemplo simples (por enquanto): $ cat testefor2 #!/bin/bash # 2o. Prog didtico para entender o for for Palavra in Papo de Botequim do echo $Palavra done E executando vem: $ testefor2 Papo de Botequim Como voc viu, este exemplo to bobo e simples como o anterior, mas serve para mostrar o comportamento bsico do for. Veja s a fora do for: ainda estamos na primeira sintaxe do comando 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, era s para facilitar a compreenso. No duro, as listas no so obrigatriamente separadas por espaos mas antes de prosseguir, deixa eu te mostrar como se comporta uma varivel do sistema chamada de $IFS. Repare seu contedo: $ echo "$IFS" | od -h 0000000 0920 0a0a 0000004 Isto , mandei a varivel (protegida da interpretao do Shell pelas aspas) para um dump hexadecimal (od -h) e resultou: Contedo da Varivel $IFS Hexadecimal 09 20 0a Significado <TAB> <ESPAO> <ENTER>

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

33

Preste ateno na dica a seguir para entender a construo deste comando cat:

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 fim do echo. E desta forma, mais uma vez pudemos notar que os trs caracteres esto presentes naquela varivel. Agora veja voc, IFS significa Inter Field Separator ou, traduzindo, separador entre campos. Uma vez entendido isso, eu posso afirmar (porque vou provar) que o comando for no usa listas separadas por espaos em branco, mas sim pelo contedo da varivel $IFS, cujo valor padro (default) so esses caracteres que acabamos de ver. Para comprovarmos isso, vamos mostrar um script que recebe o nome do artista como parmetro e lista as msicas que ele executa, mas primeiramente vamos ver como est o nosso arquivo musicas: $ cat musicas album 1^Artista1~Musica1:Artista2~Musica2 album 2^Artista3~Musica3:Artista4~Musica4 album 3^Artista5~Musica5:Artista6~Musica6 album 4^Artista7~Musica7:Artista1~Musica3 album 5^Artista9~Musica9:Artista10~Musica10 Em cima deste "leiaute" foi desenvolvido o script a seguir: $ cat listartista #!/bin/bash # Dado um artista, mostra as suas musicas if [ $# -ne 1 ] then echo Voce deveria ter passado um parametro exit 1 fi IFS=" :" for ArtMus in $(cut -f2 -d^ musicas) do echo "$ArtMus" | grep $1 && echo $ArtMus | cut -f2 -d~ done O script, como sempre, comea testando se os parmetros foram passados corretamente, em seguida o IFS foi setado para <ENTER> e dois-pontos (:) (como demonstram as aspas em linha diferentes), porque ele que separa os blocos Artistan~Musicam. Desta forma, a varivel $ArtMus ir receber cada um destes 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 execut-lo: $ listartista Artista1 Artista1~Musica1 Musica1 Artista1~Musica3 Musica3 Artista10~Musica10 Musica10 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:). Neste caso o $1 seria Perereca e o resto deste lindo nome seria ignorado na pesquisa. Para que isso no ocorresse, eu deveia passar o nome do artista entre aspas (") ou alterar $1 por $@ (que significa todos os parmetros passados), que a melhor soluo, mas neste caso eu teria que modificar a crtica dos parmetros e o grep. A nova crtica no seria se eu passei um parmetro, mas pelo menos um parmetro e quanto ao grep, veja s o que resultaria aps a substituio do $* (que entraria no lugar do $1) pelos parmetros: echo "$ArtMus" | grep perereca & peteleca O que resultaria em erro. O correto seria: echo "$ArtMus" | grep -i "perereca & peteleca" Onde foi colocado a opo -i para que a pesquisa ignorasse maisculas e minsculas e as aspas tambm foram inseridas para que o nome do artista fosse visto como uma s cadeia monoltica.

34

Ainda falta consertar o erro dele ter listado o Artista10. Para isso o melhor dizer ao grep que a cadeia est no incio de $ArtMus (a expresso regular para dizer que est no incio ^) e logo aps vem um til (~). necessrio tambm que se redirecione a sada do grep para /dev/null para que os blocos no sejam mais listados. Veja ento a nova (e definitiva) cara do programa: $ cat listartista #!/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 Que executando vem: $ listartista Artista1 Musica1 Musica3

Segunda sintaxe do comando for


for var do cmd1 cmd2 cmdn done - U, sem o in como ele vai saber que valor assumir? - Pois , n? Esta construo a primeira vista parece xquisita mas bastante simples. Neste caso, var assumir um-a-um cada um dos parmetros passados para o progama. Vamos logo aos exemplos 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 musicas como parametro e # lista os interpretes. Se o nome for composto, deve # ser passado entre aspas. # ex. "Eu nao sou cachorro nao" "Churrasquinho de Mae" # if [ $# -eq 0 ] then echo Uso: $0 musica1 [musica2] ... [musican] exit 1 fi IFS=" :" for Musica do echo $Musica Str=$(grep -i "$Musica" musicas) || { echo " No encontrada" continue } for ArtMus in $(echo "$Str" | cut -f2 -d^) do echo " $ArtMus" | grep -i "$Musica" | cut -f1 -d~ done done Da mesma forma que os outros, comeamos o exerccio com uma crtica sobre os parmetros recebidos, em seguida fizemos um for

35

em que a varivel $Musica receber cada um dos parmetros passados, colocando em $Str todos os lbuns que contm as msicas passadas. Em seguida, o outro for pega cada bloco Artista~Musica nos registros que esto em $Str e lista cada artista que execute aquela msica. Como sempre vamos execut-lo para ver se funciona mesmo: $ listamusica musica3 Musica4 "Eguinha Pocot" musica3 Artista3 Artista1 Musica4 Artista4 Eguinha Pocot No encontrada A listagem ficou feinha porque ainda no sabemos formatar a sada, mas qualquer dia desses, quando voc souber posicionar o cursor, fazer negrito, trabalhar com cores e etc, faremos esta listagem novamente usando todas estas perfumarias e ela ficar muito fashion. 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 os outros?" Para fazer isso existem duas formas: 1 - Com a primeira sintaxe que vimos, como nos exemplos a seguir direto no prompt: $ for i in $(seq 9) > do > echo -n "$i " > done 1 2 3 4 5 6 7 8 9 Neste a varivel i assumiu os inteiros de 1 a 9 gerados pelo comando seq e a opo -n do echo foi usada para no saltar linha a cada nmero listado (sinto-me ecologicamente correto por no gastar um monte de papel da revista quando isso pode ser evitado). Ainda usando o for com seq: $ for i in $(seq 3 9) > do > echo -n "$i " > done 4 5 6 7 8 9 Ou ainda na forma mais completa do seq: $ for i in $(seq 0 3 9) > do > echo -n "$i " > done 0 3 6 9 2 A outra forma de fazer o desejado com uma sintaxe muito semelhante ao for da linguagem C, como veremos a seguir.

Terceira sintaxe do comando for


for ((var=ini; cond; incr)) do cmd1 cmd2 cmdn done Onde: var=ini - Significa que a varivel var comear de um valor inicial ini; cond - Siginifica que o loop ou lao do for ser executado enquanto var no atingir a condio cond; incr - Significa o incremento que a varivel var sofrer em cada passada do loop. Como sempre vamos aos exemplos que a coisa fica mais fcil: $ for ((i=1; i<=9; i++)) > do > echo -n "$i " > done 1 2 3 4 5 6 7 8 9 Neste caso a varivel i partiu do valor inicial 1, o bloco de comando (neste caso somente o echo) ser executado enquanto i menor ou igual (<=) a 9 e o incremento de i ser de 1 a cada passada do loop.

36

Repare que no for propriamente dito (e no no bloco de comandos) no coloquei um cifro ($) antes do i, e a notao para incrementar (i++) diferente do que vimos at agora. Isto porque o uso de parnteses duplos (assim como o comando let) chama o interpretador aritmtico do Shell, que mais tolerante. Como me referi ao comando let, s para mostrar como ele funciona e a versatilidade do for, vamos fazer a mesma coisa, porm omitindo a ltima parte do escopo do for, passando-a para o bloco de comandos. $ for ((; i<=9;)) > do > let i++ > echo -n "$i " > done 1 2 3 4 5 6 7 8 9 Repare que o incremento saiu do corpo do for e passou para o bloco de comandos, repare tambm que quando usei o let, no foi necessrio sequer inicializar a varivel $i. Veja s os comandos a seguir dados diretamente no prompt para mostrar o que acabo de falar: $ echo $j $ let j++ $ echo $j 1 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 ficam 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 - Pois amigo, tenho certeza que voc j tomou um xarope do comando for. Por hoje chega, na prxima vez que nos encontrarmos falaremos sobre outras instrues de loop, mas eu gostaria que at l voc fizesse um pequeno script para contar a quantidade de palavras de um arquivo texto, cujo nome seria recebido por parmetro. OBS: Essa contagem tem de ser feita usando o comando for para se habituar ao seu uso. No vale usar o wc -w. - A Chico! Traz a saideira. No se esquea, qualquer dvida ou falta de companhia para um chope ou at para falar mal dos polticos s mandar um e-mail para julio.neves@gmail.com. Vou aproveitar tambm para mandar o meu jab: diga para os amigos que quem estiver afim de fazer um curso porreta de programao em Shell que mande um e-mail para julio.neves@uniriotec.br para informar-se. Valeu!

Papo de botequim parte VI

Comandos de Loop ou Lao (Continuao) Um Pouco Mais de for e Matemtica O comando while O comando until Atalhos no loop

Comandos de Loop ou Lao (Continuao)


- Fala 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?

37

- Claro! T empolgado com essa linguagem, eu fiz da forma que voc pediu, isto sem usar o comando wc porque seno era mais mole ainda. Olha s como eu fi... - pa! Perai! Voc realmente est fissurado na linguagem, mas eu t sequinho pra tomar um chope. A Chico, traz dois por favor. Um sem colarinho! - Como eu ia dizendo olha a forma que eu fiz. muito fcil... $ cat contpal.sh #!/bin/bash # Script meramente pedaggico cuja # funo contar a qtd de palavras # de um arquivo. Supe-se que as # palavras esto separadas entre si # por espao, ou . if [ $# -ne 1 ] then echo uso: $0 /caminho/do/arquivo exit 2 fi Cont=0 for Palavra in $(cat $1) do Cont=$((Cont+1)) done echo O arquivo $1 tem $Cont palavras. Ou seja, o programa comea como sempre verificando 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 (default) 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. Agora vamos testar o programa passando este arquivo como parmetro: $ contpal.sh ArqDoDOS.txt O arquivo ArqDoDOS.txt tem 14 palavras - Beleza, funcionou legal!

Um Pouco Mais de for e Matemtica


Voltando vaca fria, na ltima vez que aqui estivemos, terminamos o nosso papo mostrando o loop de for a seguir: for ((; i<=9;)) do let i++ echo -n "$i " done Uma vez que chegamos neste ponto, creio ser bastante interessante citar que o Shell trabalha com o conceito de "Expanso Aritmtica" (Arithmetic Expansion), da qual vou falar rapidamente porque na seo Tira Gosto isso est muito bem mastigado. A expanso aritmtica acionada por uma construo da forma: $((expresso)) ou let expresso No ltimo for citado usei a expanso das duas formas, mas no poderamos seguir adiante sem saber que a expresso pode ser de uma das listadas a seguir:

Expanso Aritmtica

38

Expresso id++ id-++id -id ** * / % + <= >= < > == != && ||

Resultado ps-incremento e ps-decremento de variveis pr-incremento e pr-decremento de variveis exponenciao multiplicao, diviso, resto da diviso adio, subtrao comparao igualdade, desigualdade E lgico

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

O comando while
Todos os programadores conhecem este comando, porque ele comum a todas as linguagens e nelas, o que normalmente ocorre que um bloco de comandos executado, enquanto (enquanto em ingles while) uma determinada condio for verdadeira. Pois bem, isto o que ocorre 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 fica assim: while comando do cmd1 cmd2 ... cmdn done e desta forma o bloco de comandos formado pelas instrues cmd1, cmd2,... e cmdn executado enquanto a execuo da instruo comando for bem sucedida. Suponha a seguinte cena: tem uma tremenda gata me esperando e eu preso no trabalho sem poder sair porque o meu chefe, que um p no saco (alis chefe-chato uma redundncia, n?:), ainda estava na sua sala, que fica bem na minha passagem para a rua. Ele comeou a ficar com as antenas (provavelmente instaladas na cabea dele pela esposa) ligadas 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 fiz, 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 hesite, d exit e v a luta Neste scriptizinho, o comando while testa o pipeline composto pelo who e pelo grep e que ser verdadeiro enquanto o grep localizar a palavra xefe na sada do who. Desta forma, o script dormir por 30 segundos enquanto o chefe estiver logado (Argh!). Assim que ele se desconectar do servidor, o fluxo do script sair do loop e dar a to ansiada mensagem de liberdade. Quando o executei adivinha o que aconteceu? $ logaute.sh xefe pts/0 Jan 4 08:46 (10.2.4.144) xefe pts/0 Jan 4 08:47 (10.2.4.144) ... xefe pts/0 Jan 4 08:52 (10.2.4.144) Isto a cada 30 segundos seria enviado para a tela a sada do grep, o que no seria legal j que poluiria a tela do meu micro e a mensagem esperada poderia passar desapercebida. Para evitar isso j sabemos que a sada do pipeline tem que ser redirecionada para /dev/null.

39

$ 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 hesite, d exit e v a 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 mostar uma nova varivel do sistema. Veja estes comandos diretos no prompt: $ sleep 10& [1] 16317 $ echo $! 16317 [1]+ Done sleep 10 $ echo $! 16317 Isto , criei um processo em background para dormir por 10 segundos, somente para mostrar que a varivel $! guarda o PID (Process IDentification) do ltimo processo em background, mas repare aps a linha do done, que a varivel reteve o valor mesmo aps o trmino deste processo. Bem sabendo isso j fica mais fcil de monitorar qualquer processo em background. Veja s como: $ 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 Este 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 a opo -q (quiet) do grep, ela serve para tranform-lo num comando mineiro, isto , para o grep "trabalhar em silncio". O mesmo resultado poderia ser obtido se a linha fosse while ps | grep $! > /dev/null, como nos exemplos que vimos at agora. No esquea: o Bash disponibiliza a varivel $! que possui o PID (Process IDentification) do ltimo processo executado em background.

Vamos melhorar o musinc, que o 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 suficiente 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 Onde prompt de leitura o texto que voc quer que aparea escrito na tela, e quando o operador teclar o dado, ele ir para a varivel var. Por exemplo: $ read -p "Ttulo do lbum: " Tit Bem, uma vez entendido isso, vamos especificao do nosso problema: faremos um programa que inicialmente ler o nome do lbum e em seguida fara um loop de leitura, pegando a msica e o artista. Este loop termina quando for informada uma msica vazia, isto , ao ser solicitada a digitao da msica, o operador d 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. Vamos ver como ficou: $ cat musinc #!/bin/bash # Cadastra CDs (versao 4) # clear read -p "Ttulo do lbum: " Tit [ "$Tit" ] || exit 1 # Fim da execuo se ttulo vazio

40

if grep "^$Tit\^" musicas > /dev/null then echo Este lbum j est cadastrado exit 1 fi 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 exemplo, comea com a leitura do ttulo do lbum, que se no for informado, terminar a execuo do programa. Em seguida um grep procura no incio (^) de cada registro de musicas, 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 de while simples, cujo nico destaque o fato de estar armazenando o artista da msica anterior na varivel $oArt que s ter o seu contedo alterado, quando algum dados for informado para a varivel $Art, isto , quando no teclou-se um simples <ENTER> para manter o artista anterior. O que foi visto at agora sobre o while foi muito pouco. Este comando muito utilizado, principalmente para leitura de arquivos, porm nos falta bagagem para prosseguir. Depois que aprendermos a ler, veremos esta instruo mais a fundo. Leitura de arquivo significa ler um-a-um todos os registros, o que sempre uma operao lenta. Fique atento para no usar o while quando seu uso for desnecessrio. O Shell tem ferramentas como o sed e a famlia grep que vasculham arquivos de forma otimizada sem ser necessrio o uso de comandos de loop para faz-lo registro a registro (ou at palavra a palavra).

O comando until
O comando until funciona exatamente igual 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 enquanto o while executa o bloco de intrues do loop enquanto um comando for bem sucedido, 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: until comando do cmd1 cmd2 ... cmdn done E desta 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, o 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. Baseado neste principio belicoso que o meu chefe, desenvolveu, no mesmo servidor que eu executava o logaute.sh um script para controlar o meu horrio de chegada. Um dia deu um problema da rede, ele me pediu para dar uma olhada no micro dele e me deixou sozinho em sua sala. Imediatamente comecei a bisbilhotar seus arquivos - porque guerra guerra - e veja s o que descobri: $cat chegada.sh #!/bin/bash until who | grep julio

41

do sleep 30 done echo $(date "+ Em %d/%m s %H:%Mh") >> relapso.log Olha que safado! O cara estava montando um log com os horrios que eu chegava, e ainda por cima chamou o arquivo que me monitorava de relapso.log! O que ser que ele quis dizer com isso? Neste script, o pipeline who | grep julio, ser bem sucedido somente quando julio for encontrado no comando who, isto , quando eu me "logar" no servidor. At que isso acontea, o comando sleep, que forma o bloco de instrues do until, por o programa em espera por 30 segundos. Quando este loop encerrar-se, ser dada uma mensagem para o relapso.log (ARGHH!). Supondo que no dia 20/01 eu me loguei s 11:23 horas, a mensagem seria a seguinte: Em 20/01 s 11:23h Quando vamos cadastrar msicas, o ideal seria que pudssemos cadastrar diversos CDs, e na ltima verso que fizemos do musinc, isso no ocorre, a cada CD que cadastramos o programa termina. Vejamos como melhor-lo:

$ cat musinc #!/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 flag de sada else if grep "^$Tit\^" musicas > /dev/null then echo Este lbum j est cadastrado exit 1 fi Reg="$Tit^" Cont=1 oArt= 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 fi done 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 valor (no caso coloquei 1 mas poderia ter colocado qualquer coisa. O importante que no seja vazia) para sair deste loop, terminando desta forma o programa. No resto, o script idntico sua verso anterior.

Atalhos no loop
Nem sempre um ciclo de programa, compreendido entre um do e um done, sai pela porta da frente. Em algumas oportunidades,

42

temos que colocar um comando que aborte de forma controlada este loop. De maneira inversa, algumas vezes desejamos que o fluxo de execuo do programa volte antes de chegar ao done. Para isto, temos respectivamente, os comandos break (que j vimos rapidamente nos exemplos do comado while) e continue, que funcionam da seguinte forma: O que eu no havia dito anteriormente que nas suas sintaxes genricas eles aparecem da seguinte forma: break [qtd loop] e continue [qtd loop] Onde qtd loop representa a quantidade dos loops mais internos sobre os quais os comandos iro atuar. Seu valor default 1.

Duvido que voc nunca tenha deletado um arquivo e logo aps deu um tabefe na testa se xingando porque no devia t-lo removido. Pois , na dcima vez que fiz esta besteira, criei um script para simular uma lixeira, isto , quando mando remover um (ou vrios) arquivo(s), o programa "finge" que removeu-o, mas no duro o que fez foi mand-lo(s) para o diretrio /tmp/LoginName_do_usuario. Chamei este programa de erreeme e no /etc/profile coloquei a seguinte linha: alias rm=erreeme O programa era assim: $ cat erreeme #/bin/bash # # Salvando Copia de Arquivo Antes de Remove-lo # if [ $# -eq 0 ] # Tem de ter um ou mais arquivos para remover then echo "Erro -> Uso: erreeme arq [arq] ... [arq]" echo " O uso de metacaracteres e permitido. Ex. erreeme arq*" exit 1 fi MeuDir="/tmp/$LOGNAME" # Variavel do sist. Contm o nome do usurio. if [ ! -d $MeuDir ] # Se no existir o meu diretrio sob o /tmp... then mkdir $MeuDir # Vou cria-lo fi if [ ! -w $MeuDir ] # Se no posso gravar no diretrio... then echo Impossivel salvar arquivos em $MeuDir. Mude permissao... exit 2 fi Erro=0 # Variavel para indicar o cod. de retorno do prg for Arq # For sem o "in" recebe os parametros passados do if [ ! -f $Arq ] # Se este arquivo no existir... then echo $Arq nao existe. Erro=3 continue # Volta para o comando for fi DirOrig=`dirname $Arq` # Cmd. dirname informa nome do dir de $Arq

43

if [ ! -w $DirOrig ] # Verifica permisso de gravacaoo no diretrio then echo Sem permissao de remover no diretorio de $Arq Erro=4 continue # Volta para o comando for fi if [ "$DirOrig" = "$MeuDir" ] # Se estou "esvaziando a lixeira"... then echo $Arq ficara sem copia de seguranca rm -i $Arq # Pergunta antes de remover [ -f $Arq ] || echo $Arq removido # Ser que o usuario removeu? continue fi cd $DirOrig # Guardo no fim do arquivo o seu diretorio pwd >> $Arq # original para usa-lo em um script de undelete mv $Arq $MeuDir # Salvo e removo echo $Arq removido done exit $Erro # Passo eventual numero do erro para o codigo de retorno Como voc pode ver, a maior parte do script formada por pequenas criticas aos parmetros informados, mas como o script pode ter recebido diversos arquivos para remover, a cada arquivo que no se encaixa dentro do especificado, h um continue, para que a sequncia 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 deles, os outros no so removidos, no ? Ento, o continue foi usado para evitar que um improprio desses 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 desafio: faa-o em casa e me traga para discutirmos no nosso prximo encontro aqui no boteco. - Poxa, mas nesse eu acho que vou danar, pois no sei nem como comear... - Cara, este programa como tudo que se faz em Shell, extremamente fcil, para ser feito em, no mximo 10 linhas. No se esquea que o arquivo est salvo em /tmp/$LOGNAME e que a sua ltima linha o diretrio em que ele residia antes de ser "removido". Tambm no se esquea de criticar se foi passado o nome do arquivo a ser removido. - eu vou tentar, mas sei no... - Tenha f irmo, eu t te falando que mole! Qualquer dvida s me passar um e-mail para julio.neves@gmail.com. Agora chega de papo que eu j estou de goela seca de tanto falar. Me acompanha no prximo chope ou j vai sair correndo para fazer o script que passei? - Deixa eu pensar um pouco... - Chico, traz mais um chope enquanto ele pensa! No se esquea, qualquer dvida ou falta de companhia para um chope ou at para falar mal dos polticos s mandar um e-mail para julio.neves@gmail.com. Vou aproveitar tambm para mandar o meu jab: diga para os amigos que quem estiver afim de fazer um curso porreta de programao em Shell que mande um e-mail para julio.neves@uniriotec.br para informar-se. Valeu!

Papo de botequim parte VII


O comando tput E agora podemos ler os dados da tela Vamos ler arquivos?

- Cumequi rapaz! Derreteu os pensamentos para fazer o scriptizinho que eu te pedi? - , eu realmente tive de colocar muita pensao na tela preta mas acho que consegui! Bem, pelo menos no testes que fiz a coisa funcionou, mas voc tem sempre que botar chifres em cabea de cachorro! - No bem assim, programar em shell muito fcil, o que vale so as dicas e macetes que no so triviais. As correes que te fao, so justamente para mostr-los. Mas vamos pedir dois chopes enquanto dou uma olhadela no teu script.

44

- A Chico, traz dois. No esquea que um sem colarinho.

$ cat restaura #!/bin/bash # # Restaura arquivos deletados via erreeme # if [ $# -eq 0 ] then echo "Uso: $0 " exit 1 fi # Pega nome do diretrio original na ltima linha Dir=`tail -1 /tmp/$LOGNAME/$1` # O grep -v exclui ltima linha e cria o # arquivo com diretorio e nome originais grep -v $Dir /tmp/$LOGNAME/$1 > $Dir/$1 # Remove arquivo que jah estava moribundo rm /tmp/$LOGNAME/$1 - Pera, deixa ver se entendi. Primeiramente voc coloca na varivel Dir a ltima linha do arquivo cujo nome formado por /tmp/nome do operador ($LOGNAME)/parmetro passado com nome do arquivo a ser restaurado ($1). Em seguida o grep -v que voc montou exclui a linha em que estava o nome do diretrio, isto , sempre a ltima e manda o restante do arquivo, que seria assim o arquivo j limpo, para o diretrio original e depois remove o arquivo da "lixeira"; S E N S A C I O N A L! Impecvel! Zero erro! Viu? voc j est pegando as manhas do shell! - Ento vamul chega de lesco-lesco e bl-bl-bl, de que voc vai falar hoje? - t vendo que o bichinho do Shell te pegou. Que bom, mas vamos ver como se pode (e deve) ler dados e formatar telas e primeiramente vamos entender um comando que te d todas as ferramentas para voc formatar a sua tela de entrada de dados.

O comando tput
O maior uso deste comando para posicionar o cursor na tela, mas tambm muito usado para apagar dados da tela, saber a quantidade de linhas e colunas para poder posicionar corretamente um campo, apagar um campo cuja crtica detectou como errado. Enfim, quase toda a formatao da tela feita por este comando. Uns poucos atributos do comando tput podem eventualmente no funcionar se o modelo de terminal definido pela varivel $TERM no tiver esta facilidade incorporada. Na tabela a seguir, apresenta os principais atributos do comando e os efeitos executados sobre o terminal, mas veja bem existem muito mais do que esses, veja s: $ tput it 8 Neste exemplo eu recebi o tamanho inicial da <TAB> ( Initial T ab), mas me diga: para que eu quero saber isso? Se voc quiser saber tudo sobre o comando tput (e olha que coisa que no acaba mais), veja em: http://www.cs.utah.edu/dept/old/texinfo/tput/tput.html#SEC4. Principais Opes do Comando tput Opes do tput Efeito cup lin col CUrsor Position - Posiciona o cursor na linha lin e coluna col. A origem zero bold rev smso smul blink sgr0 reset lines cols el Coloca a tela em modo de nfase Coloca a tela em modo de vdeo reverso Idntico ao anterior A partir desta instruo, os caracteres teclados aparecero sublinhados na tela Os caracteres teclados aparecero piscando Aps usar um dos atributos acima, use este para restaurar a tela ao seu modo normal Limpa o terminal e restaura suas definies de acordo com o terminfo ou seja, o terminal volta ao padro definido pela varivel $TERM Devolve a quantidade de linhas da tela no momento da instruo Devolve a quantidade de colunas da tela no momento da instruo Erase Line - Apaga a linha a partir da posio do cursor

45

ed il n dl n ech n sc

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 Erase CHaracters - Apaga n caracteres a partir da posio do cursor Save Cursor position - Salva a posio do cursor

rc Restore Cursor position - Coloca o cursor na posio marcada pelo ltimo sc Vamos fazer um programa bem besta (e portanto fcil) para mostrar alguns atributos deste comando. o famoso e famigerado Al Mundo s que esta frase ser escrita no centro da tela e em vdeo reverso e aps isso, o cursor voltar para a posio em que estava antes de escrever esta to criativa frase. Veja:

$ cat alo.sh #!/bin/bash # Script bobo para testar # o comando tput (versao 1) Colunas=`tput cols` # Salvando quantidade colunas Linhas=`tput lines` # Salvando quantidade linhas Linha=$((Linhas / 2)) # Qual eh a linha do meio da tela? Coluna=$(((Colunas - 9) / 2)) # Centrando a mensagem na tela tput sc # Salvando posicao do cursor tput cup $Linha $Coluna # Posicionando para escrever tput rev # Video reverso echo Al Mundo tput sgr0 # Restaura video ao normal tput rc # Restaura cursor aa posio original Como o programa j est todo comentado, acho que a nica explicao necessria seria para a linha em que criada a varivel Coluna e o estranho ali aquele nmero 9, mas ele o tamanho da cadeia que pretendo escrever (Al Mundo). Desta forma este programa somente conseguiria centrar cadeias de 9 caracteres, mas veja isso: $ var=Papo $ echo ${#var} 4 $ var="Papo de Botequim" $ echo ${#var} 16 Ahhh, melhorou! Ento agora sabemos que a construo ${#variavel} devolve a quantidade de caracteres de variavel. Assim sendo, vamos otimizar o nosso programa para que ele escreva em vdeo reverso, no centro da tela a cadeia passada como parmetro e depois o cursor volte posio que estava antes da execuo do script. $ cat alo.sh #!/bin/bash # Script bobo para testar # o comando tput (versao 2) Colunas=`tput cols` # Salvando quantidade colunas Linhas=`tput lines` # Salvando quantidade linhas Linha=$((Linhas / 2)) # Qual eh a linha do meio da tela? Coluna=$(((Colunas - ${#1}) / 2)) # Centrando a mensagem na tela tput sc # Salvando posicao do cursor tput cup $Linha $Coluna # Posicionando para escrever tput rev # Video reverso echo $1 tput sgr0 # Restaura video ao normal tput rc # Restaura cursor aa posio original Este script igual ao anterior, s que trocamos o valor fixo da verso anterior (9), por ${#1}, onde este 1 o $1 ou seja, esta construo devolve o tamanho do primeiro parmetro passado para o programa. Se o parmetro que eu quiser passar tiver espaos em branco, teria que coloc-lo todo entre aspas, seno o $1 seria somente o primeiro pedao. Para evitar este aborrecimento, s substituir o $1 por $*, que como sabemos o conjunto de todos os parmetros. Ento aquela linha ficaria assim: Coluna=`$(((Colunas - ${#*}) / 2))` #Centrando a mensagem na tela e a linha echo $1 passaria a ser echo $*. Mas no esquea de qdo executar, passar a frase que vc desja centrar como parmetro.

46

E agora podemos ler os dados da tela


Bem a partir de agora vamos aprender tudo sobre leitura, s no posso ensinar a ler cartas e bzios porque se eu soubesse, estaria rico, num pub londrino tomando scotch e no em um boteco desses tomando chope. Mas vamos em frente. Da ltima vez que nos encontramos aqui eu j dei uma palinha sobre o comando read. Para comearmos a sua analise mais detalhada. 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 Como voc viu, o read recebe uma lista separada por espaos em branco e coloca cada item desta lista em uma varivel. Se a quantidade de variveis for menor que a quantidade de itens, a ltima varivel recebe o restante. Eu disse lista separada por espaos em branco? Agora que voc j conhece tudo sobre o $IFS (Inter Field Separator) que eu te apresentei quando falvamos do comando for, ser que ainda acredita nisso? Vamos testar direto no prompt: $ 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" Viu, estava furado! O read l uma lista, assim como o for, separada pelos caracteres da varivel $IFS. Ento veja como isso pode facilitar a sua vida: $ grep julio /etc/passwd julio:x:500:544:Julio C. Neves - 7070:/home/julio:/bin/bash $ oIFS="$IFS" # Salvando IFS $ IFS=: $ grep julio /etc/passwd | read lname lixo uid gid coment home shell $ echo -e "$lname\n$uid\n$gid\n$coment\n$home\n$shell" julio 500 544 Julio C. Neves - 7070 /home/julio /bin/bash $ IFS="$oIFS" # Restaurando IFS 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 para que o \n fosse entendido como um salto 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 a seguir: Opes do comando read no Bash

47

Opo -p prompt -n num -t seg

Ao Escreve o prompt antes de fazer a leitura L at num caracteres Espera seg segundos para que a leitura seja concluda

-s O que est sendo teclado no aparece na tela E agora direto aos exemplos curtos para demonstrar estas opes. Para ler um campo "Matrcula": $ echo -n "Matricula: "; read Mat # -n nao salta linha Matricula: 12345 $ echo $Mat 12345

Ou simplificando com a opo -p: $ read -p "Matricula: " Mat Matricula: 12345 $ echo $Mat 12345 Para ler uma determinada quantidade de caracteres: $ read -n5 -p"CEP: " Num ; read -n3 -p- Compl CEP: 12345-678$ $ echo $Num 12345 $ echo $Compl 678 Neste exemplo fizemos dois read: um para a primeira parte do CEP e outra para o seu complemento, deste modo formatando a entrada de dados. O cifro ($) aps o ltimo algarismo teclado, porque o read no tem o new-line implcito por default como o tem o echo. Para ler que at um determinado tempo se esgote (conhecido como time out): $ read -t2 -p "Digite seu nome completo: " Nom || echo 'Eta moleza!' Digite seu nome completo: JEta moleza! $ echo $Nom $ Obviamente isto foi uma brincadeira, pois s tinha 3 segundos para digitar o meu nome completo e s me deu tempo de teclar um J (aquele colado no Eta), mas serviu para mostrar duas coisas: 1. O comando aps o par de barras verticais (||) (o ou lgico, lembra-se?) ser executado caso a digitao no tenha sido concluda no tempo estipulado; 2. A varivel Nom permaneceu vazia. Ela ser valorada somente quando o <ENTER> for teclado. Para ler um dado sem ser exibido na tela: $ read -sp "Senha: " Senha: $ echo $REPLY segredo :) Aproveitei um erro para mostrar um macete. Quando escrevi a primeira linha, esqueci de colocar o nome da varivel que iria receber a senha, e s notei quando ia listar o seu valor. Felizmente a varivel $REPLY do Bash, possui a ltima cadeia lida e me aproveitei disso para no perder a viagem. Teste voc mesmo o que acabei de fazer. Mas o exemplo que dei, era para mostrar que a opo -s impede o que est sendo teclado de ir para a tela. Como no exemplo anterior, a falta do new-line fez com que o prompt de comando ($) permanecesse na mesma linha. Bem, agora que sabemos ler da tela vejamos como se l os dados dos arquivos.

Vamos ler arquivos?


Como eu j havia lhe dito, e voc deve se lembrar, o while testa um comando e executa um bloco de instrues enquanto este comando for bem sucedido. Ora quando voc est lendo um arquivo que lhe d permisso de leitura, o read s ser mal sucedido quando alcanar o EOF (end of file), desta forma podemos ler um arquivo de duas maneiras: 1 - Redirecionando a entrada do arquivo para o bloco do while assim: while read Linha do

48

echo $Linha done < arquivo 2 - Redirecionando a sada de um cat para o while, da seguinte maneira: cat arquivo | while read Linha do echo $Linha done Cada um dos processos tem suas vantagens e desvantagens: Vantagens do primeiro processo:

mais rpido; No necessita de um subshell para assisti-lo; Em um bloco de instrues grande, o redirecionamento fica pouco visvel o que por vezes prejudica a vizualizao do cdigo; Como o nome do arquivo est antes do while, mais fcil a vizualizao do cdigo. O Pipe (|) chama um subshell para interpret-lo, tornando o processo mais lento, pesado e por vezes problemtico (veja o exemplo a seguir).

Desvantagem do primeiro processo:

Vantagem do segundo processo:

Desvantagens do segundo processo:

Para ilustrar o que foi dito, veja estes exemplos a seguir: $ cat readpipe.sh #!/bin/bash # readpipe.sh # Exemplo de read passando arquivo por pipe. Ultimo="(vazio)" cat $0 | # Passando o arq. do script ($0) p/ while while read Linha do Ultimo="$Linha" echo "-$Ultimo-" done echo "Acabou, ltimo=:$Ultimo:" Vamos ver sua execuo: $ readpipe.sh -#!/bin/bash-# readpipe.sh-# Exemplo de read passando arquivo por pipe.--Ultimo="(vazio)"-cat $0 | # Passando o arq. do script ($0) p/ while-while read Linha-do-Ultimo="$Linha"-echo "-$Ultimo-"-done-echo "Acabou, ltimo=:$Ultimo:"Acabou, ltimo=:(vazio): Como voc viu, o script lista todas as suas prprias linhas com um sinal de menos (-) antes e outro depois de cada, e no final exibe o contedo da varivel $Ultimo. Repare no entanto que o contedo desta varivel permanece como (vazio). - U ser que a varivel no foi atualizada? - Foi, e isso pode ser comprovado porque a linha echo "-$Ultimo-" lista corretamente as linhas. - Ento porque isso aconteceu? - Por que como eu disse, o bloco de instrues redirecionado pelo pipe (|) executado em um subshell e l as variveis so atualizadas. Quando este subshell termina, as atualizaes das variveis vo para os pncaros do inferno junto com ele. Repare que

49

vou fazer uma pequena mudana nele, passando o arquivo por redirecionamento de entrada (<) e as coisas passaro a funcionar na mais perfeita ordem: $ cat redirread.sh #!/bin/bash # redirread.sh # Exemplo de read passando arquivo por pipe. Ultimo="(vazio)" while read Linha do Ultimo="$Linha" echo "-$Ultimo-" done < $0 # Passando o arq. do script ($0) p/ while echo "Acabou, ltimo=:$Ultimo:" E veja a sua perfeita execuo: $ redirread.sh -#!/bin/bash-# redirread.sh-# Exemplo de read passando arquivo por pipe.--Ultimo="(vazio)"-while read Linha-do-Ultimo="$Linha"-echo "-$Ultimo-"-done < $0 # Passando o arq. do script ($0) p/ while-echo "Acabou, ltimo=:$Ultimo:"Acabou, ltimo=:echo "Acabou, ltimo=:$Ultimo:": Bem amigos da Rede Shell, para finalizar o comando read s falta mais um pequeno e importante macete que vou mostrar utilizando um exemplo prtico. Suponha que voc queira listar na tela um arquivo e a cada dez registros esta listagem pararia para que o operador pudesse ler o contedo da tela e ela s voltasse a rolar (scroll) aps o operador digitar qualquer tecla. Para no gastar papel (da Linux Magazine) pra chuchu, vou fazer esta listagem na horizontal e o meu arquivo (numeros), tem 30 registros somente com nmeros seqnciais. Veja: $ seq 30 > numeros $ cat 10porpag.sh #!/bin/bash # Prg de teste para escrever # 10 linhas e parar para ler # Verso 1 while read Num do let ContLin++ # Contando... echo -n "$Num " # -n para nao saltar linha ((ContLin % 10)) > /dev/null || read done < numeros Na tentativa de fazer um programa genrico criamos a varivel $ContLin (por que na vida real, os registros no so somente nmeros seqenciais) e parvamos para ler quando o resto da diviso por 10 fosse zero (mandando a sada para /dev/null de forma a no aparecer na tela, sujando-a). Porm, quando fui executar deu a seguinte zebra: $ 10porpag.sh 1 2 3 4 5 6 7 8 9 10 12 13 14 15 16 17 18 19 20 21 23 24 25 26 27 28 29 30 Repare que faltou o nmero 11 e a listagem no parou no read. O que houve foi que toda a entrada do loop estava redirecionada do arquivo numeros e desta forma, a leitura foi feita em cima deste arquivo, desta forma perdendo o 11 (e tambm o 22). Vamos mostrar ento como deveria ficar para funcionar a contento: $ cat 10porpag.sh #!/bin/bash # Prg de teste para escrever # 10 linhas e parar para ler # Verso 2 while read Num do let ContLin++ # Contando... echo -n "$Num " # -n para nao saltar linha

50

((ContLin % 10)) > /dev/null || read < /dev/tty done < numeros Observe que agora a entrada do read foi redirecionada por /dev/tty, que nada mais seno o terminal corrente, explicitando desta forma que aquela leitura seria feita do teclado e no de numeros. bom realar que isto no acontece somente quando usamos o redirecionamento de entrada, se houvssemos usado o redirecionamento via pipe (|), o mesmo teria ocorrido. Veja agora a sua execuo: $ 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 Isto est quase bom mas falta um pouco para ficar excelente. Vamos melhorar um pouco o exemplo para que voc o reproduza e teste (mas antes de testar aumente o nmero de registros de numeros ou reduza o tamanho da tela, para que haja quebra). $ cat 10porpag.sh #!/bin/bash # Prg de teste para escrever # 10 linhas e parar para ler # Verso 3 clear while read Num do ((ContLin++)) # Contando... echo "$Num" ((ContLin % (`tput lines` - 3))) || { read -n1 -p"Tecle Algo " < /dev/tty # para ler qq caractere clear # limpa a tela apos leitura } done < numeros A mudana substancial feita neste exemplo com relao quebra de pgina, j que ela feita a cada quantidade-de-linhas-da-tela (tput lines) menos (-) 3, isto , se a tela tem 25 linha, listar 22 registros e parar para leitura. No comando read tambm foi feita uma alterao, inserido um -n1 para ler somente um caractere sem ser necessariamente um <ENTER> e a opo -p para dar a 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 exerccio de apredizagem para voc melhorar a sua CDteca que bastante simples. Reescreva o seu programa que cadastra CDs para montar toda a tela com um nico echo e depois v posicionando frente de cada campo para receber os valores que sero teclados pelo operador. No se esquea, qualquer dvida ou falta de companhia para um chope s mandar um e-mail para julio.neves@gmail.com. Vou aproveitar tambm para mandar o meu jab: diga para os amigos que quem estiver afim de fazer um curso porreta de programao em Shell (de 40 horas) que mande um e-mail para julio.neves@uniriotec.br para informar-se. Valeu! -- JarbasJunior - 20 Oct 2005

Papo de botequim parte VIII


Funes O comando source

- E a cara tudo bem? - Legal!, eu queria te mostrar o que fiz mas j sei que voc vai querer molhar o bico primeiro n? - S pra contrariar, hoje vou deixar voc mostrar logo o seu "bacalho". Vai mostra a o que voc fez. - Poxa o exerccio que voc passou muito grande. Veja s como eu resolvi: $ cat musinc5 #!/bin/bash # Cadastra CDs (versao 5) #

51

clear LinhaMesg=$((`tput lines` - 3)) TotCols=$(tput cols) echo " Ttulo do lbum: Faixa: Nome da Msica: <

# Linha que msgs sero dadas para operador # Qtd colunas da tela para enquadrar msgs Inclusao de Msicas ======== == ======= | Este campo foi criado somente para | orientar o preenchimento Tela montada com um nico echo Posiciona e limpa linha Operador deu <ENTER>

Intrprete:" # while true do tput cup 5 38; tput el # read Album [ ! "$Album" ] && # { Msg="Deseja Terminar? (S/n)" TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # tput cup $LinhaMesg $Col echo "$Msg" tput cup $LinhaMesg $((Col + TamMsg + 1)) read -n1 SN tput cup $LinhaMesg $Col; tput el # [ $SN = "N" -o $SN = "n" ] && continue # clear; exit # Fim da execuo } grep "^$Album\^" musicas > /dev/null && { Msg="Este lbum j est cadastrado" TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / tput cup $LinhaMesg $Col echo "$Msg" read -n1 tput cup $LinhaMesg $Col; tput el # continue # } Reg="$Album^" # oArtista= # while true do ((Faixa++)) tput cup 7 38 echo $Faixa tput cup 9 38 # read Musica [ "$Musica" ] || # { Msg="Fim de lbum? (S/n)" TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # tput cup $LinhaMesg $Col echo "$Msg" tput cup $LinhaMesg $((Col + TamMsg + 1) read -n1 SN tput cup $LinhaMesg $Col; tput el # [ "$SN" = N -o "$SN" = n ]&&continue # break # } tput cup 11 38 # [ "$oArtista" ]&& echo -n "($oArtista) " # read Artista [ "$Artista" ] && oArtista="$Artista"

Centra msg na linha

Apaga msg da tela $SN igual a N ou (-o) n?

2)) # Centra msg na linha

Apaga msg da tela Volta para ler outro lbum $Reg receber os dados para gravao Variavel que guarda artista anterior

Posiciona para ler musica Se o operador tiver dado ...

Centra msg na linha

Apaga msg da tela $SN igual a N ou (-o) n? Sai do loop para gravar Posiciona para ler Artista Artista anterior default

52

Reg="$Reg$oArtista~$Musica:" # Montando registro tput cup 9 38; tput el # Apaga Musica da tela tput cup 11 38; tput el # Apaga Artista da tela done echo "$Reg" >> musicas # Grava registro no fim do arquivo sort musicas -0 musicas # Classifica o arquivo done - o programa t legal, t todo estruturadinho, mas gostaria de alguns poucos comentrios sobre o que voc fez:

S para relembrar, as seguintes construes: [ ! $Album ] && e [ $Musica ] || representam a mesma coisa, isto , no caso da primeira, o testamos se a varivel $Album no (!) tem nada dentro, ento (&&) ... Na segunda, testamos se $Musica tem dado, seno (||) ... Se voc reclamou do tamanho dele, porque ainda no dei algumas dicas. Repare que a maior parte do script para dar mensagens centradas na penltima linha da tela. Repare ainda que algumas mensagens pedem um S ou um N e outras so s de advertncia. Seria o caso tpico do uso de funes, que seriam escritas somente uma vez e chamada a execuo de diversos pontos do script. Vou montar duas funes para resolver estes casos e vamos incorpor-las ao seu programa para ver o resultado final.

Funes
- Chico! Agora traz dois chopes, sendo um sem colarinho, para me dar inspirao. Pergunta () { # 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 a # seguir 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} local Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha tput cup $LinhaMesg $Col echo "$Msg" tput cup $LinhaMesg $((Col + TamMsg + 1)) read -n1 SN [ ! $SN ] && SN=$2 # Se vazia coloca default em 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 return # Sai da funo } Como podemos ver, uma funo definida quando fazemos nome_da_funo () e todo o seu corpo est entre chaves ({}). Assim como conversamos aqui no Boteco sobre passagem de parmetros, as funes os recebem da mesma forma, isto , so parmetros posicionais ($1, $2, ..., $n) e todas as regras que se aplicam a passagem de parmetros para programas, tambm valem para funes, mas muito importante realar que os parmetros passados para um programa no se confundem com aqueles que este passou para suas funes. Isso significa, por exemplo, que o $1 de um script diferente do $1 de uma de suas funes Repare que as variveis $Msg, $TamMsg e $Col so de uso restrito desta rotina, e por isso foram criadas como local. A finalidade disso simplesmente para economizar memria, j que ao sair da rotina, elas sero devidamente detonadas da partio e caso no tivesse usado este artifcio, permaneceriam residentes. A linha de cdigo que cria local Msg, concatena ao texto recebido ($1) um abre parnteses, a resposta default ($2) em caixa alta, uma barra, a outra resposta ($3) em caixa baixa e finaliza fechando o parnteses. Uso esta conveno para, ao mesmo tempo, mostrar as opes disponveis e realar a resposta oferecida como default. Quase ao fim da rotina, a resposta recebida ($SN) passada para caixa baixa de forma que no corpo do programa no se precise fazer este teste. Veja agora como ficaria a funo para dar uma mensagem na tela: function MandaMsg { # A funo recebe somente um parmetro # com a mensagem que se deseja exibir, # para no obrigar ao programador passar # a msq entre aspas, usaremos $* (todos # os parmetro, lembra?) e no $1.

53

local Msg="$*" local TamMsg=${#Msg} local Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha tput cup $LinhaMesg $Col echo "$Msg" read -n1 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela return # Sai da funo } Esta uma outra forma de definir 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. Quanto ao mais, nada difere da anterior, exceto que, como consta dos comentrios, usamos a varivel $* que como j sabemos o conjunto de todos os parmetros passados, para que o programador no precise usar aspas envolvendo a mensagem que deseja passar para a funo. Para terminar com este bl-bl-bl vamos ver ento as alteraes que o programa necessita quando usamos o conceito de funes: $ cat musinc6 #!/bin/bash # Cadastra CDs (versao 6) # # rea de variveis globais LinhaMesg=$((`tput lines` - 3)) # Linha que msgs sero dadas para operador TotCols=$(tput cols) # Qtd colunas da tela para enquadrar msgs Repare que a estruturao do script est conforme o grfico a seguir: # rea de funes Pergunta () { # 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} local Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha tput cup $LinhaMesg $Col echo "$Msg" tput cup $LinhaMesg $((Col + TamMsg + 1)) read -n1 SN [ ! $SN ] && SN=$2 # Se vazia coloca default em 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 return # Sai da funo } function MandaMsg { # A funo recebe somente um parmetro # com a mensagem que se deseja exibir, # para no obrigar ao programador passar # a msq entre aspas, usaremos $* (todos # os parmetro, lembra?) e no $1. local Msg="$*" local TamMsg=${#Msg} local Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha tput cup $LinhaMesg $Col echo "$Msg" read -n1 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela return # Sai da funo } # O corpo do programa propriamente dito comea aqui clear echo "

54

Inclusao de Msicas ======== == ======= Ttulo do lbum: Faixa: Nome da Msica: Intrprete:" # Tela montada com um nico echo | Este campo foi < criado somente para | orientar o preenchimento

while true do tput cup 5 38; tput el # Posiciona e limpa linha read Album [ ! "$Album" ] && # Operador deu { Pergunta "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 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 musica read Musica [ "$Musica" ] || # Se o operador tiver dado ... { Pergunta "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 Musica 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 Variveis Globais Funes Corpo do Programa Esta estruturao devido ao Shell ser uma linguagem interpretada e desta forma o programa lido da esquerda para a direita e de cima para baixo e uma varivel para ser vista simultaneamente pelo script e suas funes deve ser declarada (ou inicializada) antes de qualquer coisa. As funes por sua vez devem ser declaradas antes do corpo do programa propriamente dito porque no ponto em que o programador mencionou seu nome, o interpretador Shell j o havia antes localizado e registrado que era uma funo. Uma coisa bacana no uso de funes faz-las o mais genrico possvel de forma que elas sirvam para outras aplicaes, sem necessidade de serem reescritas. Essas duas que acabamos de ver tm uso generalizado, pois difcil um script que tenha uma entrada de dados pelo teclado que no use uma rotina do tipo da MandaMsg ou no interage com o operador por algo semelhante Pergunta. Conselho de amigo: crie um arquivo e cada funo nova que voc criar, anexe-a a este arquivo. Ao final de um tempo voc ter uma bela biblioteca de funes que lhe poupar muito tempo de programao.

55

O comando source
V 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 No olhe a resposta no, volte a prestar ateno! Bem, j que voc est mesmo sem saco de pensar e prefere ler a resposta, vou te dar uma dica: acho que voc sabe que o .bash_profile um dos programas que so automaticamente "executados" quando voc se loga (ARRGGHH! Odeio este termo). Agora que te dei esta dica olhe novamente para a sada do ls e me diga o que h de diferente nela. Como eu disse o .bash_profile "executado" em tempo de logon e repare que no tem nenhum direito de execuo. Isso se d porque o se voc o executasse como qualquer outro script careta, quando terminasse sua execuo todo o ambiente por ele gerado morreria junto com o Shell sob o qual ele foi executado (voc se lembra que todos os scripts so executados em subshells, n?). Pois . para coisas assim que existe o comando source, tambm conhecido por . (ponto). Este comando faz com que no seja criado um novo Shell (um subshell) para executar o programa que que lhe passado como parmetro. Melhor um exemplo que 453 palavras. Veja este scriptizinho a seguir: $ cat script_bobo cd .. ls Ele simplesmente deveria ir para o diretrio acima do diretrio atual. Vamos executar uns comandos envolvendo o script_bobo e vamos analisar os resultados: $ pwd /home/jneves $ script_bobo jneves juliana paula silvie $ pwd /home/jneves Se eu mandei ele subir um diretrio, porque no subiu? Subiu sim! O subshell que foi criado para executar o script tanto subiu que listou os diretrios dos quatro usurios abaixo do /home, s que assim que o script acabou, o subshell foi para o beleleu e com ele todo o ambiente criado. Olha agora como a coisa muda: $ source script_bobo jneves juliana paula silvie $ pwd /home $ cd /home/jneves $ . script_bobo jneves juliana paula silvie $ pwd /home Ahh! Agora sim! Sendo passado como parmetro do comando source ou . (ponto), o script foi executado no Shell corrente deixando neste, todo o ambiente criado. Agora damos um rewind para o incio da explicao sobre este comando. L falamos do .bash_profile, e a esta altura voc j deve saber que a sua incumbncia , logo aps o login, deixar o ambiente de trabalho preparado para o usurio, e agora entendemos que por isso mesmo que ele executado usando este artifcio. E agora voc deve estar se perguntando se s para isso que este comando serve, e eu lhe digo que sim, mas isso nos traz um monte de vantagens e uma das mais usadas tratar funes como rotinas externas. Veja uma outra forma de fazer o nosso programa para incluir CDs no arquivo musicas: $ cat musinc7 #!/bin/bash # Cadastra CDs (versao 7) # # rea de variveis globais LinhaMesg=$((`tput lines` - 3)) TotCols=$(tput cols) # Linha que msgs sero dadas para operador # Qtd colunas da tela para enquadrar msgs

# O corpo do programa propriamente dito comea aqui clear echo " Inclusao de Msicas ======== == ======= Ttulo do lbum: Faixa: Nome da Msica: | Este campo foi < criado somente para | orientar o preenchimento

56

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 { 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 musica read Musica [ "$Musica" ] || # Se o operador tiver dado ... { . 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 anter. default read Artista [ "$Artista" ] && oArtista="$Artista" Reg="$Reg$oArtista~$Musica:" # Montando registro tput cup 9 38; tput el # Apaga Musica 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 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, desta forma reutilizando o seu cdigo. Por motivos meramente didticos as execues de pergunta.func e mandamsg.func esto sendo comandadas por source e por . (ponto) indiscriminadamente, embora prefira o source por ser mais visvel desta forma dando maior legibilidade ao cdigo e facilitando sua posterior manuteno. Veja agora como ficaram estes dois arquivos: $ # # # # # # cat pergunta.func 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)"

Msg="$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)" TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha tput cup $LinhaMesg $Col echo "$Msg" tput cup $LinhaMesg $((Col + TamMsg + 1)) read -n1 SN [ ! $SN ] && SN=$2 # Se vazia coloca default em SN

57

echo $SN | tr A-Z a-z # A sada de SN ser em minscula tput cup $LinhaMesg $Col; tput el # Apaga msg da tela $ # # # # # cat mandamsg.func A funo recebe somente um parmetro com a mensagem que se deseja exibir, para no obrigar ao programador passar a msq entre aspas, usaremos $* (todos os parmetro, lembra?) e no $1. Msg="$*"

TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha tput cup $LinhaMesg $Col echo "$Msg" read -n1 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela Em ambos os arquivos, fiz somente duas mudanas que veremos nas observaes a seguir, porm tenho mais trs a fazer: 1. As variveis no esto sendo mais declaradas como local, porque est uma diretiva que s pode ser usada no corpo de funes e portanto estas 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, uma vez que ele s serviria para indicar um eventual erro via um cdigo de retorno previamente estabelecido (por exemplo return 1, return 2, ...), sendo que o return e return 0 so idnticos e significam rotina executada sem erros; 3. O comando que estamos acostumados a usar para gerar cdigo de retorno o exit, mas a sada de uma rotina externa no pode ser feita desta forma, porque por estar sendo executada no mesmo Shell que o script chamador, o exit simplesmente encerraria este Shell, terminando a execuo de todo o script; 4. De onde surgiu a varivel LinhaMesg? Ela veio do musinc7, porque ela havia sido declarada antes da chamada das rotinas (nunca esquecendo que o Shell que est interpretando o script e estas rotinas o mesmo); 5. Se voc decidir usar rotinas externas, no se avexe, abunde os comentrios (principalmente sobre a passagem dos parmetros) para facilitar a manuteno e seu uso por outros programas no futuro. - Bem, agora voc j tem mais um monte de novidade para melhorar os scripts que fizemos voc se lembra do programa listartista no qual voc passava o nome de um artista como parmetro e ele devolvia as suas msicas? Ele era assim: $ cat listartista #!/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 - Claro que me lembro!... - Ento para firmar os conceitos que te passei, faa ele com a tela formatada, em loop, de forma que ele s termine quando receber um <ENTER> puro no nome do artista. Ahhh! Quando a listagem atingir a antepenltima linha da tela, o programa dever dar uma parada para que o operador possa l-las, isto , suponha que a tela tenha 25 linhas. A cada 22 msicas listadas (quantidade de linhas menos 3) o programa aguardar que o operador tecle algo para ento prosseguir. Eventuais mensagens de erro devem ser passadas usando a rotina mandamsg.func que acabamos de desenvolver. - Chico, manda mais dois, o meu com pouca presso... No se esquea, qualquer dvida ou falta de companhia para um chope ou at para falar mal dos polticos s mandar um e-mail para julio.neves@gmail.com. Vou aproveitar tambm para mandar o meu jab: diga para os amigos que quem estiver afim de fazer um curso porreta de programao em Shell que mande um e-mail para julio.neves@uniriotec.br para informar-se. Valeu!

58

Papo de botequim parte IX


Envenenando a escrita Principais Variveis do Shell Expanso de parmetros

- T bom, j sei que voc vai querer chope antes de comear, mas t to afim de te mostrar o que fiz que j vou at pedindo a rodada e em seguida vou te mostrar. - A Chico, manda dois. O dele sem colarinho pra no deixar cheiro ruim neste bigodo... - Enquanto o chope no chega deixa eu te relembrar que voc me pediu para refazer o listartista com a tela formatada, em loop, de forma que ele s termine quando receber um <ENTER> puro no nome do artista. Eventuais mensagens de erros e perguntas deveriam ser dadas na antepenltima linha da tela utilizando as rotina mandamsg.func e pergunta.func que acabamos de desenvolver. - Primeiramente eu dei uma encolhida no mandamsg.func e no pergunta.func, que ficaram assim: $ cat mandamsg.func # A funo recebe somente um parmetro # com a mensagem que se deseja exibir, # para no obrigar ao programador passar # a msg entre aspas, usaremos $* (todos # os parmetro, lembra?) e no $1. Msg="$*" TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha tput cup $LinhaMesg $Col read -n1 -p "$Msg " $ cat pergunta.func # 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)" Msg="$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)" TamMsg=${#Msg} Col=$(((TotCols - TamMsg) / 2)) # Centra 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 - E agora a vai o grando: $ cat listartista3 #!/bin/bash # Dado um artista, mostra as suas musicas # versao 3 LinhaMesg=$((`tput lines` - 3)) # Linha que msgs sero dadas para operador TotCols=$(tput cols) # Qtd colunas da tela para enquadrar msgs clear echo

" +----------------------------------------------------+ | Lista Todas as Msicas de um Determinado Artista | | ----- ----- -- ------- -- -- ----------- ------- | | | | Informe o Artista: | +----------------------------------------------------+"

while true do

59

tput cup 5 51; tput ech 31 # ech=Erase chars (31 caracteres para no apagar barra vertical) read Nome if [ ! "$Nome" ] # $Nome estah vazio? then . pergunta.func "Deseja Sair?" s n [ $SN = n ] && continue break fi fgrep -iq "^$Nome~" musicas || # fgrep no interpreta ^ como expresso regular { . mandamsg.func "No existe msica deste artista" continue } tput cup 7 29; echo '| LinAtual=8 IFS=" :" for ArtMus in $(cut -f2 -d^ musicas) # Exclui nome do album do if echo "$ArtMus" | grep -iq "^$Nome~" then tput cup $LinAtual 29 echo -n '| ' echo $ArtMus | cut -f2 -d~ tput cup $LinAtual 82 echo '|' let LinAtual++ if [ $LinAtual -eq $LinhaMesg ] then . mandamsg.func "Tecle Algo para Continuar..." tput cup 7 0; tput ed # Apaga a tela a partir da linha 7 tput cup 7 29; echo '| |' LinAtual=8 fi fi done tput cup $LinAtual 29; echo '| |' tput cup $((++LinAtual)) 29 read -n1 -p "+-----------Tecle Algo para Nova Consulta------------+" tput cup 7 0; tput ed # Apaga a tela a partir da linha 7 done - Poxa, voc chegou com a corda toda! Mas gostei da forma que voc resolveu o problema e estruturou o programa. Foi mais trabalhoso mas a apresentao ficou legal e voc explorou bastante as opes do tput. Vamos testar o resultado com um lbum do Emerson, Lake & Palmer que tenho cadastrado: +----------------------------------------------------+ | Lista Todas as Msicas de um Determinado Artista | | ----- ----- -- ------- -- -- ----------- ------- | | Informe o Artista: Emerson, Lake & Palmer | +----------------------------------------------------+ | | | Jerusalem | | Toccata | | Still ... You Turn Me On | | Benny The Bouncer | | Karn Evil 9 | | | +-----------Tecle Algo para Nova Consulta------------+ |'

Envenenando a escrita
- Ufa! Agora voc j sabe tudo sobre leitura, mas sobre escrita 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?

60

, com estes comandos voc escreve 90% das coisas necessrias, porm se precisar de escrever algo formatado eles lhe daro muito trabalho. Para formatar a sada veremos agora uma instruo muito interessante - o printf - sua sintaxe a seguinte: printf formato [argumento...] Onde: formato - uma cadeia de caracteres que contem 3 tipos de objeto: 1. caracteres simples; 2. caracteres para especificao de formato; 3. seqncia de escape no padro da linguagem C. Argumento - a cadeia a ser impressa sob o controle do formato. Cada um dos caracteres utilizados para especificao de formato precedido pelo caracter % e logo a seguir vem a especificao de formato de acordo com a tabela: Tabela dos Caracteres de Formatao do printf Letra c d e f g o s x % A expresso ser impressa como: Simples caractere Nmero no sistema decimal Notao cientfica exponencial Nmero com ponto decimal (float) O menor entre os formatos %e e %f com supresso dos zeros no significativos Nmero no sistema octal Cadeia de caracteres Nmero no sistema hexadecimal Imprime um %. No existe nenhuma converso

As seqncias de escape padro da linguagem C so sempre precedidas por um contra-barra (\) e as reconhecidas pelo comando printf so: Sequencias de Escape do printf Seqncia a b f n r t Efeito 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 No acabou por a no! Tem muito mais coisa sobre a instruo, mas como muito cheio de detalhes e, portanto, chato para explicar e, pior ainda para ler ou estudar, vamos passar direto aos exemplos com seus comentrios, que no estou aqui para encher o saco de ningum. $ printf "%c" "1 caracter" 1$ Errado! S listou 1 caractere e no saltou linha ao final $ printf "%c\n" "1 caracter" 1 Saltou linha mas ainda no listou a cadeia inteira $ printf "%c caractere\n" 1 1 caractere Esta a forma correta o %c recebeu o 1 $ a=2 $ printf "%c caracteres\n" $a 2 caracteres O %c recebeu o valor da varivel $a $ printf "%10c caracteres\n" $a 2 caracteres $ printf "%10c\n" $a caracteres 2 c Repare que nos dois ltimos exemplos, em virtude do %c, s foi listado um caracter de cada cadeia. O 10 frente do c, no significa 10 caracteres. Um nmero seguindo o sinal de percentagem (%) significa o tamanho que a cadeia ter aps a execuo do comando.

61

E tome de exemplo: $ printf "%d\n" 32 32 $ printf "%10d\n" 32 32 Preenche com brancos esquerda e no com zeros $ printf "%04d\n" 32 0032 04 aps % significa 4 dgitos com zeros esquerda $ printf "%e\n" $(echo "scale=2 ; 100/6" | bc) 1.666000e+01 O default do %e 6 decimais $ printf "%.2e\n" `echo "scale=2 ; 100/6" | bc` 1.67e+01 O .2 especificou duas decimais $ printf "%f\n" 32.3 32.300000 O default do %f 6 decimais $ printf "%.2f\n" 32.3 32.30 O .2 especificou duas decimais $ printf "%.3f\n" `echo "scale=2 ; 100/6" | bc` 33.330 O bc devolveu 2 decimais. o printf colocou 0 direita $ printf "%o\n" 10 12 Converteu o 10 para octal $ printf "%03o\n" 27 033 Assim a converso fica com mais jeito de octal, n? $ printf "%s\n" Peteleca Peteleca $ printf "%15s\n" Peteleca Peteleca Peteleca com 15 caracteres enchidos com brancos $ printf "%-15sNeves\n" Peteleca Peteleca Neves O menos (-) encheu direita com brancos $ printf "%.3s\n" Peteleca Pet 3 trunca as 3 primeiras $ printf "%10.3sa\n" Peteleca Peta Pet com 10 caracteres concatenado com a (aps o s) $ printf "EXEMPLO %x\n" 45232 EXEMPLO b0b0 Transformou para hexa mas os zeros no combinam $ printf "EXEMPLO %X\n" 45232 EXEMPLO B0B0 Assim disfarou melhor (repare o X maisculo) $ printf "%X %XL%X\n" 49354 192 10 C0CA C0LA O ltimo exemplo no marketing e bastante completo, vou coment-lo passo-a-passo: 1. O primeiro %X converteu 49354 em hexadecimal resultando C0CA (leia-se "c", "zero", "c" e "a"); 2. 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; 3. E finalmente o ltimo %X transformou o 10 em A. Conforme vocs podem notar, a instruo printf bastante completa e complexa (ainda bem que o echo resolve quase tudo). Creio que quando resolvi explicar o printf atravs de exemplos, acertei em cheio pois no saberia como enumerar tantas regrinhas sem tornar a leitura enfadonha.

Principais Variveis do Shell


O Bash possui diversas variveis que servem para dar informaes sobre o ambiente ou alter-lo. Seu nmero muito grande e no pretendo mostrar todas, mas uma pequena parte que pode lhe ajudar na elaborao de scripts. Ento a vo as principais: Principais variveis do Bash Varivel CDPATH Contedo Contm os caminhos que sero pesquisados para tentar localizar um diretrio especificado. Apesar desta varivel ser pouco conhecida, seu uso deve ser incentivado por poupar muito trabalho, principalmente em instalaes com estrutura de diretrios com bastante nveis. Limita o nmero de instrues que cabem dentro do arquivo de histrico de comandos (normalmente .bash_history mas efetivamente o que est armazenado na varivel $HISTFILE). Seu valor default 500. O nome do host corrente (que tambm pode ser obtido com o comando uname -n). Usada para determinar a lngua falada no pais (mais especificamente categoria do locale).

HISTSIZE HOSTNAME LANG

62

LINENO LOGNAME

O nmero da linha do script ou da funo que est sendo executada, seu uso principal para dar mensagens de erro juntamente com as variveis $0 (nome do programa) e $FUNCNAME (nome da funo em execuo) Armazena o nome de login do usurio. Especifica, em segundos, a freqncia que o Shell verificar a presena de correspondncias nos arquivos indicados pela variveis $MAILPATH ou $MAIL. O tempo padro 60 segundos. Uma vez este tempo expirado, o Shell far esta verificao antes de exibir o prximo prompt primrio (definido em $PS1). Se esta varivel estiver sem valor ou com um valor menor ou igual a zero, a verificao de novas correspondncias no ser efetuada. Caminhos que sero pesquisados para tentar localizar um arquivo especificado. Como cada script um arquivo, caso use o diretrio corrente (.) na sua varivel $PATH, voc no necessitar de usar o ./scrp para que scrp seja executado. Basta fazer scrp. Este o modo que procedo aqui no Botequim. uma varivel do tipo vetor (array) que contm uma lista valores de cdigo de retorno do ltimo pipeline executado, isto , um array que abriga cada um dos $? de cada instruo do ltimo pipeline.

MAILCHECK

PATH PIPESTATUS

PROMPT_COMM Se esta varivel receber uma instruo, toda vez que voc der um <ENTER> direto no prompt principal AND ($PS1), este comando ser executado. til quando se est repetindo muito uma determinada instruo. PS1 o prompt principal. No "Papo de Botequim" usamos os seus defaults: $ para usurio comum e # para root, mas muito freqente que ele esteja customizado. Uma curiosidade que existe at concurso de quem programa o $PS1 mais criativo. (clique para dar uma googlada) Tambm chamado prompt de continuao, aquele sinal de maior (>) que aparece aps um <ENTER> sem o comando ter sido encerrado. Possui o caminho completo ($PATH) do diretrio corrente. Tem o mesmo efeito do comando pwd. Cada vez que esta varivel acessada, devolve um nmero inteiro, que um randmico entre 0 e 32767. Use esta varivel para recuperar o ltimo campo lido, caso ele no tenha nenhuma varivel associada. Esta varivel contm a quantidade de segundos que o Shell corrente est de p. Use-a somente para esnobar um usurios daquilo que chamam de sistema operacional, mas necessita de boots freqentes. Se tiver um valor maior do que zero, este valor ser tomado como o padro de timeout do comando read. No prompt, este valor interpretado como o tempo de espera por uma ao antes de expirar a sesso. Supondo que a varivel contenha 30, o Shell dar logout aps 30 segundos de prompt sem nenhuma ao.

PS2 PWD RANDOM REPLY SECONDS

TMOUT

CDPATH

$ echo $CDPATH .:..:~:/usr/local $ pwd /home/jneves/LM $ cd bin $ pwd /usr/local/bin Como /usr/local estava na minha varivel $CDPATH, e no existia o diretrio bin em nenhum dos seus antecessores (., .. e ~), o cd foi executado para /usr/local/bin

LANG

$ date Thu Apr 14 11:54:13 BRT 2005 $ LANG=pt_BR date Qui Abr 14 11:55:14 BRT 2005 Com a especificao da varivel LANG=pt_BR (portugus do Brasil), a data passou a ser informada no padro brasileiro. interessante observarmos que no foi usado ponto-e-vrgula (;) para separar a atribuio de LANG do comando date.

PIPESTATUS

$ 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 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, e desta forma vimos que a primeira instruo (who) foi bem sucedida (cdigo de retorno 0) e a seguinte (grep), no (cdigo de retorno 1).

RANDOM

63

Para gerar randomicamente um inteiro entre 0 e 100, fazemos: $ echo $((RANDOM%101)) 73 Ou seja pegamos o resto da diviso por 101 do nmero randmico gerado, porque o resto da diviso de qualquer nmero por 101 varia entre 0 e 100.

REPLY

$ read -p "Digite S ou N: " Digite S ou N: N $ echo $REPLY N Eu sou do tempo que memria era um bem precioso que custava muuuuito caro. Ento para pegar um S ou um N, no costumo a alocar um espao especial e assim sendo, pego o que foi digitado na varivel $REPLY.

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 eficientes quanto os intrnsecos (built-ins). Quando o nosso problema for performance, devemos dar preferncia ao uso dos intrnsecos e a partir de agora vou te mostrar algumas tcnicas para o teu programa pisar no acelerador. Na tabela e 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. Expanso de parmetros Expresso ${var:-padrao} ${#cadeia} ${cadeia:posicao} ${cadeia:posicao:tamanho} ${cadeia#expr} ${cadeia##expr} ${cadeia%expr} ${cadeia%%expr} ${cadeia/subcad1/subcad2} ${cadeia//subcad1/subcad2} ${cadeia/#subcad1/subcad2} ${cadeia/%subcad1/subcad2}

Resultado esperado Se var no tem valor, o resultado da expresso padrao Tamanho de $cadeia Extrai uma subcadeia de $cadeia a partir de posicao. Origem zero Extrai uma subcadeia de $cadeia a partir de posicao com tamanho igual a tamanho. Origem zero Corta a menor ocorrncia de $cadeia esquerda da expresso expr Corta a maior ocorrncia de $cadeia esquerda da expresso expr Corta a menor ocorrncia de $cadeia direita da expresso expr Corta a maior ocorrncia de $cadeia direita da expresso expr Troca em $cadeia a primeira ocorrncia de subcad1 por subcad2 Troca em $cadeia todas as ocorrncias de subcad1 por subcad2 Se subcad1 combina com o incio de $cadeia, ento trocado por subcad2 Se subcad1 combina com o fim de $cadeia, ento trocado por subcad2

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} Desta forma se o operador deu um simples <ENTER> para confirmar que aceitou o valor default, aps executar esta instruo, a varivel ter o valor S, caso contrrio, ter o valor digitado.

Para sabermos o tamanho de uma cadeia:

$ cadeia=0123 $ echo ${#cadeia} 4 Para extrair de uma cadeia da posio um at o final fazemos: $ cadeia=abcdef $ echo ${cadeia:1} bcdef

64

Repare que a origem zero e no um.

Na mesma varivel $cadeia do exemplo acima, para extrair 3 caracteres a partir da 2 posio:

$ echo ${cadeia:2:3} cde Repare que novamente que a origem da contagem zero e no um.

Para suprimir tudo esquerda da primeira ocorrncia de uma cadeia, faa:

$ cadeia="Papo de Botequim" $ echo ${cadeia#*' '} de Botequim $ echo "Conversa "${cadeia#*' '} Conversa de Botequim Neste exemplo foi suprimido esquerda tudo que casasse com a menor ocorrncia da expresso *' ', ou seja, tudo at o primeiro espao em branco. Estes exemplos tambm poderiam ser escritos sem protegermos o espao da interpretao do Shell (mas prefiro proteg-lo para facilitar a legibilidade do cdigo), veja: $ echo ${cadeia#* } de Botequim $ echo "Conversa "${cadeia#* } Conversa de Botequim Repare que na construo de expr permitido o uso de metacaracteres.

Utilizando o mesmo valor da varivel $cadeia, observe como faramos para termos somente Botequim:

$ echo ${cadeia##*' '} Botequim $ echo "Vamos 'Chopear' no "${cadeia##*' '} Vamos 'Chopear' no Botequim Desta vez suprimimos esquerda de cadeia a maior ocorrncia da expresso expr. Assim como no caso anterior, o uso de metacaracteres permitido. Outro exemplo mais til: para que no aparea o caminho (path) completo do seu programa (que, como j sabemos est contido na varivel $0) em uma mensagem de erro, inicie o seu texto da seguinte forma: echo Uso: ${0##*/} texto da mensagem de erro Neste exemplo seria suprimido esquerda tudo at a ltima barra (/) do caminho (path), desta forma sobrando somente o nome do programa.

O uso do percentual (%) como se olhssemos o jogo-da-velha (#) no espelho, isto , so simtricos. Ento vejamos um exemplo para provar isso:

$ echo $cadeia Papo de Botequim $ echo ${cadeia%' '*} Papo de $ echo ${cadeia%%' '*} Papo 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 Neste caso preste a ateno quando for usar metacaracteres, eles so gulosos! Eles sempre combinaro com a maior possibilidade, veja o exemplo a seguir onde a inteno era trocar Papo de Botequim por Conversa de Botequim: $ echo $cadeia Papo de Botequim $ echo ${cadeia/*o/Conversa} Conversatequim A idia era pegar tudo at o primeiro o, mas o que foi trocado foi tudo at o ltimo o. Isto poderia ser resolvido de diversas maneiras, veja algumas: $ echo ${cadeia/*po/Conversa} Conversa de Botequim $ echo ${cadeia/????/Conversa}

65

Conversa de Botequim Trocando todas as ocorrncias de uma subcadeia por outra. Quando fazemos: $ echo ${cadeia//o/a} Papa de Batequim Trocamos todos as letras o por a. Outro exemplo mais til para contarmos a quantidade de arquivos existentes no diretrio corrente. Observe a linha a seguir: $ ls | wc -l 30 Viu? O wc produz um monte de espaos em branco no incio. Para tir-los podemos fazer: $ QtdArqs=$(ls | wc -l) # QtdArqs recebe a sada do comando $ echo ${QtdArqs// /} 30 No ltimo exemplo, como eu sabia que a sada era composta de brancos e nmeros, montei esta expresso para trocar todos os espaos por nada. Repare que aps as duas primeiras barras existe um espao em branco. Outra forma de fazer a mesma coisa seria: $ echo ${QtdArqs/* /} 30 Trocando uma subcadeia no incio ou no fim de uma varivel. Para trocar no incio fazemos: $ echo $Passaro quero quero $ echo "Como diz o sulista - "${Passaro/#quero/no} Como diz o sulista - no quero Para trocar no final fazemos: $ echo "Como diz o nordestino - "${Passaro/%quero/no} Como diz o nordestino - quero no - Agora j chega, o papo hoje foi muito chato porque foi muita decoreba, mas o principal voc ter entendido o que te falei e, quando precisar, consulte estes guardanapos em que rabisquei estas dicas e depois guarde-os para consultas futuras. Mas voltando vaca fria: t na hora de tomar outro e ver o jogo do mengo. Na prxima vou te dar moleza e s vou cobrar o seguinte: pegue a rotina pergunta.func, (a que na qual falamos no incio do nosso bate papo de hoje) e otimize-a para que a varivel $SN receba o valor default por expanso de parmetros, como vimos. - Chico, v se no esquece de mim e enche meu copo. No se esquea, qualquer dvida ou falta de companhia para um chope s mandar um e-mail para julio.neves@gmail.com. Vou aproveitar tambm para mandar o meu jab: diga para os amigos que quem estiver afim de fazer um curso porreta de programao em Shell que mande um e-mail para julio.neves@uniriotec.br para informar-se. Valeu!

Papo de botequim parte X


O comando eval Sinais de Processos Sinais assassinos O trap no atrapalha Comando getopts

- E a amigo, te dei a maior moleza, n? Um exerciciozinho muito simples... - mais nos testes que eu fiz, e de acordo com o que voc ensinou sobre substituio de parmetros, achei que deveria fazer outras alteraes nas funes que desenvolvemos para torn-las de uso geral como voc me disse que todas as funes deveriam ser, quer ver? - Claro n man, se te pedi para fazer porque estou afim 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 definidas a linha em que seria dada a mensagem e a quantidade de colunas. O que fiz foi incluir duas linhas - nas quais empreguei substituio de parmetros - que caso uma destas variveis no fosse informada, a prpria funo atribuiria. A linha de mensagem seria trs linhas acima do fim da tela e o total de colunas seria obtido pelo comando tput cols. Veja como ficou:

66

$ cat pergunta.func # A funcao recebe 3 parametros 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)" TotCols=${TotCols:-$(tput cols)} # Se nao estava definido, agora esta 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 centrar 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 saida de SN serah em minuscula tput cup $LinhaMesg $Col; tput el # Apaga msg da tela - Gostei, voc j se antecipou ao que eu ia pedir. S pra gente encerrar este papo de substituio de parmetros, repare que a legibilidade est horrorvel, mas a performance, isto , velocidade de execuo, est tima. Como funes so coisas muito pessoais, j que cada um usa as suas, e quase no do manuteno, eu sempre opto pela performance. - Hoje vamos sair daquela chatura que foi o nosso ltimo papo e vamos voltar lgica saindo da decoreba, mas volto a te lembrar, tudo que eu te mostrei da outra vez aqui no Boteco do Chico vlido e quebra um galho, guarde aqueles guardanapos que rabiscamos que, mais cedo ou mais tarde, vo te ser muito teis.

O comando eval
- Vou te dar um problema que eu duvido que voc resolva: $ var1=3 $ var2=var1 - Te dei estas duas variveis, e quero que voc me diga como eu posso, s me referindo a $var2, listar o valor de $var1 (3). - A isso mole, s fazer: echo $`echo $var2` - Repare que eu coloquei o echo $var2 entre crases (`), que desta forma ter prioridade de execuo e resultar em var1, montando echo$var1 que produzir 3... - A ? Ento execute para ver se est correto. $ echo $`echo $var2` $var1 - U! Que foi que houve? O meu raciocnio me parecia bastante lgico... - O seu raciocnio realmente foi lgico, o problema que voc esqueceu de uma das primeiras coisas que te falei aqui no Boteco e vou repetir. O Shell usa a seguinte ordem para resolver uma linha de comandos:

Resolve os redirecionamentos; Substitui as variveis pelos seus valores; Resolve e substitui os meta caracteres; Passa a linha j toda esmiuada para execuo.

Desta forma, quando 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 identificou isso como uma cadeia e no como uma varivel. Problemas deste 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 se seus dados fossem parmetros do eval e em seguida o eval executa a linha recebida, submetendo-a ao Shell, dando ento na prtica duas passadas em cmd. Desta forma se executssemos o comando que voc props colocando o eval sua frente, teramos a sada esperada, veja: $ eval echo $`echo $var2` 3 Este exemplo tambm poderia ter sido feito da seguinte maneira: $ eval echo \$$var2 3

67

Na primeira passada a contrabarra (\) seria retirada e $var2 seria resolvido produzindo var1, para a segunda passada teria sobrado echo $var1, que produziria o resultado esperado. Agora vou colocar um comando dentro de var2: $ var2=ls Vou executar: $ $var2 10porpag1.sh alo2.sh listamusica logaute.sh 10porpag2.sh confuso listartista mandamsg.func 10porpag3.sh contpal.sh listartista3 monbg.sh alo1.sh incusu logado Agora vamos colocar em var2 o seguinte: ls $var1; e em var1 vamos colocar l*, vejamos: $ 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, desta forma s nos resta executar o comando eval para dar as duas passadas necessrias. Uma vez um colega de uma excelente lista sobre 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 seria executado. A minha proposta foi a seguinte: $ cat fazmenu #!/bin/bash # # Lista numerando 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" Parece complicado porque usei muito printf para formatao da tela, mas bastante simples, vamos entend-lo: o primeiro printf foi colocado para fazer o cabealho e logo em seguida comecei a montar dinamicamente a varivel $CASE, na qual ao final 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 se fazer uma opo invlida, ser executada uma funo externa para dar mensagens de erro. Vamos execut-lo para ver a sada gerada: $ fazmenu.sh Opcao Programa 001 002 003 004 005 006 10porpag1.sh 10porpag2.sh 10porpag3.sh alo1.sh alo2.sh contpal.sh

68

007 fazmenu.sh 008 logaute.sh 009 monbg.sh 010 readpipe.sh 011 redirread.sh Informe a opo desejada: Neste programa seria interessante darmos uma opo de trmino, e para isso seria necessrio a incluso de uma linha aps o loop de montagem da tela e alterarmos a linha na qual fazemos a atribuio final do valor da varivel $CASE. Vejamos como ele ficaria: $ cat fazmenu #!/bin/bash # # Lista numerando 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 printf "\t%d\t%s\n\n" 999 "Fim do programa" # Linha incluida CASE="$CASE 999) exit;; # Linha alterada *) ./erro;; esac" read -n3 -p "Informe a opo desejada: " opt echo eval "$CASE"

Sinais de Processos
Existe no Linux uma coisa chamada sinal (signal). Existem diversos sinais que podem ser mandados para (ou gerados por) processos em execuo. Vamos de agora em diante dar uma olhadinha nos sinais mandados para os processos e mais frente vamos dar uma passada rpida pelos sinais gerados por processos.

Sinais assassinos
Para mandar um sinal a um processo, usamos normalmente o comando kill, cuja sintaxe : kill -sig PID Onde PID o identificador do processo (Process IDentification ou Process ID). Alm do comando kill, algumas seqncias de teclas tambm podem gerar sig. A tabela a seguir mostra os sinais mais importantes para monitorarmos: Sinais Mais Importantes Sinal Gerado por: 0 EXIT Fim normal do programa 1 SIGHUP Quando recebe um kill -HUP 2 SIGINT Interrupo pelo teclado (<CTRL+C>) 3 SIGQUIT Interrupo pelo teclado (<CTRL+\>) 15 SIGTERM Quando recebe um kill ou kill -TERM Alm destes sinais, existe o famigerado -9 ou SIGKILL que, para o processo que o est recebendo, equivale a meter o dedo no boto de desliga do computador o que seria altamente indesejvel j que muitos programas necessitam "limpar o meio de campo" ao seu trmino. Se o seu final ocorrer de forma prevista, ou seja se tiver um trmino normal, muito fcil de fazer esta limpeza, porm se o seu programa tiver um fim brusco muita coisa pode ocorrer:

possvel que em um determinado espao de tempo, o seu computador esteja cheio de arquivos de trabalho inteis Seu processador poder ficar atolado de processos zombies e defuncts gerados por processos filhos que perderam os pais; necessrio liberar sockets abertos para no deixar os clientes congelados; Seus bancos de dados podero ficar corrompidos porque sistemas gerenciadores de bancos de dados necessitam de um tempo para gravar seus buffers em disco (commit).

69

Enfim, existem mil razes para no usar um kill com o sinal -9 e para monitorar fins anormais de programas.

O trap no atrapalha
Para fazer a monitorao descrita acima existe o comando trap cuja sintaxe : trap "cmd1; cmd2; cmdn" S1 S2 ... SN ou trap 'cmd1; cmd2; cmdn' S1 S2 ... SN Onde os comandos cmd1, cmd2, cmdn sero executados caso o programa receba os sinais S1 S2 ... SN. As aspas (") ou os apstrofos (') s so necessrios caso o trap possua mais de um comando cmd associado. Cada um dos cmd pode ser tambm uma funo interna, uma externa ou outro script. Para entender o uso de aspas (") e apstrofos (') vamos recorrer a um exemplo que trata um fragmento de um script que faz um ftp para uma mquina remota ($RemoComp), na qual o usurio $Fulano, sua senha $Segredo e vai transmitir o arquivo contido em $Arq. Suponha ainda que estas quatro variveis foram recebidas em uma rotina anterior de leitura e que este script muito usado por diversas pessoas da instalao. Vejamos este trecho de cdigo: ftp -ivn $RemoComp << FimFTP >> /tmp/$$ 2>> /tmp/$$ user $Fulano $Segredo binary get $Arq FimFTP Repare que, tanto as sadas do dos dilogos do ftp, como os erros encontrados, esto sendo redirecionados para /tmp/$$, o que uma construo bastante normal para arquivos temporrios usados em scripts com mais de um usurio, porque $$ a varivel que contm o nmero do processo (PID), que nico, e com este tipo de construo evita-se que dois ou mais usurios disputem a posse e os direitos sobre o arquivo. Caso este ftp seja interrompido por um kill ou um <CTRL+C>, certamente deixar lixo no disco. exatamente esta a forma como mais se usa o comando trap. Como isto trecho de um script, devemos, logo no seu incio, como um de seus primeiros comandos, fazer: trap "rm -f /tmp/$$ ; exit" 0 1 2 3 15 Desta forma, caso houvesse uma interrupo brusca (sinais 1, 2, 3 ou 15) antes do programa encerrar (no exit dentro do comando trap), ou um fim normal (sinal 0), o arquivo /tmp/$$ seria removido. Caso na linha de comandos do trap no houvesse a instruo exit, ao final da execuo desta linha o fluxo do programa retornaria ao ponto em que estava quando recebeu o sinal que originou a execuo deste trap. Este trap poderia ser subdividido, ficando da seguinte forma: trap "rm -f /tmp/$$" 0 trap "exit" 1 2 3 15 Assim ao receber um dos sinais o programa terminaria, e ao terminar, geraria um sinal 0, que removeria o arquivo. Caso seu fim seja normal, o sinal tambm ser gerado e o rm ser executado. Note tambm que o Shell pesquisa a linha de comandos uma vez quanto 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 que o comando trap foi lido da primeira vez, j que as aspas (") no protegem o cifro ($) da interpretao do Shell. Se voc desejasse que a substituio fosse realizada somente quando recebesse o sinal, o comando deveria ser colocado entre apstrofos ('). Assim, na primeira interpretao do trap, o Shell no veria o cifro ($), porm os apstrofos (') seriam removidos e finalmente o Shell poderia substituir o valor da varivel. Neste caso, a linha ficaria da seguinte maneira: trap 'rm -f /tmp/$$ ; exit' 0 1 2 3 15 Suponha dois casos: voc tem dois scripts que chamaremos de script1, cuja primeira linha ser um trap e script2, sendo este ltimo colocado em execuo pelo primeiro, e por serem dois processos, tero dois PID distintos.

1 Caso: O ftp encontra-se em script1 Neste 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 neste momento e o PID do script2 seria diferente do encontrado em /tmp/$$ (no esquea que $$ a varivel que contm o PID do processo ativo);

70

2 Caso: O ftp acima encontra-se em script2 Neste caso, o argumento do comando trap deveria estar entre apstrofos ('), pois caso a interrupo se desse durante a execuo de script1, o arquivo no teria sido criado, caso ocorresse durante a execuo de script2, o valor de $$ seria o PID deste 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), isto significa que os sinais especificados devem ser ignorados quando recebidos. Por exemplo, o comando: trap "" 2 Especifica que o sinal de interrupo (<CTRL+C>) deve ser ignorado. No caso citado, quando no se deseja que sua execuo seja interrompida. No ltimo exemplo note que o primeiro argumento deve ser especificado para que o sinal seja ignorado, e no equivalente a escrever o seguinte, cuja finalidade retornar o sinal 2 ao seu estado padro (default): trap 2 Se voc ignora um sinal, todos os Subshells iro ignorar este sinal. Portanto, se voc especifica qual ao deve ser tomada quando receber um sinal, ento todos os Subshells iro tambm tomar a ao quando receberem este sinal, ou seja, os sinais so automaticamente exportados. Para o sinal que temos mostrado (sinal 2), isto significa que os Subshells sero encerrados. Suponha que voc execute o comando: trap "" 2 e ento execute um Subshell, que tornar a executar outro script como um Subshell. Se for gerado um sinal de interrupo, este no ter efeito nem sobre o Shell principal nem sobre os Subshell por ele chamados, j que todos eles ignoraro o sinal. Outra forma de restaurar um sinal ao seu default fazendo: trap - sinal Em korn shell (ksh) no existe a opo -s do comando read para ler uma senha. O que costumamos fazer usar o comando stty com a opo -echo que inibe a escrita na tela at que se encontre um stty echo para restaurar esta escrita. Ento, se estivssemos usando o interpretador ksh, a leitura da senha teria que ser feita da seguinte forma: echo stty read stty -n "Senha: " -echo Senha echo

O problema neste tipo de construo que caso o operador no soubesse a senha, ele provavelmente daria um <CTRL+C> ou um <CTRL+\> durante a instruo read para descontinuar o programa e, caso ele agisse desta forma, o que quer que ele escrevesse, no apareceria na tela do seu terminal. 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 Para terminar este assunto, abra uma console grfica e escreva no prompt de comando o seguinte: $ trap "echo Mudou o tamanho da janela" 28 Em seguida, pegue o mouse (arghh!!) e arraste-o de forma a variar o tamanho da janela corrente. Surpreso? o Shell orientado a eventos... Mais unzinho porque no pude resistir. Agora escreva assim: $ trap "echo j era" 17 Em seguida faa: $ sleep 3 & Voc acabou de criar um subshell que ir dormir durante trs segundos em background. Ao fim deste tempo, voc receber a mensagem j era, porque o sinal 17 emitido a cada vez que um subshell termina a sua execuo. Para devolver estes sinais aos seus defaults, faa:

71

$ trap 17 28 Ou $ trap - 17 28 Acabamos de ver mais dois sinais que no so to importante como os que vimos anteriormente, mas vou registr-los na tabela a seguir: Sinais No Muito Importantes Sinal Gerado por: 17 SIGCHLD Fim de um processo filho 28 SIGWINCH Mudana no tamanho da janela grfica Muito legal este comando, n? Se voc descobrir algum caso bacana de uso de sinais, por favor me informe por e-mail porque muito rara a literatura sobre o assunto.

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 este comando para "fatiar" opes e argumento passados para o seu script. 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, cadeiadeopcoes deve ser abc. Se voc deseja que uma opo seja seguida por um argumento, ponha dois-pontos (:) depois da letra, como em a:bc. Isto 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 interrogao (?). O nome constante da linha de sintaxe acima, define uma varivel que cada vez que o comando getopts for executado, receber a prxima opo dos parmetros posicionais e a colocar na varivel nome. getopts coloca uma interrogao (?) na varivel definida em nome se achar uma opo no definida 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 possui argumento ou o argumento esperado no foi encontrado, a varivel OPTARG ser "matada" (unset). O comando encerra sua execuo quando:

Encontra um parmetro que no comea por menos (-); O parmetro especial -- marca o fim das opes; Quando encontra um erro (por exemplo, uma opo no reconhecida).

O exemplo abaixo meramente didtico, servindo para mostrar, em um pequeno fragmento de cdigo o uso pleno do comando. $ cat getoptst.sh #!/bin/sh # # # # # # # # # Execute assim: getoptst.sh -h -Pimpressora arq1 arq2 e note que as informacoes de todas as opcoes sao exibidas A cadeia 'P:h' diz que a opcao -P eh uma opcao complexa e requer um argumento, e que h eh uma opcao simples que nao requer argumentos.

72

while getopts 'P:h' OPT_LETRA do echo "getopts fez a variavel OPT_LETRA igual a '$OPT_LETRA'" echo " OPTARG eh '$OPTARG'" done used_up=`expr $OPTIND - 1` echo "Dispensando os primeiros \$OPTIND-1 = $used_up argumentos" shift $used_up echo "O que sobrou da linha de comandos foi '$*'" Para entend-lo melhor, vamos execut-lo como est sugerido em seu cabealho: $ 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' Desta 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 neste caso ele identifica um conjunto de trs opes/argumentos e no anterior somente dois. Veja s: $ getoptst.sh -h -P impressora 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 = 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' - Me diz uma coisa: voc no poderia ter usado um case para evitar o getopts? - Poderia sim, mas para que? Os comandos esto a para serem usados... O exemplo dado foi didtico, mas imagine um programa que aceitasse muitas opes e seus parmetros poderiam ou no estar colados s opes, suas opes tambm poderiam ou no estar coladas, ia ser um case infernal e com getopts s seguir os passos acima. - ... Vendo desta forma acho que voc tem razo. porque eu j estou meio cansado com tanta informao nova na minha cabea. Vamos tomar a saideira ou voc ainda quer explicar alguma particularidade do Shell? - Nem um nem outro, eu tambm j cansei mas hoje no vou tomar a saideira porque estou indo dar aula na UniRIO, que a primeira universidade federal que est preparando no uso de Software Livre, seus alunos do curso de graduao em informtica. Mas antes vou te deixar um problema para te encucar: quando voc varia o tamanho de uma tela, no seu centro no aparece dinamicamente em vdeo reverso a quantidade de linhas e colunas? Ento! Eu quero que voc reproduza isso usando a linguagem Shell. - Chico, traz rapidinho a minha conta! Vou contar at um e se voc no trouxer eu me mando! No se esquea, qualquer dvida ou falta de companhia para um chope ou at para falar mal dos polticos s mandar um e-mail para julio.neves@gmail.com. Vou aproveitar tambm para mandar o meu jab: diga para os amigos que quem estiver afim de fazer um curso porreta de programao em Shell que mande um e-mail para julio.neves@uniriotec.br para informar-se. Valeu!

Papo de botequim parte XI


73

Named Pipes Sincronizao de processos. Bloqueio de arquivos Substituio de processos

- E a rapaz, tudo bom? - Bel, mas voc lembra que me pediu para fazer um programa que quando o tamanho de uma tela variasse, no seu centro apareceria dinamicamente, em vdeo reverso, a quantidade de linhas e colunas do jeito que o Linux faz normalmente? Pois eu fiz mas a aparncia no ficou igual. - No estou nem a para a aparncia, o que eu queria que voc exercitasse o que aprendemos. Deixe-me ver o que voc fez. $ cat 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) # Modo de enfase # Modo de video reverso # Restaura a tela ao padrao default

Muda () { clear Cols=$(tput cols) Lins=$(tput lines) tput cup $(($Lins / 2)) $(((Cols - 7) / 2)) # Centro da tela echo $Bold$Rev$Cols X $Lins$Norm } clear read -n1 -p "Mude o tamanho da tela ou tecle algo para terminar " - 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. - Poxa, perdi o maior tempo tentando descobrir como aumentar o fonte ... - Deixe isso para l e hoje vamos ver umas coisas bastante interessantes e teis.

Named Pipes
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| 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 (|) colocado ao fim do nome do arquivo, outra dica, e nos sistemas LINUX, onde a opo de cor est habilitada, o nome do arquivo escrito em vermelho por default. Nos sistemas mais antigos, os named pipes so criados pelo programa mknod, normalmente situado no diretrio /etc. Nos sistemas mais modernos, a mesma tarefa feita pelo mkfifo. O programa mkfifo recebe um ou mais nomes como argumento e cria pipes com estes nomes. Por exemplo, para criar um named pipe com o nome pipe1, faa: $ mkfifo pipe1 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 duas consoles virtuais ou uma de cada. Em uma delas faa: $ ls -l > pipe1 e em outra faa:

74

$ cat < pipe1 Voil! A sada do comando executado na primeira console foi exibida na segunda. Note que a ordem em que os comandos ocorreram no importa. Se voc prestou ateno, reparou que o primeiro comando executado, parecia ter "pendurado, congelado". 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 fique em modo de wait, necessrio que em uma ponta do pipe tenha um processo "tagarela" e na outra um "ouvinte" e no exemplo que demos, o ls era o "falador" e o cat era o "orelho". Uma aplicao 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 viu que o uso desta ferramenta timo para sincronizar processos e para fazer bloqueio em arquivos de forma a evitar perda/corrupo de informaes devido a atualizaes simultneas (concorrncia). Vejamos exemplos para ilustrar estes casos.

Sincronizao de processos.
Suponha que voc dispare paralelamente dois programas (processos) cujos diagramas de blocos de suas rotinas so como a figura a seguir:

Os dois processos so disparados em paralelo e no BLOCO1 do Programa1 as trs classificaes 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 } ... Assim sendo, o comando if testa cada classificao que est sendo efetuada. Caso ocorra qualquer problema, as classificaes seguintes sero abortadas, uma mensagem contendo a cadeia pare enviada pelo pipe1 e programa1 descontinuado com um fim anormal. Enquanto o Programa1 executava o seu primeiro bloco (as classificaes) o Programa2 executava o seu BLOCO1, processando as suas rotinas de abertura e menu paralelamente ao Programa1, ganhando desta forma um bom intervalo de tempo. O fragmento de cdigo do Programa2 a seguir, mostra a transio do seu BLOCO1 para o BLOCO2: OK=`cat pipe1` if [ $OK = va ] then

75

... Rotina de impresso ... else # Recebeu "pare" em OK exit 1 fi Aps a execuo de seu primeiro bloco, o Programa2 passar a "ouvir" o pipe1, ficando parado at que as classificaes 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. Desta forma possvel disparar programas de forma assncrona e sincronizlos quando necessrio, ganhando bastante tempo de processamento.

Bloqueio de arquivos
Suponha que voc escreveu uma CGI (Common Gateway Interface) em Shell para contar quantos hits recebe uma determinada URL e a rotina de contagem est da seguinte maneira: Hits="$(cat page.hits 2> /dev/null)" || Hits=0 echo $((Hits=Hits++)) > page.hits Desta forma se a pgina receber dois ou mais acessos concorrentes, um ou mais poder(o) ser perdido(s), basta que o segundo acesso seja feito aps a leitura da arquivo page.hits e antes da sua gravao, isto , basta que o segundo acesso seja feito aps o primeiro ter executado a primeira linha do script e antes de executar a segunda. Ento o que fazer? Para resolver o problema de concorrncia vamos utilizar um named pipe. Criamos o seguinte script 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. $ cat contahits.sh #!/bin/bash PIPE="/tmp/pipe_contador" # arquivo named pipe # dir onde serao colocados os arquivos contadores de cada pagina DIR="/var/www/contador" [ -p "$PIPE" ] || mkfifo "$PIPE" while : do for URL in $(cat < $PIPE) do FILE="$DIR/$(echo $URL | sed 's,.*/,,')" # OBS1: no sed acima, como precisava procurar # uma barra,usamos vrgula como separador. # OBS2: 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 Como s este script altera os arquivos, no existe problema de concorrncia. Este script ser um daemon, isto , rodar em background. Quando uma pgina sofrer um acesso, ela escrever a sua URL no arquivo de pipe. Para testar, execute este comando: echo "teste_pagina.html" > /tmp/pipe_contador Para evitar erros, em cada pgina que quisermos adicionar o contador acrescentamos a seguinte linha: <!--#exec cmd="echo $REQUEST_URI > /tmp/pipe_contador"--> Note que a varivel $REQUEST_URI contm o nome do arquivo que o navegador (browser) requisitou. Este ltimo exemplo, fruto de uma idia que troquei com o amigo e mestre em Shell, Thobias Salazar Trevisan que escreveu o script e colocou-o em sua excelente URL. Aconselho a todos que querem aprender Shell a dar uma olhada nela (D uma olhada e inclua-a nos favoritos). Ahhh! Voc pensa que o assunto sobre named pipes est esgotado? Enganou-se. Vou mostrar um uso diferente a partir de agora.

76

Substituio de processos
Acabei de mostrar um monte de dicas sobre named pipes, agora 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 comando ou um pipeline de comandos entre parnteses e um < ou um > grudado na frente do parntese da esquerda. Por exemplo, teclando-se o comando: $ cat <(ls -l) Resultar no comando ls -l executado em um subshell como normal (por estar entre parnteses), 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), e teremos a mesma sada que a gerada pela listagem do ls -l, porm dando um ou mais passos que o usual, isto , mais onerosa para o computador. Como poderemos constatar isso? Fcil... Veja o comando a seguir: $ ls -l >(cat) l-wx------ 1 jneves jneves 64 Aug 27 12:26 /dev/fd/63 -> pipe:[7050] ... Realmente um named pipe. Voc deve estar pensando que isto uma maluquice de nerd, n? Ento suponha que voc tenha 2 diretrios: dir e dir.bkp e deseja saber se os dois esto iguais (aquela velha dvida: ser que meu backup est atualizado?). Basta comparar os dados dos arquivos dos diretrios com o comando cmp, fazendo: $ cmp <(cat dir/*) <(cat dir.bkp/*) || echo backup furado ou, melhor ainda: $ cmp <(cat dir/*) <(cat dir.bkp/*) >/dev/null || echo backup furado Da forma acima, a comparao foi efetuada em todas as linhas de todos os arquivos de ambos os diretrios. Para acelerar o processo, poderamos compara somente a listagem longa de ambos os diretrios, pois qualquer modificao que um arquivo sofra, mostrada na data/hora de alterao e/ou no tamanho do arquivo. Veja como ficaria: $ cmp <(ls -l dir) <(ls -l dir.bkp) >/dev/null || echo backup furado Este um exemplo meramente didtico, mas so tantos os comandos que produzem mais de uma linha de sada, que serve como guia para outros. Eu quero gerar uma listagem dos meus arquivos, numerando-os e ao final dar o total de arquivos do 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" 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" Quando executasse o script, pareceria estar tudo certo, 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 subshell criado pelo pipe (|) e que terminou no comando done, levando com ele todas as variveis criadas no seu interior e as alteraes feitas em todas as variveis, inclusive as criadas externamente. Somente para te mostrar que uma varivel criada fora do subshell e alterada em seu interior perde as alteraes feitas ao seu final, execute o script a seguir: #!/bin/bash LIST="" ls | while read FILE do LIST="$FILE $LIST" done echo :$LIST: # Criada no shell principal # Inicio do subshell # Alterada dentro do subshell # Fim do subshell

Ao final da execuo voc ver que aperecero apenas dois dois-pontos (::). Mas no incio deste exemplo eu disse que era meramente didtico porque existem formas melhores de fazer a mesma tarefa. Veja s estas duas: $ ls | ln ou ento, usando a prpria substituio de processos:

77

$ cat -n <(ls) Um ltimo exemplo: voc deseja comparar arq1 e arq2 usando o comando comm, mas este comando necessita que os arquivos estejam classificados. Ento a melhor forma de proceder : $ comm <(sort arq1) <(sort arq2) Esta forma evita que voc faa as seguintes operaes: $ sort arq1 > /tmp/sort1 $ sort arq2 > /tmp/sort2 $ comm /tmp/sort1 /tmp/sort2 $ rm -f /tmp/sort1 /tmp/sort2 Pessoal, o nosso Papo de Botequim chegou ao fim . Curti muito aqui e recebi diversos elogios pelo trabalho desenvolvido ao longo de doze meses e, o melhor de tudo, fiz muitas amizades e tomei muitos chopes de graa com os leitores que encontrei pelos congressos e palestras que ando fazendo pelo nosso querido Brasil. O que vou escrever aqui no est combinado nem sei se ser publicado, mas como os editores desta revista so dois malucos beleza (ambos Rafael), bem capaz de deixarem passar. o seguinte: se quiserem que o Papo de Botequim continue, entulhem a caixa postal da Linux Magazine pedindo por isso e desde j escolham o prximo tema entre sed + expresses regulares ou linguagem awk. De qualquer forma, caso no consigamos sensibilizar a direo da revista, me despeo de todos mandando um grande abrao aos barbudos e beijos s meninas e agradeendo os mais de 100 e-mails que recebi (todos elogiosos) e todos devidamente respondidos. sade de todos ns: Tim, Tim. - Chico, fecha a minha conta porque vou mudar de botequim. No se esquea, qualquer dvida ou falta de companhia para um chope ou at para falar mal dos polticos s mandar um e-mail para julio.neves@gmail.com. Vou aproveitar tambm para mandar o meu jab: diga para os amigos que quem estiver afim de fazer um curso porreta de programao em Shell que mande um e-mail para julio.neves@uniriotec.br para informar-se. Valeu!

Tira Gosto Shell em pedaos pequenos e gostosos


Em construo para sempre
Esta pgina, apesar de estar no escopo do Papo de Botequim, nunca foi publicada na Linux Magazine. Trata-se de artigos que escrevi para outras mdias, dicas teis que li pela internet afora (e neste caso com os devidos crditos), contribuies deste pessoal do Software Livre, maravilhoso e sempre pronto a ajudar e da imperdvel Lista de Shell Script

Passando parmetros com xargs Opes do xargs Opo -i Opo -n Opo -p Opo -t Resumo Here Strings Rotatrio Peczenyj Aritmtica em Shell O uso do bc Outras formas de trabalhar com inteiros Baseando Testes usando expresses regulares Colorindo a tela Ganhando o jogo com mais coringas

78

Passando parmetros com xargs


Existe um comando, cuja funo primordial construir listas de parmetros e pass-la para a execuo de outros programas ou instrues. Este comando o xargs e deve ser usado da seguinte maneira: xargs [comando [argumento inicial]] Caso o comando, que pode ser inclusive um script Shell, seja omitido, ser usado por default o echo. O xargs combina o argumento inicial com os argumentos recebidos da entrada padro, de forma a executar o comando especificado uma ou mais vezes. Exemplo: Vamos procurar em todos os arquivos abaixo de um determinado diretrio uma cadeia de caracteres usando o comando find com a opo -type f para pesquisar somente os arquivos normais, desprezando diretrios, arquivos especiais, arquivos de ligaes, etc, e vamos torn-la mais genrica recebendo o nome do diretrio inicial e a cadeia a ser pesquisada como parmetros. Para isso fazemos: $ cat grepr # Grep recursivo # Pesquisa a cadeia de caracteres definida em $2 a partir do diretorio $1 # find $1 -type f -print|xargs grep -l "$2" Na execuo deste script procuramos, a partir do diretrio definido na varivel $1, todos os arquivos que continham a cadeia definida na varivel $2. Exatamente a mesma coisa poderia ser feito se a linha do programa fosse a seguinte: find $1 -type f -exec grep -l "$2" {} \; O primeiro processo tem duas grandes desvantagens sobre o anterior:

A primeira bastante visvel: o tempo de execuo deste mtodo muito superior ao daquele, isso porque o grep ser feito em cada arquivo que lhe for passado pelo find, um-a-um, ao passo que com o xargs, ser passada toda, ou na pior das hipteses, a maior parte possvel, da lista de arquivos gerada pelo find; Dependendo da quantidade de arquivos encontrados que atendem ao find, poderemos ganhar aquela famosa e fatdica mensagem de erro Too many arguments indicando um estouro da pilha de execuo do grep. Como foi dito no item anterior, se usarmos o xargs ele passar para o grep a maior quantidade de parmetros possvel, suficiente para no causar este erro, e caso necessrio executar o grep mais de uma vez. A pessoal do linux que usa o ls colorido que nem porta de tinturaria: nos exemplos a seguir que envolvem esta instruo, voc devem usar a opo --color=none, seno existem grandes chances dos resultados no ocorrerem como o esperado.

Vamos agora analisar um exemplo que mais ou menos o inverso deste que acabamos de ver. Desta vez, vamos fazer um script para remover todos os arquivos do diretrio corrente, pertencentes a um determinado usurio. A primeira idia que surge , como no caso anterior, usar um comando find, da seguinte maneira: find . -user cara -exec rm -f {} \; Quase estaria certo, o problema que desta forma voc removeria no s os arquivos do cara no diretrio corrente, mas tambm de todos os outros subdiretrios "pendurados" neste. Vejamos ento como fazer: ls -l | grep " cara " | cut -c55- | xargs rm Desta forma, o grep selecionou os arquivos que continham a cadeia cara no diretrio corrente listado pelo ls -l. O comando cut pegou somente o nome dos arquivos, passando-os para a remoo pelo rm usando o comando xargs como ponte O xargs tambm uma excelente ferramenta de criao de one-liners (scripts de somente uma linha). Veja este para listar todos os donos de arquivos (inclusive seus links) "pendurados" no diretrio /bin e seus subdiretrios. $ find /bin -type f -follow | \ xargs ls -al | tr -s ' ' | cut -f3 -d' ' | sort -u Muitas vezes o /bin um link (se no me engano, no Solaris o ) e a opo -follows obriga o find a seguir o link. O comando xargs alimenta o ls -al e a seqncia de comandos seguinte para pegar somente o 3 campo (dono) e classific-lo devolvendo somente uma vez cada dono (opo -u do comando sort, que equivale ao comando uniq).

Opes do xargs
Voc pode usar as opes do xargs para construir comandos extremamente poderosos.

79

Opo -i
Para exemplificar isso e comear a entender as principais opes desta instruo, vamos supor que temos que remover todos as arquivos com extenso .txt sob o diretrio corrente e apresentar os seus nomes na tela. Veja o que podemos fazer: $ find . -type f -name "*.txt" | \ xargs -i bash -c "echo removendo {}; rm {}" A opo -i do xargs troca pares de chaves ({}) pela cadeia que est recebendo pelo pipe (|). Ento neste caso as chaves ({}) sero trocadas pelos nomes dos arquivos que satifaam ao comando find.

Opo -n
Olha s a brincadeira que vamos fazer com o xargs: $ ls | xargs echo > arq.ls $ cat arq.ls arq.ls arq1 arq2 arq3 $ cat arq.ls | xargs -n1 arq.ls arq1 arq2 arq3 Quando mandamos a sada do ls para o arquivo usando o xargs, comprovamos o que foi dito anteriormente, isto , o xargs manda tudo que possvel (o suficiente para no gerar um estouro de pilha) de uma s vez. Em seguida, usamos a opo -n 1 para listar um por vez. S para dar certeza veja o exemplo a seguir, quando listaremos dois em cada linha: $ cat arq.ls | xargs -n 2 arq.ls arq1 arq2 arq3 Mas a linha acima poderia (e deveria) ser escrita sem o uso de pipe (|), da seguinte forma: $ xargs -n 2 < arq.ls

Opo -p
Outra opo legal do xargs a -p, na qual o sistema pergunta se voc realmente deseja executar o comando. Digamos que em um diretrio voc tenha arquivos com a extenso .bug e .ok, os .bug esto com problemas que aps corrigidos so salvos como .ok. D uma olhadinha na listagem deste diretrio: $ ls dir arq1.bug arq1.ok arq2.bug arq2.ok ... arq9.bug arq9.ok Para comparar os arquivos bons com os defeituosos, fazemos: $ ls | xargs -p -n2 diff -c diff -c arq1.bug arq1.ok ?...y .... diff -c arq9.bug arq9.ok ?...y

Opo -t
Para finalizar, o xargs tambm tem a opo -t, onde vai mostrando as instrues que montou antes de execut-las. Gosto muito desta opo para ajudar a depurar o comando que foi montado.

Resumo
Ento podemos resumir o comando de acordo com a tabela a seguir: Opo -i -nNum -lNum Ao Substitui o par de chaves ({}) pelas cadeias recebidas Manda o mximo de parmetros recebidos, at o mximo de Num para o comando a ser executado Manda o mximo de linhas recebidas, at o mximo de Num para o comando a ser executado

80

-p -t

Mostra a linha de comando montada e pergunta se deseja execut-la Mostra a linha de comando montada antes de execut-la

Here Strings
Acabei de dar um treinamento no Instituto de Pesquisas Eldorado, onde peguei uma turma de 12 alunos de altssima qualidade. Devido ao preparo do pessoal, o nvel do curso foi to bom que cheguei a abordar alguns conceitos pouco usados como o redirecionamento por here strings. Ento pensei: se uma coisa to til, porque no mostr-la a todos? Ai vai ... Primeiro um programador com complexo de inferioridade criou o redirecionamento de entrada e representou-o com um sinal de menor (<) para representar seus sentimento. Em seguida, outro sentindo-se pior ainda, criou o here document representando-o por dois sinais de menor (<<) porque sua fossa era maior. O terceiro, pensou: "estes dois no sabem o que estar por baixo"... Ento criou o here strings representado por trs sinais de menor (<<<). Brincadeiras a parte, o here strings utilssimo e, no sei porque, um perfeito desconhecido. Na pouqussima literatura que h sobre o tema, nota-se que o here strings freqentemente citado como uma variante do here document, teoria com a qual discordo pois sua aplicabilidade totalmente diferente daquela. Sua sintaxe simples: $ comando <<< $cadeia Onde cadeia expandida e alimenta a entrada primria (stdin) de comando. Como sempre, vamos direto aos exemplos dos dois usos mais comuns para que vocs prprios tirem suas concluses.

Uso #1. Substituindo a famigerada construo echo "cadeia" | comando, que fora um fork, criando um subshell e onerando o tempo de execuo.

Exemplos: $ a="1 2 3" $ cut -f 2 -d ' ' <<< $a # Normalmente faz-se: echo $a | cut -f 2 -d ' ' 2 $ echo $NomeArq Meus Documentos # Arrrghhh! $ tr "A-Z " "a-z_" <<< $NomeArq # Substituindo o echo $NomeArq | tr "A-Z " "a-z_" meus_documentos $ bc <<<"3 * 2" 6 $ bc <<<"scale = 4; 22 / 7" 3.1428 Para mostrar a melhoria no desempenho, vamos fazer um loop de 500 vezes usando o exemplo dados para o comando tr: Veja agora esta seqncia de comandos com medidas de tempo: $ time for ((i=1; i<= 500; i++)); { tr "A-Z " "a-z_" <<< $NomeArq >/dev/null; } real user sys $ time 0m3.508s 0m2.400s 0m1.012s for ((i=1; i<= 500; i++)); { echo $NomeArq | tr "A-Z " "a-z_" >/dev/null; }

real 0m4.144s user 0m2.684s sys 0m1.392s Veja agora esta seqncia de comandos com medidas de tempo: $ time for ((i=1;i<=100;i++)); { who | cat > /dev/null; } real 0m1.435s user 0m1.000s sys 0m0.380s $ time for ((i=1;i<=100;i++)); { cat <(who) > /dev/null; } real 0m1.552s user 0m1.052s sys 0m0.448s $ time for ((i=1;i<=100;i++)); { cat <<< $(who) > /dev/null; } real 0m1.514s user 0m1.056s sys 0m0.412s Observando este quadro voc ver que no primeiro usamos a forma convencional, no segundo usamos um named pipe temporrio

81

para executar uma substituio de processos e no terceiro usamos here strings. Notar tambm que ao contrrio do exemplo anterior, aqui o uso de here strings no foi o mais veloz. Mas repare bem que neste ltimo caso o comando who est sendo executado em um subshell e isso onerou o processo como um todo. Vejamos uma forma rpida de inserir uma linha como cabealho de um arquivo: $ cat num 1 2 3 4 5 6 7 8 9 10 $ cat - num <<< "Impares Pares" Impares Pares 1 2 3 4 5 6 7 8 9 10 Uso #2. Outra forma legal de usar o here strings casando-o com um comando read, no perdendo de vista o que aprendemos sobre IFS (veja aqui, na explicao do comando for). O comando cat com as opes -vet mostra o <ENTER> como $, o <TAB> como ^I e os outros caracteres de controle com a notao ^L onde L uma letra qualquer. Vejamos ento o contedo de uma varivel e depois vamos ler cada um de seus campos: Exemplos: $ echo "$Linha" Leonardo Mello (21)3313-1329 $ cat -vet <<< "$Linha" Leonardo Mello^I(21)3313-1329$ # Os separadores sao branco e <TAB> (^I) $ read Nom SNom Tel <<< "$Linha" $ echo "${Nom}_$S{Nom}_$Tel" # Vamos ver se ele leu cada um dos campos Leonardo_Mello_(21)3313-1329 # Leu porque os separadores casavam com o IFS Tambm podemos ler direto para um vetor (array) veja: $ echo $Frutas Pera:Uva:Ma $ IFS=: $ echo $Frutas Pera Uva Ma # Sem as aspas o shell mostra o IFS como branco $ echo "$Frutas" Pera:Uva:Ma # Ahhh, agora sim! $ read -a aFrutas <<< "$Frutas" # A opo -a do read, l para um vetor $ for i in 0 1 2 > do > echo ${aFrutas[$i]} # Imprimindo cada elemento do vetor > done Pera Uva Ma

Rotatrio Peczenyj
Estava, como fao todo dia, dando um lida nos e-mails da "Lista de Shell Script" , quando vi uma "descoberta" totalmente inusitada do Tiago Barcellos Peczenyj. Quando resolvi montar esta coletnea de dicas, me lembrei disso e pedi-lhe para me encaminhar aquele e-mail novamente. O texto a a seguir o e-mail que ele me mandou, s inseri o ltimo exemplo e tirei as abreviaturas. Julio descobri uma forma para o Shell criar combinaoes fazendo rotao com os elementos estipulados. Podemos gerar todos os binrios de 0000 a 1111 da seguinte forma: $ A={0,1} $ eval echo $A$A$A$A 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 Uma aplicao pratica que vejo para combinar valores diferentes sem ter que encadear loops nem usar o seq $ A={`seq -s , -f "_%g" 3`} $ eval echo -e $A$A$A |tr ' _' '\n ' | grep -vE '.+?(\b[0-9]+\b).+?\1' 1 2 3 1 3 2

82

2 1 3 2 3 1 3 1 2 3 2 1 Neste caso eu combinei os nmeros de 1 a 3 eliminando repeties com o grep. Usei um tr 'podre' para melhor tratar os dados, saltando linha. O grep simples como voc pode notar, eu vejo se uma determinada parte da combinao (.+?(\b[0-9]+\b).+?) existe em outra parte (\1), se existe eu no deixo imprimir por causa da opo -v, assim 1 1 2 1 2 1 1 1 1 no sero impressos. Agora vai o meu exemplo: o one-liner a seguir gerar todas as permisses possveis (em octal) para o arquivo arq (o exemplo foi interrompido porque existem 512 combinaes de permisses possveis). $ A={`seq -s , 0 7`} $ eval echo -e $A$A$A | tr ' ' '\n' | xargs -i bash -c "chmod {} arq; ls -l arq" ---------- 1 julio julio 100 2006-11-27 11:50 arq ---------x 1 julio julio 100 2006-11-27 11:50 arq --------w- 1 julio julio 100 2006-11-27 11:50 arq --------wx 1 julio julio 100 2006-11-27 11:50 arq -------r-- 1 julio julio 100 2006-11-27 11:50 arq -------r-x 1 julio julio 100 2006-11-27 11:50 arq -------rw- 1 julio julio 100 2006-11-27 11:50 arq -------rwx 1 julio julio 100 2006-11-27 11:50 arq . . . . . . . . . . . . . . . . . . -rwxrwxrw- 1 julio julio 100 2006-11-27 11:50 arq -rwxrwxrwx 1 julio julio 100 2006-11-27 11:50 arq Vamos ver este exemplo passo-a-passo para entend-lo: $ echo $A {0,1,2,3,4,5,6,7} $ eval echo -e $A$A$A 000 001 002 003 004 005 006 007 010 ... ... 767 770 771 772 773 774 775 776 777 $ eval echo -e $A$A$A | tr ' ' '\n' # O tr trocar cada espaco em branco por um <ENTER> 000 001 002 003 . . . 774 775 776 777 A seguir o xargs (clique para dicas do xargs) executa o comando bash -c (que serve para executar uma linha de comandos) que por sua vez executa o chmod e o ls -l para mostrar que as permisses esto sendo alteradas.

Aritmtica em Shell
Antigamente usvamos o comando expr para fazer operaes aritmticas e muita gente ainda usa, pois compatvel com quaquer ambiente. Exemplo: $ expr 7 \* 5 / 3 # 7 vezes 5 = 35 dividido por 3 = 11 14 Neste artigo porm, vamos ver outras formas no tanto conhecidas, porm mais simples de usar, mais elaboradas e com preciso maior.

O uso do bc
Uma forma bacana de fazer clculos em Shell usada normalmente quando a expresso aritmtica mais complexa, ou quando necessrio trabalharmos com casas decimais usar a instruo calculadora do UNIX/LINUX. O bc. Veja como: Exemplo:

83

$ echo "(2 + 3) * 5" | bc # Parnteses usados para dar precedncia 25 Para trabalhar com nmeros reais (nmeros no necessariamente inteiros), especifique a preciso (quantidade de decimais) com a opo scale do comando bc. Assim vejamos o penltimo exemplo: $ echo "scale=2; 7*5/3" | bc 11.66 Outros exemplos: $ echo "scale=3; 33.333*3" | bc 99.999 $ num=5 $ echo "scale=2; ((3 + 2) * $num + 4) / 3" | bc 9.66 Obviamente todos os exemplos acima no caso de linux, poderiam (e deveriam) ser escritos usando Here Strings. Veja os ltimos como ficariam: $ bc <<< "scale=3; 33.333*3" 99.999 $ num=5 $ bc <<< "scale=2; ((3 + 2) * $num + 4) / 3" 9.66 Uma vez apareceu na lista (excelente por sinal) de Shell script no Yahoo (http://br.groups.yahoo.com/group/shell-script/) um cara com a seguinte dvida: "eu tenho um arquivo cujos campos esto separados por e o terceiro deles possui nmeros. Como posso calcular a soma de todos os nmeros desta coluna do arquivo?" Mandei a seguinte resposta: $ echo $(cut -f3 num | tr '\n' +)0 | bc 20.1 Vamos por partes para entender melhor e primeiramente vamos ver como era o arquivo que fiz para teste: $ cat num a b 3.2 a z 4.5 w e 9.6 q w 2.8 Como pode-se ver, est dentro do padro do problema, onde eu tenho como terceiro campo nmeros reais. Vamos ver o que faria a primeira parte da linha de comandos, onde eu transformo os caracteres (new-line) em um sinal de mais (+): $ cut -f3 num | tr '\n' + 3.2+4.5+9.6+2.8+ Se eu mandasse desse jeito para o bc, ele me devolveria um erro por causa daquele sinal de mais (+) solto no final do texto. A minha sada foi colocar um zero no final, pois somando zero o resultado no se alterar. Vamos ver ento como ficou: $ echo $(cut -f3 num | tr -s '\n' +)0 3.2+4.5+9.6+2.8+0 Isso o que se costuma chamar one-liner, isto , cdigos que seriam complicados em outras linguagens (normalmente seria necessrio criar contadores e fazer um loop de leitura somando o terceiro campo ao contador) e em Shell so escritos em uma nica linha. H tambm gente que chama isso de mtodo KISS, que o acrnimo de Keep It Simple Stupid. Mas o potencial de uso desta calculadora no se encerra a, existem diversas facilidades por ela propiciadas. Veja s este exemplo: $ echo "obase=16; 11579594" | bc B0B0CA $ echo "ibase=16; B0B0CA" | bc # B, zero, B, zero, C, e A 11579594 Nestes exemplos vimos como fazer mudanas de base de numerao com o uso do bc. Na primeira explicitamos a base de sada (obase) como 16 (hexadecimal) e na segunda, dissemos que a base da entrada (ibase) era 10 (decimal).

Outras formas de trabalhar com inteiros


Outra forma muito legal de fazer clculos usar a notao $((exp aritmtica)). bom ficar atento, porm, ao fato desta sintaxe no ser universalizada. O Bourne Shell (sh), por exemplo, no a reconhece. Exemplo: Usando o mesmo exemplo que j havamos usado: $ echo $(((2+3)*5)) 25 Agora olha s esta maluquice: # Os parnteses mais internos priorizaram o 2+3

$ tres=3 $ echo $(((2+tres)*5)) # Varivel tres no precedida pelo $

84

25 $ echo $(((2+$tres)*5)) # Varivel tres precedida pelo $ 25 U!! No o cifro ($) precedente que caracteriza uma varivel? Sim, porm em todos os sabores UNIX que testei, sob bash ou ksh, ambas as formas de construo produzem uma boa aritmtica. Preste a ateno nesta seqncia: $ unset i # $i mooorreu! $ echo $((i++)) 0 $ echo $i 1 $ echo $((++i)) 2 $ echo $i 2 Repare que apesar da varivel no estar definida, pois foi feito um unset nela, nenhum dos comandos acusou erro, porque, como estamos usando construes aritmticas, sempre que uma varivel no existe, inicializada com zero (0). Repare que o i++ produziu zero (0). Isto ocorre porque este tipo de construo chama-se ps-incrementao, isto , primeiramente o comando executado e s ento a varivel incrementada. No caso do ++i, foi feita uma pr-incrementao: primeiro incrementou e somente aps o comando foi executado. Tambm so vlidos: $ echo $((i+=3)) 5 $ echo $i 5 $ echo $((i*=3)) 15 $ echo $i 15 $ echo $((i%=2)) 1 $ echo $i 1 Estas trs operaes seriam o mesmo que: i=$((i+3)) i=$((i*3)) i=$((i%2)) E isto seria vlido para todos os operadores aritmticos o que em resumo produziria a tabela a seguir: Expanso Aritmtica Expresso id++ id-++id -id ** * / % + <= >= < > == != && || Resultado ps-incremento e ps-decremento de variveis pr-incremento e pr-decremento de variveis exponenciao multiplicao, diviso, resto da diviso adio, subtrao comparao igualdade, desigualdade E lgico

OU lgico Mas o auge desta forma de construo com duplo parnteses o seguinte: $ echo $var 50 $ var=$((var>40 ? var-40 : var+40)) $ echo $var 10

85

$ var=$((var>40 ? var-40 : var+40)) $ echo $var 50 Este tipo de construo deve ser lido da seguinte forma: caso a varivel var seja maior que 40 (var>40), ento (?) faa var igual a var menos 40 (var-40), seno (:) faa var igual a var mais 40 (var+40). O que quis dizer que os caracteres ponto-deinterrogao (?) e dois-pontos (:) fazem o papel de "ento" e "seno", servindo desta forma para montar uma operao aritmtica condicional. Da mesma forma que usamos a expresso $((...)) para fazer operaes aritmticas, tambm poderamos usar a intrnseca (builtin) let ou construo do tipo $[...]. Os operadores so os mesmos para estas trs formas de construo, o que varia um pouco a operao aritmtica condicional com o uso do let. Vejamos como seria: $ echo $var 50 $ let var='var>40 ? var-40 : var+40' $ echo $var 10 $ let var='var>40 ? var-40 : var+40' $ echo $var 50

Baseando
Se voc quiser trabalhar com bases diferentes da decimal, basta usar o formato: base#numero Onde base um nmero decimal entre 2 e 64 representando o sistema de numerao, e numero um nmero no sistema numrico definido por base. Se base# for omitida, ento 10 assumida como default. Os algarismos maiores que 9 so representados por letras minsculas, maisculas, @ e _, nesta ordem. Se base for menor ou igual a 36, maisculas ou minsculas podem ser usadas indiferentemente para definir algarismos maiores que 9 (no est mal escrito, os algarismos do sistema hexadecimal, por exemplo, variam entre 0 (zero) e F). Vejamos como isso funciona: $ echo $[2#11] 3 $ echo $((16#a)) 10 $ echo $((16#A)) 10 $ echo $((2#11 + 16#a)) 13 $ echo $[64#a] 10 $ echo $[64#A] 36 $ echo $((64#@)) 62 $ echo $((64#_)) 63 Nestes exemplos usei as notaes $((...)) e $[...] indistintamente, para demonstrar que ambas funcionam. Funciona tambm uma mudana automtica para a base decimal, desde que voc esteja usando a conveno numrica do C, isto , em 0xNN, o NN ser tratado como um hexadecimal e em 0NN, o NN ser visto como um octal. Veja o exemplo: Exemplo $ echo $((10)) # decimal 10 $ echo $((010)) # octal 8 $ echo $((0x10)) # hexadecimal 16 $ echo $((10+010+0x10)) # Decimal + octal + hexadecimal 64 Ah, j ia me esquecendo! As expresses aritmticas com os formatos $((...)), $[...] e com o comando let usam os mesmos operadores usados na instruo expr, alm dos operadores unrios (++, --, +=, *=, ...) e condicionais que acabamos de ver.

86

Testes usando expresses regulares


No Papo de Botequim 004, ns falamos tudo sobre comandos condicionais, mas faltou um que no existia quela poca. Neste mesmo Papo de Botequim, na seo E tome de test ns chegamos a falar de uma construo do tipo: [[ Expressao ]] && cmd Onde o comando cmd ser executado caso a expresso condicional Expressao seja verdadeira. Disse ainda que Expressao poderia ser estipulada de acordo com as regras de Gerao de Nome de Arquivos (File Name Generation). A partir do bash verso 3, foi incorporado a esta forma de teste um operador representado por =~, cuja finalidade fazer comparaes com Expresses Regulares. Exemplo: $ echo $BASH_VERSION # Conferindo se a verso do Bash igual ou superior a 3.0.0 3.2.17(15)-release $ Cargo=Senador $ [[ $Cargo =~ ^(Governa|Sena|Verea)dora?$ ]] && echo poltico poltico $ Cargo=Senadora $ [[ $Cargo =~ ^(Governa|Sena|Verea)dora?$ ]] && echo poltico poltico $ Cargo=Diretor $ [[ $Cargo =~ ^(Governa|Sena|Verea)dora?$ ]] && echo poltico $ Vamos dar uma esmiuada na Expresso Regular ^(Governa|Sena|Verea)dora?$: ela casa com tudo que comea (^) por Governa, ou (|) Sena, ou (|) Verea, seguido de dor e seguido de um a opcional (?). O cifro ($) serve para marcar o fim. Em outras palavras esta Expresso Regular casa com Governador, Senador, Vereador, Governadora, Senadora e Vereadora.

Colorindo a tela
Como voc j havia visto no Papo de Botequim 007, o comando tput serve para fazer quase tudo referente a formatao de tela, mas o que no foi dito que com ele tambm pode-se usar cores de frente (dos caracteres) e de fundo. Existem tambm outras formas de fazer o mesmo, acho porm, esta que veremos agora, mais intuitiva (ou menos desintuitiva). A tabela a seguir mostra os comandos para especificarmos os padres de cores de frente (foreground) ou de fundo (background): Obtendo cores com o comando tput Comando tput setaf n tput setab n Efeito Especifica n como a cor de frente (foreground) Especifica n como a cor de fundo (background)

Bem, agora voc j sabe como especificar o par de cores, mas ainda no sabe as cores. A tabela a seguir mostra os valores que o n (da tabela anterior) deve assumir para cada cor: Valores das cores com o comando tput Valor 0 1 2 3 4 5 6 7 Neste ponto voc j pode comear a brincar com as cores. - Mas pera, ainda so muito poucas! - , tem toda razo... O problema que ainda no lhe disse que se voc colocar o terminal em modo de nfase (tput bold), estas cores geram outras oito. Vamos montar ento a tabela definitiva de cores: Valores das cores com o comando tput Valor 0 Cor Preto Cor aps tput bold Cinza escuro Cor Preto Vermelho Verde Marrom Azul Prpura Ciano Cinza claro

87

1 2 3 4 5 6

Vermelho Verde Marron Azul Prpura Ciano

Vermelho claro Verde claro Amarelo Azul Brilhante Rosa Ciano claro

7 Cinza claro Branco Colocar como exemplo escrever com letra branca sobre fundo preto.

Ganhando o jogo com mais coringas


Estava eu lendo meus e-mails quando recebo um do Tiago enviado para a lista de Shell Script (j falei da lista e do Tiago no Rotatrio Peczenyj). A seguir o contedo do e-mail: No sei se conhecimento de todos mas o shell possui, alem do globbing normal (a expanso *, ? e [a-z] de nomes de arquivos e diretrios), um globbing extendido. Acho que, em alguns casos, podera ser BEM util, eliminando um pipe para um grep por exemplo. So eles: ?(padrao) Casa zero ou uma ocorrncia de um determinado padrao *(padrao) Casa zero ou mais ocorrncias de um determinado padrao +(padrao) Casa uma ou mais ocorrncias de um determinado padrao @(padrao) Casa com exatamente uma ocorrncia de um determinado padrao !(padrao) Casa com qualquer coisa, exceto com padrao Para poder utiliz-lo precisa executar o shopt conforme o exemplo abaixo: $ shopt -s extglob $ ls file filename filenamename fileutils $ ls file?(name) file filename $ ls file*(name) file filename filenamename $ ls file+(name) filename filenamename $ ls file@(name) filename $ ls file!(name) # divertido esse file filenamename fileutils $ ls file+(name|utils) filename filenamename fileutils $ ls file@(name|utils) # "lembra" um {name,utils} filename fileutils Fica ai a dica No se esquea, qualquer dvida ou falta de companhia para um chope s mandar um e-mail para julio.neves@gmail.com. Vou aproveitar tambm para mandar o meu jab: diga para os amigos que quem estiver afim de fazer um curso porreta de programao em Shell que mande um e-mail para julio.neves@uniriotec.br para informar-se. Valeu!

88

Anda mungkin juga menyukai