shell script

142
Shell Script 21 de dezembro de 2006

Upload: hudson-augusto

Post on 30-Jun-2015

450 views

Category:

Documents


4 download

TRANSCRIPT

Page 1: Shell Script

Shell Script21 de dezembro de 2006

Page 2: Shell Script

Sumário

I Sobre essa apostila 2

II Informações Básicas 4

III Shell Script 9

1 O que é Shell Script 10

2 Plano de ensino 112.1 Objetivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112.2 Público Alvo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112.3 Pré-requisitos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112.4 Descrição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112.5 Metodologia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112.6 Programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122.7 Avaliação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132.8 Bibliografia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

3 Introdução 14

4 Expressões Regulares 154.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154.2 Comando grep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154.3 Os Metacaractres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164.4 Metacaractres quantificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174.5 Metacaractres tipo âncora . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

4.5.1 Circunflexo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194.5.2 Cifrão - o fim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194.5.3 Borda - a limítrofe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

4.6 Outros metacaracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204.7 Explicando-os melhor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

4.7.1 Escape . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204.7.2 Ou . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214.7.3 Grupo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214.7.4 Retrovisor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

1

Page 3: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

5 Parte I 245.1 Diálogo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245.2 O ambiente Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245.3 O ambiente Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255.4 Uma rapidinha nos principais sabores de Shell . . . . . . . . . . . . . . . . . . . . . 255.5 Explicando o funcionamento do Shell . . . . . . . . . . . . . . . . . . . . . . . . . . 265.6 Caracteres para remoção do significado . . . . . . . . . . . . . . . . . . . . . . . . . 285.7 Caracteres de redirecionamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

5.7.1 Caracteres de redirecionamento . . . . . . . . . . . . . . . . . . . . . . . . . 295.7.2 Redirecionamento da Saída Padrão . . . . . . . . . . . . . . . . . . . . . . . 295.7.3 Redirecionamento da Saída de erro Padrão . . . . . . . . . . . . . . . . . . . 305.7.4 Redirecionamento da Entrada Padrão . . . . . . . . . . . . . . . . . . . . . . 31

5.8 Redirecionamento de Comandos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325.9 Caracteres de Ambiente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

6 Parte II 376.1 Diálogo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376.2 Eu fico com o grep, você com a gripe . . . . . . . . . . . . . . . . . . . . . . . . . . 376.3 A família grep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386.4 Exemplos da família grep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396.5 Vamos montar uma cdteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406.6 Passando parâmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406.7 Macetes paramétricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

7 Parte III 487.1 Trabalhando com cadeias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 487.2 O comando cut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

7.2.1 O comando cut a opção -c . . . . . . . . . . . . . . . . . . . . . . . . . . . . 487.2.2 O comando cut a opção -f . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

7.3 O comando tr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 517.3.1 Trocando caracteres com tr . . . . . . . . . . . . . . . . . . . . . . . . . . . . 517.3.2 Removendo caracteres com tr . . . . . . . . . . . . . . . . . . . . . . . . . . 527.3.3 Xpremendo com tr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 537.3.4 O Comando if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

8 Parte IV 588.1 Diálogo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 588.2 O comando Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 598.3 Continuação do comando test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 618.4 Querida, encolheram o comando condicional . . . . . . . . . . . . . . . . . . . . . . 648.5 E tome de test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 668.6 Acaso casa com case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

9 Parte V 709.1 Comandos de Loop (ou laço) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 709.2 O Comando for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

9.2.1 Primeira sintaxe do comando for . . . . . . . . . . . . . . . . . . . . . . . . . 709.2.2 Segunda sintaxe do comando for . . . . . . . . . . . . . . . . . . . . . . . . . 759.2.3 Terceira sintaxe do comando for . . . . . . . . . . . . . . . . . . . . . . . . . 77

2

Page 4: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

10 Parte VI 7910.1 Um pouco mais de for e matemática . . . . . . . . . . . . . . . . . . . . . . . . . . . 7910.2 O Comando while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8010.3 O Comando Until . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8410.4 Atalhos no loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86

11 Parte VII 9011.1 O comando tput . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9011.2 E agora podemos ler os dados na tela . . . . . . . . . . . . . . . . . . . . . . . . . . 9311.3 Vamos ler arquivos? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

12 Parte VIII 10112.1 Funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10112.2 O comando source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105

13 Parte IX 11113.1 Envenenando a escrita . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11113.2 Principais Variáveis do Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11313.3 Expansão de parâmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116

14 Parte X 12114.1 O comando eval . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12114.2 Sinais de Processos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12414.3 Sinais assassinos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12514.4 O trap não atrapalha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12514.5 O comando getopts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129

15 Parte XI 13315.1 Named Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13315.2 Sincronização de processos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13415.3 Bloqueio de arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13515.4 Substituição de processos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137

3

Page 5: Shell Script

Parte I

Sobre essa apostila

4

Page 6: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Conteúdo

O conteúdo dessa apostila é fruto da compilação de diversos materiais livres publicados na in-ternet, disponíveis em diversos sites ou originalmente produzido no CDTC em http://www.cdtc.org.br.

O formato original deste material bem como sua atualização está disponível dentro da licençaGNU Free Documentation License, cujo teor integral encontra-se aqui reproduzido na seção demesmo nome, tendo inclusive uma versão traduzida (não oficial).

A revisão e alteração vem sendo realizada pelo CDTC ([email protected]), desde outubrode 2006. Criticas e sugestões construtivas são bem-vindas a qualquer tempo.

Autores

A autoria deste conteúdo, atividades e avaliações é de responsabilidade de Waldemar SilvaJúnior ([email protected]).

O texto original faz parte do projeto Centro de Difusão de Tecnolgia e Conhecimento, que vemsendo realizado pelo ITI em conjunto com outros parceiros institucionais, atuando em conjuntocom as universidades federais brasileiras que tem produzido e utilizado Software Livre, apoiandoinclusive a comunidade Free Software junto a outras entidades no país.

Informações adicionais podem ser obtidas atráves do email [email protected], ou dahome page da entidade, atráves da URL http://www.cdtc.org.br.

Garantias

O material contido nesta apostila é isento de garantias e o seu uso é de inteira responsabi-lidade do usuário/leitor. Os autores, bem como o ITI e seus parceiros, não se responsabilizamdireta ou indiretamente por qualquer prejuízo oriundo da utilização do material aqui contido.

Licença

Copyright ©2006,Waldemar Silva Júnior ([email protected]).

Permission is granted to copy, distribute and/or modify this document under the termsof the GNU Free Documentation License, Version 1.1 or any later version published bythe Free Software Foundation; with the Invariant Chapter being SOBRE ESSA APOS-TILA. A copy of the license is included in the section entitled GNU Free DocumentationLicense.

5

Page 7: Shell Script

Parte II

Informações Básicas

6

Page 8: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Sobre o CDTC

Objetivo Geral

O Projeto CDTC visa a promoção e o desenvolvimento de ações que incentivem a dissemina-ção de soluções que utilizem padrões abertos e não proprietários de tecnologia, em proveito dodesenvolvimento social, cultural, político, tecnológico e econômico da sociedade brasileira.

Objetivo Específico

Auxiliar o Governo Federal na implantação do plano nacional de software não-proprietário ede código fonte aberto, identificando e mobilizando grupos de formadores de opinião dentre osservidores públicos e agentes políticos da União Federal, estimulando e incentivando o mercadonacional a adotar novos modelos de negócio da tecnologia da informação e de novos negóciosde comunicação com base em software não-proprietário e de código fonte aberto, oferecendotreinamento específico para técnicos, profissionais de suporte e funcionários públicos usuários,criando grupos de funcionários públicos que irão treinar outros funcionários públicos e atuar comoincentivadores e defensores de produtos de software não proprietários e código fonte aberto, ofe-recendo conteúdo técnico on-line para serviços de suporte, ferramentas para desenvolvimento deprodutos de software não proprietários e de seu código fonte livre, articulando redes de terceiros(dentro e fora do governo) fornecedoras de educação, pesquisa, desenvolvimento e teste de pro-dutos de software livre.

Guia do aluno

Neste guia, você terá reunidas uma série de informações importantes para que você comeceseu curso. São elas:

• Licenças para cópia de material disponível

• Os 10 mandamentos do aluno de Educação a Distância

• Como participar dos foruns e da wikipédia

• Primeiros passos

É muito importante que você entre em contato com TODAS estas informações, seguindo oroteiro acima.

Licença

Copyright ©2006, Waldemar Silva Júnior ([email protected]).

É dada permissão para copiar, distribuir e/ou modificar este documento sob os termosda Licença de Documentação Livre GNU, Versão 1.1 ou qualquer versão posterior

7

Page 9: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

públicada pela Free Software Foundation; com o Capitulo Invariante SOBRE ESSAAPOSTILA. Uma cópia da licença está inclusa na seção entitulada "Licença de Docu-mentação Livre GNU".

Os 10 mandamentos do aluno de educação online

• 1. Acesso à Internet: ter endereço eletrônico, um provedor e um equipamento adequado épré-requisito para a participação nos cursos a distância.

• 2. Habilidade e disposição para operar programas: ter conhecimentos básicos de Informá-tica é necessário para poder executar as tarefas.

• 3. Vontade para aprender colaborativamente: interagir, ser participativo no ensino a distân-cia conta muitos pontos, pois irá colaborar para o processo ensino-aprendizagem pessoal,dos colegas e dos professores.

• 4. Comportamentos compatíveis com a etiqueta: mostrar-se interessado em conhecer seuscolegas de turma respeitando-os e fazendo ser respeitado pelo mesmo.

• 5. Organização pessoal: planejar e organizar tudo é fundamental para facilitar a sua revisãoe a sua recuperação de materiais.

• 6. Vontade para realizar as atividades no tempo correto: anotar todas as suas obrigações erealizá-las em tempo real.

• 7. Curiosidade e abertura para inovações: aceitar novas idéias e inovar sempre.

• 8. Flexibilidade e adaptação: requisitos necessário à mudança tecnológica, aprendizagense descobertas.

• 9. Objetividade em sua comunicação: comunicar-se de forma clara, breve e transparente éponto - chave na comunicação pela Internet.

• 10. Responsabilidade: ser responsável por seu próprio aprendizado. O ambiente virtual nãocontrola a sua dedicação, mas reflete os resultados do seu esforço e da sua colaboração.

Como participar dos fóruns e Wikipédia

Você tem um problema e precisa de ajuda?

Podemos te ajudar de 2 formas:

A primeira é o uso dos fóruns de notícias e de dúvidas gerais que se distinguem pelo uso:

. O fórum de notícias tem por objetivo disponibilizar um meio de acesso rápido a informaçõesque sejam pertinentes ao curso (avisos, notícias). As mensagens postadas nele são enviadas atodos participantes. Assim, se o monitor ou algum outro participante tiver uma informação queinteresse ao grupo, favor postá-la aqui.Porém, se o que você deseja é resolver alguma dúvida ou discutir algum tópico específico docurso. É recomendado que você faça uso do Forum de dúvidas gerais que lhe dá recursos mais

8

Page 10: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

efetivos para esta prática.

. O fórum de dúvidas gerais tem por objetivo disponibilizar um meio fácil, rápido e interativopara solucionar suas dúvidas e trocar experiências. As mensagens postadas nele são enviadasa todos participantes do curso. Assim, fica muito mais fácil obter respostas, já que todos podemajudar.Se você receber uma mensagem com algum tópico que saiba responder, não se preocupe com aformalização ou a gramática. Responda! E não se esqueça de que antes de abrir um novo tópicoé recomendável ver se a sua pergunta já foi feita por outro participante.

A segunda forma se dá pelas Wikis:

. Uma wiki é uma página web que pode ser editada colaborativamente, ou seja, qualquer par-ticipante pode inserir, editar, apagar textos. As versões antigas vão sendo arquivadas e podemser recuperadas a qualquer momento que um dos participantes o desejar. Assim, ela oferece umótimo suporte a processos de aprendizagem colaborativa. A maior wiki na web é o site "Wikipé-dia", uma experiência grandiosa de construção de uma enciclopédia de forma colaborativa, porpessoas de todas as partes do mundo. Acesse-a em português pelos links:

• Página principal da Wiki - http://pt.wikipedia.org/wiki/

Agradecemos antecipadamente a sua colaboração com a aprendizagem do grupo!

Primeiros Passos

Para uma melhor aprendizagem é recomendável que você siga os seguintes passos:

• Ler o Plano de Ensino e entender a que seu curso se dispõe a ensinar;

• Ler a Ambientação do Moodle para aprender a navegar neste ambiente e se utilizar dasferramentas básicas do mesmo;

• Entrar nas lições seguindo a seqüência descrita no Plano de Ensino;

• Qualquer dúvida, reporte ao Fórum de Dúvidas Gerais.

Perfil do Tutor

Segue-se uma descrição do tutor ideal, baseada no feedback de alunos e de tutores.

O tutor ideal é um modelo de excelência: é consistente, justo e profissional nos respectivosvalores e atitudes, incentiva mas é honesto, imparcial, amável, positivo, respeitador, aceita asidéias dos estudantes, é paciente, pessoal, tolerante, apreciativo, compreensivo e pronto a ajudar.A classificação por um tutor desta natureza proporciona o melhor feedback possível, é crucial, e,para a maior parte dos alunos, constitui o ponto central do processo de aprendizagem.’ Este tutorou instrutor:

• fornece explicações claras acerca do que ele espera, e do estilo de classificação que iráutilizar;

9

Page 11: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

• gosta que lhe façam perguntas adicionais;

• identifica as nossas falhas, mas corrige-as amavelmente’, diz um estudante, ’e explica por-que motivo a classificação foi ou não foi atribuída’;

• tece comentários completos e construtivos, mas de forma agradável (em contraste com umreparo de um estudante: ’os comentários deixam-nos com uma sensação de crítica, deameaça e de nervossismo’)

• dá uma ajuda complementar para encorajar um estudante em dificuldade;

• esclarece pontos que não foram entendidos, ou corretamente aprendidos anteriormente;

• ajuda o estudante a alcançar os seus objetivos;

• é flexível quando necessário;

• mostra um interesse genuíno em motivar os alunos (mesmo os principiantes e, por isso,talvez numa fase menos interessante para o tutor);

• escreve todas as correções de forma legível e com um nível de pormenorização adequado;

• acima de tudo, devolve os trabalhos rapidamente;

10

Page 12: Shell Script

Parte III

Shell Script

11

Page 13: Shell Script

Capítulo 1

O que é Shell Script

Shell é o nome que se dá à linha de comando em modo texto dos sistemas operacionais Linux eUNIX. E portanto os shell scripts são um meio de se juntar uma porção de comandos shell em umsó arquivo para serem executados quantas vezes forem necessárias. Os arquivos de lote (bach)do windows são similares, apenas com uma significativa diferença, já que a linha de comando desistemas Unix e Linux é mais poderosa.

12

Page 14: Shell Script

Capítulo 2

Plano de ensino

2.1 Objetivo

verb Capacitar o usuário para o aprendizado básico dos comandos do Linux.

2.2 Público Alvo

Usuários que tenham perfil administrativo, desenvolvedores, programadores e interessados emgeral.

2.3 Pré-requisitos

Os usuários deverão ser, necessariamente, funcionários públicos e ter conhecimentos básicospara operar um computador com uma distribuição baseada no Debian instalada.

2.4 Descrição

O curso será realizado na modalidade Educação a Distância e utilizará a Plataforma Moodlecomo ferramenta de aprendizagem. O curso tem duração de uma semana e possui um conjuntode atividades (lições, fóruns, glossários, questionários e outros) que deverão ser executadas deacordo com as instruções fornecidas. O material didático está disponível on-line de acordo comas datas pré-estabelecidas em cada tópico.

2.5 Metodologia

O curso está dividido da seguinte maneira:

Duração Descrição do Módulo1 semana Expressões Regulares2 semana Ambiente Shell e Comandos de manipulação de caracteres

13

Page 15: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Todo o material está no formato de lições, e estará disponível ao longo do curso. As liçõespoderão ser acessadas quantas vezes forem necessárias. Aconselhamos a leitura de "Ambien-tação do Moodle", para que você conheça o produto de Ensino a Distância, evitando dificuldadesadvindas do "desconhecimento"sobre o mesmo.

Ao final de cada semana do curso será disponibilizada a prova referente ao módulo estudadoanteriormente que também conterá perguntas sobre os textos indicados. Utilize o material decada semana e os exemplos disponibilizados para se preparar para prova.

Os instrutores estarão a sua disposição ao longo de todo curso. Qualquer dúvida deve serdisponibilizada no fórum ou enviada por e-mail. Diariamente os monitores darão respostas eesclarecimentos.As lições contém o contéudo principal. Elas poderão ser acessadas quantas vezes forem neces-sárias, desde que esteja dentro da semana programada. Ao final de uma lição, você receberáuma nota de acordo com o seu desempenho. Responda com atenção às perguntas de cada lição,pois elas serão consideradas na sua nota final. Caso sua nota numa determinada lição for menordo que 6.0, sugerimos que você faça novamente esta lição.Ao final do curso será disponibilizada a avaliação referente ao curso. Tanto as notas das liçõesquanto a da avaliação serão consideradas para a nota final. Todos os módulos ficarão visíveispara que possam ser consultados durante a avaliação final.Aconselhamos a leitura da "Ambientação do Moodle"para que você conheça a plataforma de En-sino a Distância, evitando dificuldades advindas do "desconhecimento"sobre a mesma.Os instrutores estarão a sua disposição ao longo de todo curso. Qualquer dúvida deverá serenviada no fórum. Diariamente os monitores darão respostas e esclarecimentos.

2.6 Programa

O curso de CVS oferecerá o seguinte conteúdo:

• Expressões regulares

• Ambiente Shell, Linhas de comando, Caracteres de remoção, redirecionamento e ambiente

• A família grep, Passagens de parâmetros

• Comando cut, Comando tr, Usando separadores, Comando condicionais

• O comando eval, O trap não atrapalha, O comando getopts

• Comando test, Simplificações dos comandos condicionais

• Comandos de Loop

• Um pouco mais de Loops e matemática

• O comando tput

• Funções

• Variáveis do Shell

• Named Pipes

14

Page 16: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

2.7 Avaliação

Toda a avaliação será feita on-line.Aspectos a serem considerados na avaliação:

• Expressões Regulares

• Ambiente Shell

• Comandos de manipulação de caracteres

• Capacidade de pesquisa e abordagem criativa na solução dos problemas apresentados.

Instrumentos de avaliação:

• Participação ativa nas atividades programadas.

• Avaliação ao final do curso.

• O participante fará várias avaliações referente ao conteúdo do curso. Para a aprovação eobtenção do certificado o participante deverá obter nota final maior ou igual a 6.0 de acordocom a fórmula abaixo:

• Nota Final = ((ML x 7) + (AF x 3)) / 10 = Média aritmética das lições

• AF = Avaliações

2.8 Bibliografia

• Site http://aurelio.net/shell/

• Site: http://www.ic.unicamp.br/ celio/mc514/bash/bash.html

15

Page 17: Shell Script

Capítulo 3

Introdução

Olá pessoal!

Sejam bem vindos ao curso de Bash Básico!

O curso tem duração de apenas duas semanas e consiste na leitura de alguns livros e liçõese, ao término delas, realizar algumas avaliações. Todas essas atividades valem pontuação. Paraum bom aproveitamento do curso é importante que haja entrosamento com os materiais, os fó-runs e os tutores. Seja participativo, questione e tire suas dúvidas. O material didático é do prof.Júlio Neves e de fato não há nada melhor aprender brincando. É interessante já estar instalado oLinux e deixar o terminal sempre aberto. Assim facilita acompanhar o raciocício da leitura. Qual-quer dúvida quanto ao comando pode ser resolvido digitando no Shell:

$ man nomedocomando

ou ainda discutindo concosco no fórum. Assim aprendemos juntos a funcionalidade de cadaum.

Bom trabalho!

16

Page 18: Shell Script

Capítulo 4

Expressões Regulares

4.1 Introdução

Uma Expressão Regular (ER) é um método formal de se especificar um padrão de texto.É uma composição de símbolos, caracteres com funções especiais, chamados "metacaracte-res"que, agrupados entre si e com caracteres literais, formam uma seqüência, uma expressão.Essa expressão é testada em textos e retorna sucesso caso este texto obedeça exatamente atodas as suas condições. Diz-se que o texto "casou"com a expressão.A ERs servem para se dizer algo abrangente de forma específica. Definido o padrão de busca,tem-se uma lista (finita ou não) de possibilidades de casamento. Em um exemplo rápido, [rgp]atopode casar "rato", "gato"e "pato".As ERs são úteis para buscar ou validar textos variáveis como:

• data

• horário

• número IP

• endereço de e-mail

• endereço de Internet

• declaração de uma função ()

• dados na coluna N de um texto

• dados que estão entre <tags></tags>

• número de telefone, RG, CPF, cartão de crédito

Vários editores de texto e linguagens de programação têm suporte a ERs, então o tempo inves-tido em seu aprendizado é recompensado pela larga variedade de aplicativos onde ele pode serpraticado.

4.2 Comando grep

Para não precisar listar todo o conteúdo de um arquivo por completo para apenas saber os da-dos do usuário "root", pode-se usar o grep para pesquisar e retornar somente a linha dele. O

17

Page 19: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

comando grep tem o seguinte formato:

grep palavra arquivo

Vamos utilizar como exemplo o arquivo /etc/passwd, que é a base de usuários de um sistemaUNIX/Linux. Vale a pena, antes, verificar como que se constitui esse arquivo dando o comando:

$cat /etc/passwd

Observa-se que serão obtidas várias linhas, onde cada um se refere a um usuário diferente.E cada linha possui o seguinte formato:

login : senha : UID : GID : Nome completo : Diretório $HOME : shellz

Voltando ao grep.

Para "pescar"somente a linha do usuário root faremos:

$ grep root /etc/passwdroot:x:0:0:root:/root:/bin/bash

4.3 Os Metacaractres

Cada metacaracteres é uma ferramenta que tem uma função específica. Servem para dar maispoder às pesquisas, informando padrões e posições imposssíveis de se especificar usando so-mente caracteres normais.

Os metacaracteres são pequenos pedacinhos simples, que agrupados entre si, ou com carac-teres normais, formam algo maior, uma expressão. O importante é compreender bem cada umindividualmente, e depois apenas lê-los em seqüência.

Metacaracteres Representantes

São aqueles cuja função é representar um ou mais caracteres.Ponto .Lista [...]Lista Negada [^...]

O ponto

O ponto é nosso curinga solitário, que está sempre à procura de um casamento, não importacom quem seja. Pode ser um número, uma letra, um TAB, um , o que vier ele traça, pois o pontocasa qualquer coisa.

Exemplos:"n.o"casaria: não, nao, ...

18

Page 20: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

".eclado"casaria: teclado, Teclado, ..."12.45"casaria: 12:45, 12 45, 12.45, ...

A listaBem mais exigente que o ponto, a lista não casa com qualquer um. Ela sabe exatamente o quequer, e nada diferente daquilo, a lista casa com quem ela conhece. Toda "lista"(os colchetes eseu conteúdo) vale para apenas uma posição, um caractere, por maior que seja.

Exemplos:n[ãa]o não, nao, ...[Tt]eclado teclado, Teclado, ....12[:. ]45 12:45, 12 45, 12.45, ...

A lista é de certa forma exigente. Sendo assim:Evite Prefira:

[0123456789] [0-9][0-9][0-9]:[0-9][0-9] [012][0-9]:[0-5][0-9][A-z] [A-Za-z]

Lista negadaA lista negada é exatamente igual à lista, podendo ter caracteres literais, intervalos e classesPOSIX. Tudo o que se aplica a lista normal, se aplica à negada também.A única diferença é que ela possui lógica inversa, ou seja, ela casará com qualquer coisa, fora oscomponentes listados.A única diferença é que ela possui lógica inversa, ou seja, ela casará com qualquer coisa, fora oscomponentes listados.Observe que a diferença em sua notação é que o primeiro caractere da lista é um circunflexo, eleindica que esta é uma lista negada. Então se [0-9] são números, [^\0-9] é qualquer coisa foranúmeros. Pode ser letras, símbolos, espaço em branco, qualquer coisa, menos números. Porém,ao iniciar o circunflexo (^) fora das chaves possui outro significado diferente: simboliza o iníciode uma linha. Mas tem de ser alguma coisa. Só porque ela é uma lista negada isso não significaque ela pode casar "nada".Exemplos:

[A-Z^] casa maiúsculas e o circunflexo e [^A-Z^]casa tudo fora isso.Como mandam as regras da boa escrita, sempre após caracteres de pontuação como a vírgulaou o ponto, devemos ter um espaço em branco os separando do resto do texto.Então vamos procurar por qualquer coisa que não o espaço após a pontuação:

[:;,.!?][^ ]

4.4 Metacaractres quantificadores

Os quantificadores servem para indicar o número de repetições permitidas para a entidade imedi-atamente anterior. Essa entidade pode ser um caractere ou metacaractere. Em outras palavras,eles dizem a quantidade de repetições que o átomo anterior pode ter, quantas vezes ele podeaparecer.São eles:

opcional ?

19

Page 21: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

asterisco *mais +chaves

Opcional

É útil para procurar palavras no singular e plural e pode ser tornar opcionais caracteres e me-tacaracteres.Exemplos:

Expressão Casa comOndas? Onda OndasSenadora? Senador Senadora[BFM]?ala ala Bala Fala Mala

Asterisco

Pode aparecer em qualquer quantidade. O curinga .* é o tudo e o nada, qualquer coisa. Exem-plos:

6*0 0, 60, 660, 6660, ..., 666666666660, ...bi*p bp, bip, biip, biiip, biiiip...b[ip]* b, bi, bip, biipp, bpipipi, biiiiip ...

Mais

Tem funcionamento idêntico ao do asterisco, tudo o que vale para um, se aplica ao outro. Aúnica diferença é que o mais (+) não é opcional, então a entidade anterior deve casar pelo menosuma vez, e pode ter várias. Sua utilidade é quando queremos no mínimo uma repetição. Exem-plos:

6+0 60, 660, 6660, ..., 666666660, ...bi+p bip, biip, biiip, biiiip...b[ip]+ bi, bip, biipp, bpipipi, biiiiip, bppp, ...

Chaves

As chaves são a solução para uma quantificação mais controlada, onde se pode especificar exa-tamente quantas repetições se quer da entidade anterior. Colocando um número entre chaves " ",indica-se uma quantidade de repetições do caractere (ou metacaractere) anterior. As chaves sãoprecisas podendo especificar um número exato, um mínimo, um máximo, ou uma faixa numérica.Elas, inclusive, simulam o *, + e ? .Exemplos:n,m significa de n até m vezes, assim algo como 61,4 casa 6, 66, 666 e 6666. Só, nada mais queisso.

0,1 zero ou 1 (igual ao opcional)0, zero ou mais (igual ao asterisco)1, um ou mais (igual ao mais)3 exatamente

20

Page 22: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

4.5 Metacaractres tipo âncora

São aqueles que não casam caracteres ou definem quantidades, ao invés disso eles marcamuma posição específica na linha. Assim, eles não podem ser quantificados, então o mais, o aste-risco e as chaves não têm influência sobre âncoras.

São eles:

• circunflexo - ^

• cifrão - \$

• borda - /b

Explicando cada metacaractere

4.5.1 Circunflexo

Este metacaractere (do tipo de posicionamento por representar uma posição específica da linha)simboliza o início de uma linha. É também o marcador de lista negada mas apenas dentro dalista (e no começo), fora dela ele é a âncora que marca o início de uma linha, veja:

^[0-9]

significa que casa com uma linha começando com qualquer algarismo. O inverso disso seria:

^[^0-9]

4.5.2 Cifrão - o fim

Este é similar e complementar ao circunflexo, pois representa o fim de uma linha e só é válido nofinal de uma expressão regular.

Quando demos o comando:$ grep bash$ /etc/passwd

significa que procuramos pela palavra "bash"no final da linha, ou ainda, a palavra "bash"seguidade um fim de linha.

Esse cifrão é o mesmo caractere que é utilizado para identificar as variáveis do shell, como $PWDe $HOME. Para evitar possíveis problemas com a expansão de variáveis, é preciso "proteger"aexpressão regular passada ao grep. A proteção é feita colocando-se a ER entre ’aspas simples’fazendo:$ grep 'bash$' /etc/passwd

Veja outros exemplos:

21

Page 23: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

.

4.5.3 Borda - a limítrofe

A borda marca os limites de uma palavra, ou seja, onde ela começa e/ou termina. É muito útilpara casar palavras exatas, e não partes de palavras. Palavra aqui é um conceito que englobaapenas, ou seja, letras, números e o sublinhado:

[A-Za-z0-9_]

Veja os exemplos:Veja como se comportam as ERs nas palavras dia, diafragma, radial, melodia e bom-dia!:

• dia - - - dia, diafragma, radial, melodia, bom-dia!

• \bdia - - - dia, diafragma, bom-dia!

• dia\b - - - dia, melodia, bom-dia!

• \bdia\b — dia, bom-dia!

4.6 Outros metacaracteres

Vamos ver outros metacaracteres, que têm funções específicas e não relacionadas entre si, por-tanto não podem ser agrupados em outra classe fora a tradicional "outros". Mas atenção, issonão quer dizer que eles são inferiores, pelo contrário, o poder das ERs é multiplicado com seuuso e um mundo de possibilidades novas se abre a sua frente.

São eles:

• escape \

• ou |

• grupo ()

• retrovisor /n

4.7 Explicando-os melhor

4.7.1 Escape

Temos duas formas de casar um metacaractere dentro de uma ER:

• Usando Listas: Lua[*] casa com Lua*

22

Page 24: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

• "Escapando"o Caractere: Lua\* casa com Lua*

Isto é, a contrabarra (\) "escapa"qualquer metacaractere, tirando todos os seus poderes. O es-cape é tão poderoso que pode escapar a si próprio! O \ casa uma barra invertida \ literal. Então,agora que sabemos muito sobre ERs, que tal uma expressão para casar um número de RG?Lembre que ele tem o formato n.nnn.nnn-n, é fácil![0-9]\.[0-9]{3}\.[0-9]{3}-[0-9]

O \* = [*] = asterisco literal

Ironia -> O escape escapa o escape, escapando-se a si próprio simultaneamente.

4.7.2 Ou

Para procurar por uma coisa ou outra, deve-se usar o pipe "|" e delimitar as opções com osparênteses "( )". É muito comum em uma posição específica de nossa Expressão Regular (ER)termos mais de uma alternativa possível, por exemplo, ao casar um cumprimento amistoso, po-demos ter uma terminação diferente para cada parte do dia:

boa-tarde|boa-noite

O ’ou’ serve para esses casos em que precisamos dessas alternativas. Essa ER se lê: "ouboa-tarde, ou boa-noite", ou seja "ou isso ou aquilo". Lembre que a lista também é uma espéciede ou (|), mas apenas para uma letra, então:

[gpr]ato é o mesmo que gato|pato|rato

São similares, embora nesse caso em que apenas uma letra muda entre as alternativas, a lista éa melhor escolha. Em outro exemplo, o ou é útil também para casarmos um endereço de Internet,que pode ser uma página, ou um sítio FTP http://|ftp://

4.7.3 Grupo

Assim como artistas famosos e personalidades que conseguem arrastar multidões, o grupo temo dom de juntar vários tipos de sujeitos em um mesmo local. Dentro de um grupo podemoster um ou mais caracteres, metacarateres e inclusive outros grupos! Como em uma expressãomatemática, os parênteses definem um grupo, e seu conteúdo pode ser visto como um bloco naexpressão.

Todos os metacaracteres quantificadores que vimos anteriormente, podem ter seu poder ampli-ado pelo grupo, pois ele lhes dá mais abrangência. E o ’ou’, pelo contrário, tem sua abrangêncialimitada pelo grupo, e pode parecer estranho, mas é essa limitação que lhe dá mais poder.

Em um exemplo simples,

(ai)+ agrupa a palavra ai e esse grupo está quantificado pelo mais (+). Isso quer dizer quecasamos várias repetições da palavra, como ai, aiai, aiaiai, ... E assim podemos agrupar tudo o

23

Page 25: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

que quisermos, literais e metacaracteres, e quantificá-los:

(ha!)+ ha!, ha!ha!, ha!ha!ha!, ...(\.[0-9]){3} .0.6.2, .2.8.9, .6.6.6, ...(www)̇?zz\.com www.zz.com, zz.com

E em especial nosso amigo ou ganha limites ou seu poder cresce:

boa-(tarde|noite) boa-tarde, boa-noite#(|n\.|núm) 6 # 6, n. 6, núm 6(in|con)?certo incerto, concerto, certo

Podemos criar subgrupos também, então imagine que você esteja procurando o nome de umsupermercado em uma listagem e não sabe se este é um mercado, supermercado ou um hiper-mercado.

(super|hiper)mercado

Consegue casar as duas últimas possibilidades, mas note que nas alternativas super e hipertemos um trecho per comum aos dois, então podíamos "alternativizar"apenas as diferenças su ehi:

(su|hi)permercado

Precisamos também casar apenas o mercado sem os aumentativos, então temos de agrupá-los e torná-los opcionais:

((su|hi)per)?mercado

Ei! E se tivesse minimercado também?(mini|(su|hi)per)?mercado

4.7.4 Retrovisor

(quero)- \1

Mas esse \1 não é o tal do escape?

Pois é, lembra que o escape (\) servia para tirar os poderes do metacaractere seguinte. En-tão, a essa definição agora incluímos: a não ser que este próximo caractere seja um número de1 a 9, então estamos lidando com um retrovisor.

Notou o detalhe? Podemos ter no máximo 9 retrovisores por ER, então \10 é o retrovisor nú-mero 1 seguido de um zero.O verdadeiro poder do retrovisor é quando não sabemos exatamente qual texto o grupo casará.Vamos estender o quero do exemplo anterior para "qualquer palavra":

([A-Za-z]+)-\1

24

Page 26: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Viu o poder dessa ER? Ela casa palavras repetidas, separadas por um traço, como o próprioquero-quero, e mais: bate-bate, come-come, etc. Mas, e se tornássemos o traço opcional?

([A-Za-z]+)-?\1

Com uma modificação pequena, fazemos um minicorretor ortográfico para procurar por palavrasrepetidas como estas em um texto:

([A-Za-z]+) \1

Mas lembre-se que procuramos por palavras inteiras e não apenas trechos delas, então preci-samos usar as bordas para completar nossa ER:

\b([A-Za-z]+) \1\b

Como já dito, podemos usar no máximo nove retrovisores. Vamos ver uns exemplos com mais deum de nossos amigos novos:

• (lenta)(mente) é \2 \1 lentamente é mente lenta

• ((band)eira)nte \1 \2a bandeirante bandeira banda

• in(d)ol(or) é sem \1 \2 indolor é sem dor

• ((((a)b)c)d)-1 = \1, \2, \3, \4 abcd-1 = abcd,abc,ab,a

Repare que numeram-se retrovisores contando os grupos da esquerda para a direita.

Ao usar um (grupo) qualquer, você ganha um brinde, e muitas e muitas vezes nem sabe. Obrinde é o trecho de texto casado pela ER que está no grupo, que fica guardado em um cantinhoespecial, e pode ser usado em outras partes da mesma ER!

Então o retrovisor \1 é uma referência ao texto casado do primeiro grupo, nesse caso quero,ficando, no fim das contas, a expressão que queríamos. O retrovisor pode ser lembrado tambémcomo um link ou um ladrão, pois copia o texto do grupo.

Como o nome diz, é retrovisor porque ele "olha pra trás", para buscar um trecho já casado.Isso é muito útil para casar trechos repetidos em uma mesma linha. Veja bem, é o texto, e não aER.

Como exemplo, em um texto, procuramos quero-quero. Podemos procurar literalmente por quero-quero, mas assim não tem graça, vamos usar o grupo e o retrovisor para fazer isso.

25

Page 27: Shell Script

Capítulo 5

Parte I

5.1 Diálogo

Diálogo entre ouvido um Linuxer e um empurrador de mouse:

- Quem é o Bash?- O Bash é o filho mais novo da família Shell.- Pô cara! Estás a fim de me deixar maluco? Eu tinha uma dúvida e você me deixa com duas! -Não, maluco você já é há muito tempo. Desde que se decidiu a usar aquele sistema operacionalque você tem que dar dez boots por dia e não tem domínio nenhum sobre o que esta acontecendono seu computador. Mas deixa isso prá lá, vou te explicar o que é Shell e os componentes de suafamília e ao final da explanação você dirá: "Meu Deus do Shell! Porque eu não optei pelo Linuxantes?".

5.2 O ambiente Linux

Para você entender o que é e como funciona o Shell, primeiro vou te mostrar como funciona oambiente em camadas do Linux. Dê uma olhada no gráfico abaixo:

26

Page 28: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

.Neste gráfico dá para ver que a camada de hardware é a mais profunda e é formada peloscomponentes físicos do seu computador. Envolvendo esta, vem a camada do kernel que é ocerne do Linux, seu núcleo, é quem bota o hardware para funcionar, fazendo seu gerenciamentoe controle. Os programas e comandos que envolvem o kernel, dele se utilizam para realizar astarefas aplicativas para que foram desenvolvidos. Fechando tudo isso vem o Shell que leva estenome porque em inglês, Shell significa concha, carapaça, isto é, fica entre o usuário e o sistemaoperacional, de forma que tudo que interage com o sistema operacional, tem que passar pelo seucrivo.

5.3 O ambiente Shell

Bom já que para chegar ao núcleo do Linux, no seu kernel que é o que interessa a todo aplicativo,é necessária a filtragem do Shell, vamos entender como ele funciona de forma a tirar o máximoproveito das inúmeras facilidades que ele nos oferece.O Linux por definição é um sistema multiusuário - não podemos nunca esquecer disto - e parapermitir o acesso de determinados usuários e barrar a entrada de outros, existe um arquivochamado /etc/passwd que além fornecer dados para esta função de "leão-de-chácara"do Linux,também provê informações 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 "lo-gar"(ARGH!!!).Lembra que eu te falei de Shell, família, irmão? Pois é, vamos começar a entender isto: o Shell,que que se vale da imagem de uma concha envolvendo o sistema operacional propriamente dito,é o nome genérico para tratar os filhos desta idéia que, ao longo dos anos de existência do sis-tema operacional Unix foram aparecendo. Atualmente existem diversos sabores de Shell, dentreestes eu destaco o sh (Bourne Shell), o ksh (Korn Shell), bash (Bourne Again Shell) e o csh (CShell).

5.4 Uma rapidinha nos principais sabores de Shell

Bourne Shell (sh)

Desenvolvido por Stephen Bourne da Bell Labs (da AT&T onde também foi desenvolvido o Unix),este foi durante muitos anos o Shell default do sistema operacional Unix. É também chamado deStandard Shell por ter sido durante vários anos o único e até hoje é o mais utilizado até porqueele foi portado para todos os ambientes Unix e distros Linux.

Korn Shell (ksh)

Desenvolvido por David Korn, também da Bell Labs, é um superset do sh, isto é, possui todas asfacilidades do sh e a elas agregou muitas outras. A compatibilidade total com o sh vem trazendomuitos usuários e programadores de Shell para este ambiente.

Bourne Again Shell (bash)

Este é o Shell mais moderno e cujo número de adeptos mais cresce em todo o mundo, seja

27

Page 29: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

por ser o Shell default do Linux, seu sistema operacional hospedeiro, seja por sua grande diver-sidade de comandos, que incorpora inclusive diversos instruções características do C Shell. CShell (csh)

Desenvolvido por Bill Joy da Berkley University é o Shell mais utilizado em ambientes *BSD eXenix. A estruturação de seus comandos é bem similar à da linguagem C. Seu grande pecado foiignorar a compatibilidade com o sh, partindo por um caminho próprio.

Além destes Shells existem outros, mas irei falar contigo somente sobre os três primeiros, tratando-os genericamente por Shell e assinalando as especificidades de cada um que porventura hajam.

Atenção: Quando eu disse que o último campo do /etc/passwd informa ao sistema qual éo Shell que o usuário 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 execução do programa prog e ao terminar a sua execução ganhará imediatamenteum logout. Imagine o quanto se pode incrementar a segurança com este simples artifício.

5.5 Explicando o funcionamento do Shell

O Shell é o primeiro programa que você ganha ao se "logar"no Linux. É ele que vai resolver ummonte de coisas de forma a não onerar o kernel com tarefas repetitivas, aliviando-o para tratarassuntos mais nobres. Como cada usuário possui o seu próprio Shell interpondo-se entre ele eo Linux, é o Shell quem interpreta os comandos que são teclados e examina as suas sintaxes,passando-os esmiuçados para execução.

- Êpa! Esse negócio de interpretar comando não tem nada a haver com interpretador não, né?- Tem sim, na verdade o Shell é um interpretador (ou será intérprete) que traz consigo umapoderosa linguagem com comandos de alto nível, que permite construção de loops (laços), detomadas de decisão e de armazenamento de valores em variáveis, como vou te mostrar.Vou te explicar as principais tarefas que o Shell cumpre, na sua ordem de execução. Preste aten-ção nesta ordem porque ela é fundamental para o entendimento do resto do nosso bate papo.

Exame da Linha de ComandosNeste exame o Shell identifica os caracteres especiais (reservados) que têm significado para in-terpretação da linha, logo após verifica se a linha passada é um comando ou uma atribuição.

ComandoQuando uma linha é digitada no prompt do Linux, ela é dividida em pedaços separados por es-paço em branco: o primeiro pedaço é o nome do programa que terá sua existência pesquisada;identifica em seguida, nesta ordem, opções/parâmetros, redirecionamentos e variáveis.

Quando o programa identificado existe, o Shell verifica as permissões dos arquivos envolvidos(inclusive o próprio programa), dando um erro caso você não esteja credenciado a executar estatarefa.

AtribuiçãoSe o Shell encontra dois campos separados por um sinal de igual ()= sem espaços em branco

28

Page 30: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

entre eles, identifica esta seqüência como uma atribuição.

Exemplos:

$ ls linuxlinux

Neste exemplo o Shell identificou o ls como um programa e o linux como um parâmetro pas-sado para o programa ls.

$ valor=1000

Neste caso, por não haver espaços em branco (já dá para notar que o branco é um dos ca-racteres reservados) o Shell identificou uma atribuição e colocou 1000 na variável valor.

Jamais Faça:

$ valor = 1000bash: valor: not found

Neste caso, o Bash achou a palavra valor isolada por brancos e julgou que você estivesse man-dando executar um programa chamado valor, para o qual estaria passando dois parâmetros: = e1000.

Resolução de RedirecionamentosApós identificar os componentes da linha que você teclou, o Shell parte para a resolução deredirecionamentos. O Shell tem incorporado ao seu elenco de vantagens o que chamamos deredirecionamento, que pode ser de entrada (stdin), de saída (stdout) ou dos erros (stderr), con-forme vou te explicar a seguir.

Substituição de VariáveisNeste ponto, o Shell verifica se as eventuais variáveis (parâmetros começados por $), encontra-das no escopo do comando, estão definidas e as substitui por seus valores atuais.

Substituição de Meta Caracteres

Se algum metacaractere (*, ? ou []) foi encontrado na linha de comando, neste ponto eleserá substituído por seus possíveis valores. Supondo que o único arquivo no seu diretório cor-rente começado pela letra n seja um diretório chamado "nomegrandeprachuchu", se você fizer:

$ cd n*

Passa Linha de Comando para o kernelCompletadas as tarefas anteriores, o Shell monta a linha de comandos, já com todas as substitui-ções feitas, chama o kernel para executá-la em um novo Shell (Shell filho), ganhando um númerode processo (PID ou Process Identification) e permanece inativo, tirando uma soneca, durantea execução do programa. Uma vez encerrado este processo (juntamente com o Shell filho), re-cebe novamente o controle e, exibindo um prompt, mostra que está pronto para executar outroscomandos.

29

Page 31: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

5.6 Caracteres para remoção do significado

É isso mesmo, quando não desejamos que o Shell interprete um caractere especial, devemos"escondê-lo"dele. Isso pode ser feito de três formas distintas:

Apóstrofo ou plic (’)Quando o Shell vê uma cadeia de caracteres entre apóstrofos ('), ele tira os apóstrofos da ca-deia e não interpreta seu conteúdo.

$ 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 lis-tar. No segundo, os apóstrofos inibiram a interpretação do Shell e veio a resposta que não existeo arquivo linux*.

Contrabarra ou Barra Invertida (\)Idêntico aos apóstrofos exceto que a barra invertida inibe a interpretação somente do caractereque a segue.Suponha que você acidentalmente tenha criado um arquivo chamado * (asterisco) - que algunssabores 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 diretório cor-rente. A melhor forma de fazer o pretendido é:

$ rm \*

Desta forma, o Shell não interpretaria o asterisco, e em conseqüência não faria a sua expan-são.Faça a seguinte experiência científica:

$ cd /etc$ echo '*'$ echo \*$ echo *

Viu a diferença? Então não precisa explicar mais.

Aspas (")Exatamente igual ao apóstrofo exceto que, se a cadeia entre aspas contiver um cifrão ($), umacrase (`), ou uma barra invertida (\), estes caracteres serão interpretados pelo Shell.Não precisa se estressar, eu não te dei exemplos do uso das aspas por que você ainda não co-nhece o cifrão($) nem a crase (`). Daqui para frente veremos com muita constância o uso destes

30

Page 32: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

caracteres especiais, o mais importante é entender o significado de cada um.

5.7 Caracteres de redirecionamento

5.7.1 Caracteres de redirecionamento

A maioria dos comandos tem uma entrada, uma saída e pode gerar erros. Esta entrada é cha-mada Entrada Padrão ou stdin e seu default é o teclado do terminal. Analogamente, a saídado comando é chamada Saída Padrão ou stdout e seu default é a tela do terminal. Para a telatambém são enviadas por default as mensagens de erro oriundas do comando que neste caso éa chamada Saída de Erro Padrão ou stderr. Veremos agora como alterar este estado de coisas.

Vamos fazer um programa gago. Para isto faça:

$ cat

O cat é uma instrução que lista o conteúdo do arquivo especificado para a Saída Padrão (stdout).Caso a entrada não seja definida, ele espera os dados da stdin. Ora como eu não especifiquei aentrada, ele está esperando-a pelo teclado (Entrada Padrão) e como também não citei a saída,o que eu teclar irá para a tela (Saída Padrão) fazendo desta forma, como eu havia proposto umprograma gago. Experimente!

5.7.2 Redirecionamento da Saída Padrão

Para especificarmos a saída de um programa usamos o > (maior que) ou o » (maior, maior) se-guido do nome do arquivo para o qual se deseja mandar a saída. Vamos transformar o programagago em um editor de textos (que pretensão heim!).

$ cat > Arq

O cat continua sem ter a entrada especificada, portanto está aguardando que os dados sejamteclados, porém a sua saída está sendo desviada para o arquivo Arq. Assim sendo, tudo queesta sendo teclado esta indo para dentro de Arq, de forma que fizemos o editor de textos maiscurto e ruim do planeta.Se eu fizer novamente:

$ cat > Arq

Os dados contidos em Arq serão perdidos, já que antes do redirecionamento o ShellArq vazio.Para colocar mais informações no final do arquivo eu deveria ter feito: criará um $ cat �Arq

Atenção: Como já havia lhe dito, o Shell resolve a linha e depois manda o comando paraa execução. Assim, se você redirecionar a saída de um arquivo para ele próprio,primeiramente o Shell "esvazia"este arquivo e depois manda o comando para execução,

31

Page 33: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

desta forma você acabou de perder o conteúdo do seu querido arquivo.Com isso dá para notar que o � (maior maior) serve para inserir texto no final do arquivo.

5.7.3 Redirecionamento da Saída de erro Padrão

Assim como o default do Shell é receber os dados do teclado e mandar as saídas para a tela, oserros também serão enviados para a tela se você não especificar para onde deverão ser enviados.Para redirecionar os erros use 2> SaidaDeErro. Note que entre o número 2 e o sinal de maior (>)não existe espaço em branco. Atenção: Não confunda � com 2>. O primeiro anexa dadosao final de um arquivo, e o segundo redireciona a Saída de Erro Padrão (stderr) paraum arquivo que está sendo designado. Isso é importante!

Suponha que durante a execução de um script você pode, ou não (dependendo do rumo to-mado pela execução do programa), ter criado um arquivo chamado /tmp/seraqueexiste$$. Paranão ficar sujeira no seu disco, ao final do script você colocaria uma linha:

$ rm /tmp/seraqueexiste$ $ (dois cifrões juntos)

Caso o arquivo não existisse seria enviado para a tela uma mensagem de erro. Para que issonão aconteça deve-se fazer:

$ rm /tmp/seraqueexiste$ $ 2> /dev/null (dois cifrões juntos)

Sobre o exemplo que acabamos de ver tenho duas dicas a dar:

Dica 1:O $ $ (dois cifrões juntos) contém o PID, isto é, o número do seu processo. Como oLinux é multiusuário, é bom anexar sempre o $ $ (dois cifrões juntos) ao nome dos arquivosque serão usados por várias pessoas para não haver problema de propriedade, isto é,caso você batizasse o seu arquivo simplesmente como seraqueexiste, o primeiro que ousasse (criando-o então) seria o seu dono e todos os outros ganhariam um erro quandotentassem gravar algo nele.

Para que você teste a Saída de Erro Padrão direto no prompt do seu Shell, vou dar mais umexemplo. Faça:

$ ls naoexistebash: naoexiste no such file or directory$ ls naoexiste 2> arquivodeerros$

hspace*1cmtextbf$ cat arquivodeerrosbash: naoexiste no such file or directory

Neste exemplo, vimos que quando fizemos um ls em naoexiste, ganhamos uma mensagemde erro. Após, redirecionarmos a Saída de Erro Padrão para arquivodeerros e executarmos omesmo comando, recebemos somente o prompt na tela. Quando listamos o conteúdo do arquivopara o qual foi redirecionada a Saída de Erro Padrão, vimos que a mensagem de erro tinha sido

32

Page 34: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

armazenada nele. Faça este teste ai.

Dica 2:- Quem é esse tal de /dev/null?

- Em Unix existe um arquivo fantasma. Chama-se /dev/null. Tudo que é mandadopara este arquivo some. Assemelha-se a um Buraco Negro. No caso do exemplo, como nãome interessava guardar a possível mensagem de erro oriunda do comando rm, redirecionei-apara este arquivo.

É interessante notar que estes caracteres de redirecionamento são cumulativos, isto é, se noexemplo anterior fizéssemos:

$ ls naoexiste 2� arquivodeerros

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

5.7.4 Redirecionamento da Entrada Padrão

Para fazermos o redirecionamento da Entrada Padrão 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 nós caprichamos, né?então ao invés de sair redigindo o mail direto no prompt da tela de forma a tornar impossível acorreção de uma frase anterior onde, sem querer, escreveu um "nós vai", você edita um arquivocom o conteúdo da mensagem e após umas quinze verificações sem constatar nenhum erro, de-cide enviá-lo e para tal faz:

$ mail chefe < arquivocommailparaochefe

O teu chefe então receberá o conteúdo do arquivocommailparaochefe.

Um outro tipo de redirecionamento muito louco que o Shell te permite é o chamado here do-cument. Ele é representado por « (menor menor) e serve para indicar ao Shell que o escopo deum comando começa na linha seguinte e termina quando encontra uma linha cujo conteúdo sejaunicamente o label que segue o sinal «.

Veja o fragmento de script a seguir, com uma rotina de ftp:

$ ftp -ivn hostremoto � fimftpuser $Usuário $Senhabinaryget arquivoremotofimftp

Neste pedacinho de programa temos um monte de detalhes interessantes:

1. As opções que usei para o ftp (-ivn) servem para ele ir listando tudo que está acontecendo (-v de verbose), para não perguntar se você tem certeza de que deseja transmitir cada arquivo

33

Page 35: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

(-i de interactive), e finalmente a opção -n serve para dizer ao ftp para ele não solicitar ousuário e sua senha, pois esses serão informados pela instrução específica (user);

2. Quando eu usei o « fimftp, estava dizendo o seguinte para o intérprete: "Olhe aqui Shell,não se meta em nada a partir daqui até encontrar o label fimftp. Você não entenderia nada,já que são instruções específicas do comando ftp e você não entende nada de =ftp=".

Se fosse só isso seria simples, mas pelo próprio exemplo dá para ver que existem duas variáveis($Usuário e $Senha), que o Shell vai resolver antes do redirecionamento. Mas a grande vantagemdesse tipo de construção é que ela permite que comandos também sejam interpretados dentro doescopo do here document, o que também contraria o que acabei de dizer. Logo a seguir explicocomo esse negócio funciona. Agora ainda não dá, está faltando ferramenta.

1. O comando user é do repertório de instruções do ftp e serve para passar o usuário e asenha que haviam sido lidos em uma rotina anterior a esse fragmento de código e colocadosrespectivamente nas duas variáveis: $Usuário e $Senha.

2. . O binary é outra instrução do ftp, que serve para indicar que a transferência de arquivore-moto será feita em modo binário, isto é, o conteúdo do arquivo não será interpretado parasaber se está em ASCII, EBCDIC, ...

3. O get arquivoremoto diz ao ftp para pegar esse arquivo em hostremoto e trazê-lo para onosso host local. Se fosse para mandar o arquivo, usaríamos o comando put.

Atenção: Um erro muito freqüente no uso de labels (como o fimftp do exemplo anterior)é causado pela presença de espaços em branco antes ou após o mesmo. Fique muito atentoquanto 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 inteirasó para ele.

- Está bem, está bem! Eu sei que dei uma viajada e entrei pelos comandos do ftp, fugindoao nosso assunto que é Shell, mas como é sempre bom aprender e é raro as pessoas estaremdisponíveis para ensinar...

5.8 Redirecionamento de Comandos

Os redirecionamentos que falamos até aqui sempre se referiam a arquivos, isto é mandavampara arquivo, recebiam de arquivo, simulavam arquivo local, ... O que veremos a partir de agoraredireciona a saída de um comando para a entrada de outro. É utilíssimo e quebra os maioresgalhos. Seu nome é pipe (que em inglês significa tubo, já que ele encana a saída de um comandopara a entrada de outro) e sua representação é uma barra vertical (|).

$ ls | wc -l21

O comando ls passou a lista de arquivos para o comando wc, que quando está com a opção-l conta a quantidade de linha que recebeu. Desta forma, podemos afirmar categoricamente queno meu diretório existiam 21 arquivos.

34

Page 36: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

$ cat /etc/passwd |sort | lp

Esta linha de comandos manda a listagem do arquivo /etc/passwd para a entrada do comandosort. Este a classifica e manda-a para o lp que é o gerenciador do spool de impressão.

5.9 Caracteres de Ambiente

Quando quer priorizar uma expressão você coloca-a entre parênteses não é? Pois é, por causada aritmética é normal pensarmos deste jeito. Mas em Shell o que prioriza mesmo são as crases(`) e não os parênteses. Vou dar exemplos de uso das crases para você entender melhor.

Eu quero saber quantos usuários estão "logados"no computador que eu administro. Eu possofazer:

$ who | wc -l8

O comando who passa a lista de usuários conectados para o comando wc -l que conta quan-tas linhas recebeu e lista a resposta na tela. Pois bem, mas ao invés 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, então vamos ver como é que fica:

$ echo "Existem who | wc -l usuários conectados"Existem who | wc -l usuários conectados

Hi! Olha só, não funcionou! É mesmo, não funcionou e não foi por causa das aspas que eucoloquei, mas sim por que eu teria que ter executado o who | wc -l antes do echo. Para resolvereste problema, tenho que priorizar esta segunda parte do comando com o uso de crases, fazendoassim:

$ echo "Existem 'who | wc -l' usuários conectados"Existem 8 usuários 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' usuários conectadosExistem 8 usuários conectados

Como eu disse antes, as aspas protegem tudo que esta dentro dos seus limites, da interpre-tação do Shell. Como para o Shell basta um espaço em branco como separador, o monte deespaços será trocado por um único após a retirada das aspas.

Antes de falar sobre o uso dos parênteses deixa eu mandar uma rapidinha sobre o uso de ponto-e-vírgula (;). Quando estiver no Shell, você deve sempre dar um comando em cada linha. Paraagrupar comandos em uma mesma linha teremos que separá-los por ponto-e-vírgula. Então:

35

Page 37: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

$pwd ; cd /etc; pwd; cd -; pwd/home/meudir/etc//home/meudir

Neste exemplo, listei o nome do diretório corrente com o comando pwd, mudei para o diretório/etc, novamente listei o nome do diretório e finalmente voltei para o diretório onde estava anteri-ormente (cd -), listando seu nome. Repare que coloquei o ponto-e-vírgula (;) de todas as formaspossíveis para mostrar que não importa se existe espaços em branco antes ou após este carac-tere.

Finalmente vamos ver o caso dos parênteses. Veja só o caso a seguir, bem parecido com oexemplo anterior:

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

- Quequeiiisso minha gente? Eu estava no /home/meudir, mudei para o /etc, constatei queestava neste diretório com o pwd seguinte e quando o agrupamento de comandos terminou, eu vique continuava no /etc/meudir, como se eu nunca houvesse saído de lá!- Ih! Será que é tem coisa de mágico aí?- Tá me estranhando, rapaz? Não é nada disso! O interessante do uso de parênteses é queele invoca um novo Shell para executar os comandos que estão no seu interior. Desta forma,realmente fomos para o diretório /etc, porém quando todos os comandos dentro dos parêntesesforam executados, o novo Shell que estava no diretório /etcShell anterior cujo diretório correnteera /home/meudir. Faça 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"+%hh:mm"'>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 havíamos conversado sobre here do-cument. Os comandos entre crases (‘) serão priorizados e portanto o Shell os executará antesda instrução mail. Quando o suporte receber o e-mail, verá que os comandos date e ls foramexecutados imediatamente antes do comando mail, recebendo então uma fotografia do ambienteno momento em que a correspondência foi enviada.

36

Page 38: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

O prompt primário default do Shell, como vimos, é o cifrão ($), porém o Shell usa o conceitode prompt secundário, ou de continuação de comando, que é enviado para a tela quando há umaquebra de linha e a instrução não terminou. Esse prompt, é representado por um sinal de maior(>), que vemos precedendo a partir da 2ª linha do exemplo.

Para finalizar e bagunçar tudo, devo dizer que existe uma construção mais moderna que vemsendo utilizada como forma de priorização de execução de comandos, tal qual as crases (‘). Sãoas construções do tipo $(cmd), onde cmd é um (ou vários) comando que será(ão) executado(s)com prioridade em seu contexto.

Assim sendo, o uso de crases (‘) ou construções do tipo $(cmd) servem para o mesmo fim, po-rém para quem trabalha com sistemas operacionais de diversos fornecedores (multiplataforma),aconselho o uso das crases, já que o $(cmd) não foi portado para todos os sabores de Shell. Aquidentro 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) usuários conectadosExistem 8 usuários conectados

Veja só este caso:

$ Arqs=ls$ echo $Arqsls

Neste exemplo eu fiz uma atribuição (=) e executei uma instrução. O que eu queria era que avariável $Arqs, recebesse a saída do comando ls. Como as instruções de um script são interpre-tadas de cima para baixo e da esquerda para a direita, a atribuição foi feita antes da execuçãodo ls. Para fazer o que desejamos é necessário que eu priorize a execução deste comando emdetrimento da atribuição 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 colocardentro da variável $Arqs a listagem longa (ls -l) de todos os arquivos começados por arq e segui-dos de um único caractere (?). Eu deveria fazer:

$ Arqs=$(ls -l arq?)ou:

$ Arqs='ls -l arq?'

Mas veja:

37

Page 39: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

$ echo $Arqs-rw-r�r�1 jneves jneves 19 May 24 19:41 arq1 -rw-r�r� 1 jneves jneves 23 May24 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 espaços em branco, sempreque houver diversos espaços juntos, eles serão trocados por apenas um. Para que a listagemsaia bonitinha, é necessário proteger a variável da interpretação 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, voulhe explicar uma série de instruções típicas de programação Shell. Tchau! Ahh! Só mais umacoisinha que eu ia esquecendo de lhe dizer. Em Shell, o "jogo da velha"(#) é usado quando de-sejamos fazer um comentário.

$ exit # pede a conta ao garcon

38

Page 40: Shell Script

Capítulo 6

Parte II

6.1 Diálogo

- Garçom! Traz um "chops"e dois "pastel". O meu amigo hoje não vai beber por que ele finalmenteesta sendo apresentado a um verdadeiro sistema operacional e ainda tem muita coisa a aprender!- E então, amigo, tá entendendo tudo que te expliquei até agora? - Entendendo eu tô, mas não vinada prático nisso... - Calma rapaz, o que te falei até agora, serve como base ao que há de virdaqui 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 começar vamos falar dos comandos da família grep. - grep? Não conheço nenhum termo eminglês com este nome... - É claro, grep é um acrônimo Global Regular Expression Print, que usaexpressões regulares para pesquisar a ocorrência 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 inglês g/_re_/p.). Porfalar em expressões regulares (ou regexp), o Aurélio Marinho Jargas tem todas as dicas em suapágina (inclusive tutorias) que abordam o tema. Se você está mesmo a fim de aprender a progra-mar em Shell, Perl, Python, ... Acho bom você ler estes artigos para te ajudar no que está para vir.

Pergunta: O grep é um aplicativo para linha de comando que faz buscas no conteúdo dos arqui-vos. Aceita expressões regulares e ao utilizá-las no grep é recomendado utilizar escapes "p̈aracaracteres como "*"para que o bash não o entenda como um curinga para nomes de arquivos. Adefinição dada está correta?

6.2 Eu fico com o grep, você com a gripe

Esse negócio 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 várias formas de definir a entrada docomando grep. Vejamos: Pesquisando em um arquivo:

$ grep rafael /etc/passwd

Pesquisando em vários arquivos:$ grep grep *.sh

Pesquisando na saida de comando:

39

Page 41: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

$ 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 início dos registros deste arquivo,eu deveria fazer:

$ grep '^rafael' /etc/passwd

E para que serve este circunflexo e os apóstrofos, você vai me perguntar. O circunflexo (^),se você tivesse lido os artigos anteriores sobre expressões regulares que te falei, saberia queservem para limitar a pesquisa ao início de cada linha, e os apóstrofos (’) servem para o Shellnão interpretar este circunflexo, deixando-o passar incólume para o comando grep.

Olha que legal! O grep aceita como entrada, a saída de outro comando redirecionado por umpipe (isto é muito comum em Shell e é um tremendo acelerador de execução de comando já queatua como se a saída de um programa fosse guardada em disco e o segundo programa lesseeste arquivo gerado), desta forma, no 3º exemplo, o comando who listou as pessoas "logadas"namesma máquina que você (não se esqueça jamais: o Linux é multiusuário) e o grep foi usadopara verificar se o Pelegrino estava trabalhando ou "coçando".

6.3 A família grep

Este comando grep é muito conhecido, pois é usado com muita freqüência, o que muitas pessoasdesconhecem é que existem três comandos na família grep, que são:

• grep

• egrep

• fgrep

A principais características diferenciais entre os 3 são:

• O grep pode ou não usar expressões regulares simples, porém no caso de não usá-las, ofgrep é melhor, por ser mais rápido;

• O egrep ("e"de extended, extendido) é muito poderoso no uso de expressões regulares. Porser o mais lento da família, só deve ser usado quando for necessária a elaboração de umaexpressão regular não aceita pelo grep;

• O fgrep ("f"de fast, rápido, ou de "file", arquivo) como o nome diz é o rapidinho da família,executa o serviço de forma muito veloz (por vezes é cerca de 30% mais veloz que o grep e50% mais que o egrep), porém não permite o uso de expressões regulares na pesquisa.

Atenção: Tudo que foi dito acima sobre velocidade, só se aplica à família de comandosgrep do Unix. No Linux o grep é sempre mais veloz, já que os outros dois (fgrep e egrep)são scripts em Shell que chamam o primeiro e, já vou adiantando, não gosto nem um pouquinhodesta solução.

40

Page 42: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Agora que você já conhece as diferenças entre os membros da família, me diga: o que vocêacha dos três exemplos que eu dei antes das explicações?- 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! Entãovamos ver mais exemplos para clarear de vez as diferenças de uso dos membros da família.

6.4 Exemplos da família grep

Exemplos

Eu sei que em um arquivo existe um texto falando sobre Linux só não tenho certeza se estáescrito com L maiúsculo ou l minúsculo. Posso fazer de duas formas:

$ egrep (Linux | linux) arquivo.txt

ou

$ grep [Ll]inux arquivo.txt

No primeiro caso, a expressão regular complexa "(Linux | linux)"usa os parênteses para agru-par as opções e a barra vertical (|) como um "ou"lógico, isto é, estou procurando Linux ou linux.

No segundo, a expressão regular [Ll]inux significa: começado por L ou l seguido de inux. Poresta expressão ser mais simples, o grep consegue resolvê-la, portanto acho melhor usar a se-gunda forma, já que o egrep tornaria a pesquisa mais lenta.

Outro exemplo. Para listar todos os subdiretórios do diretório corrente, basta:

$ ls -l | grep '^d'drwxr-xr-x 3 root root 4096 Dec 18 2000 docdrwxr-xr-x 11 root root 4096 Jul 13 18:58 freecivdrwxr-xr-x 3 root root 4096 Oct 17 2000 gimpdrwxr-xr-x 3 root root 4096 Aug 8 2000 gnomedrwxr-xr-x 2 root root 4096 Aug 8 2000 idldrwxrwxr-x 14 root root 4096 Jul 13 18:58 localedrwxrwxr-x 12 root root 4096 Jan 14 2000 lyxdrwxrwxr-x 3 root root 4096 Jan 17 2000 pixmapsdrwxr-xr-x 3 root root 4096 Jul 2 20:30 scribusdrwxrwxr-x 3 root root 4096 Jan 17 2000 soundsdrwxr-xr-x 3 root root 4096 Dec 18 2000 xine

No exemplo que acabamos de ver, o circunflexo (^) serviu para limitar a pesquisa à primeiraposição da saída do ls longo. Os apóstrofos foram colocados para o Shell não "ver"o circunflexo(^).

Vamos ver mais um. Sabemos que as quatro primeiras posições possíveis de um ls -l de um

41

Page 43: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

arquivo comum (arquivo comum! Não é diretório, nem link, nem...) devem ser:

.Assim sendo, para descobrir todos os arquivos executáveis em um determinado diretório eu de-veria 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 início de cada linha, entãoas linhas listadas serão as que começam por um traço (-), seguido de qualquer coisa (o pontoquando usado como uma expressão regular significa qualquer coisa), novamente seguido dequalquer coisa, vindo a seguir um x ou um s.

Obteríamos o mesmo resultado se fizéssemos:

$ ls -la | grep '^-..[xs]'

e agilizaríamos a pesquisa.

6.5 Vamos montar uma cdteca

Vamos começar a desenvolver programas, acho que a montagem de um banco de dados demúsicas é bacana para efeito didático (e útil nesses tempos de downloads de mp3 e "queima-dores"de CDs). Não se esqueça que, da mesma forma que vamos desenvolver um monte deprogramas para organizar os seus CDs de música, com pequenas adaptações, você pode fazero mesmo com os CDs de software que vêm com a Linux Magazine e outros que você compra ouqueima, disponibilizando este banco de software, desta forma ganhando muitos pontos com seuadorado chefe. para todos que trabalham com você (o Linux é multiusuário, e como tal deve serexplorado).- Péra ai! De onde eu vou receber os dados dos CDs?- Inicialmente, vou lhe mostrar como o seu programa pode receber parâmetros de quem o estiverexecutando e em breve, ensinarei a ler os dados pela tela ou de um arquivo.

6.6 Passando parâmetros

O layout do arquivo musicas será o seguinte:

nome do álbum^intérprete1~nome da música1:..:intérprete~nome da música

42

Page 44: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

isto é, o nome do álbum será separado por um circunflexo (^) do resto do registro, que é formadopor diversos grupos compostos pelo intérprete de cada música do CD e a respectiva música in-terpretada. Estes grupos são separados entre si por dois-pontos (:) e internamente, o intérpreteserá separado por um til (~) do nome da música.

Eu quero escrever um programa que chamado musinc, que incluirá registros no meu arquivomusicas. Eu passarei o conteúdo de cada álbum como parâmetro na chamada do programa fa-zendo assim:

$ musinc "álbum^interprete~musica:interprete~musica:..."

Desta forma o programa musinc estará recebendo os dados de cada álbum como se fosse umavariável. A única diferença entre um parâmetro recebido e uma variável é que os primeiros re-cebem nomes numéricos (nome numérico fica muito esquisito, né? O que quis dizer é que seusnomes são formados por um e somente um algarismo), isto é $1, $2, $3, ..., $9. Vamos, antes detudo, fazer um teste:

Exemplos

$ cat teste#!/bin/bash

# Programa para testar passagem de parametrosecho "1o. parm -> $1"echo "2o. parm -> $2"echo "3o. parm -> $3"

Vamos executá-lo:

$ teste passando parametros para testarbash: teste: cannot execute

Ops! Esqueci-me de torná-lo executável. Vou fazê-lo de forma a permitir que todos possamexecutá-lo e em seguida vou testá-lo:

$ chmod 755 teste$ teste passando parametros para testar1o. parm -> passando2o. parm -> parametros3o. parm -> para

Repare que a palavra testar, que seria o quarto parâmetro, não foi listada. Isto deu-se justa-mente porque o programa teste só listava os três primeiros parâmetros. Vamos executá-lo deoutra forma:

$ teste "passando parametros"para testar1o. parm -> passando parametros

43

Page 45: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

2o. parm -> para3o. parm -> testar

As aspas não deixaram o Shell ver o espaço em branco entre as palavras e considerou-as umúnico parâmetro.

6.7 Macetes paramétricos

Já que estamos falando em passagem de parâmetros deixa eu te dar mais umas dicas:

.Exemplos

Vamos alterar o programa teste para usar as variáveis que acabamos de ver. Vamos fazê-loassim:

$ cat teste#!/bin/bash# Programa para testar passagem de parametros (2a. Versao)echo O programa $0 recebeu $# parametrosecho "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 interpretaçãodo Shell (se não usasse as contrabarras as aspas não apareceriam). Vamos executá-lo:

$ teste passando parametros para testarO programa teste recebeu 4 parametros1o. parm -> passando2o. parm -> parametros3o. parm -> para

Todos de uma só "tacada": passando parametros para testar

Conforme eu disse, os parâmetros recebem números de 1 a 9, mas isso não significa que nãoposso usar mais de 9 parâmetros significa somente que só posso endereçar 9. Vamos testar isso:

Exemplo:

$ cat teste

44

Page 46: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

#!/bin/bash# Programa para testar passagem de parametros (3a. Versao)echo O programa $0 recebeu $# parametrosecho "11o. parm -> $11"shiftecho "2o. parm -> $1"shift 2echo "4o. Parm -> $1"

Vamos executá-lo:

$ teste passando parametros para testarO programa teste recebeu 4 parametros que são:11o. parm -> passando12o. parm -> parametros4o. parm -> testar

Duas coisas muito interessantes neste script:

1. Para mostrar que os nomes dos parâmetros variam de $1 a $9 eu fiz um echo $11 e o queaconteceu? 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 numérico (porémseu default é 1 como no exemplo dado), despreza os n primeiros parâmetros, tornando oparâmetro de ordem n+1, o primeiro ou seja, o $1.

Bem, agora que você já sabe mais sobre passagem de parâmetros do que eu, vamos voltar ànossa "cdteca"para fazer o script de inclusão de CDs no meu banco chamado musicas. O pro-grama é 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 é fácil e funcional, limito-me a anexar ao fim do arquivo musicas o parâmetro recebido.Vamos cadastrar 3 álbuns para ver se funciona (para não ficar "enchendo lingüiça", vou suporque em cada CD só existem 2 músicas):

$ musinc "album 3^Artista5~Musica5:Artista6~Musica5"$ musinc "album 1^Artista1~Musica1:Artista2~Musica2"$ musinc "album 2^Artista3~Musica3:Artista4~Musica4"

Listando o conteúdo de musicas.

$ cat musicasalbum 3^Artista5~Musica5:Artista6~Musica5album 1^Artista1~Musica1:Artista2~Musica2

45

Page 47: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

album 2^Artista3~Musica3:Artista4~Musica4"

Não está funcional como achava que deveria ficar... podia ter ficado melhor. Os álbuns estãofora de ordem, dificultando a pesquisa. Vamos alterar nosso script e depois testá-lo novamente:

$ cat musinc#!/bin/bash# Cadastra CDs (versao 2)#echo $1 � musicassort 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 musicasalbum 1^Artista1~Musica1:Artista2~Musica2album 2^Artista3~Musica3:Artista4~Musica4"album 3^Artista5~Musica5:Artista6~Musica5album 4^Artista7~Musica7:Artista8~Musica8

Simplesmente inseri uma linha que classifica o arquivo musicas dando a saída nele mesmo (paraisso serve a opção -o), após cada álbum ser anexado.

Oba! Agora está legal e quase funcional. Mas atenção, não se desespere! Esta não é a versãofinal. O programa ficará muito melhor e mais amigável, em uma nova versão que desenvolvere-mos após aprendermos a adquirir os dados da tela e formatar a entrada.

Exemplos

Ficar listando com o comando cat não está com nada, vamos então fazer um programa cha-mado muslist para listar um álbum cujo nome será passado como parâmetro:

$ 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 al-bum 2 é necessário protegê-la da interpretação do Shell, para que ele não a interprete como doisparâmetros. Vamos fazer assim:

$ muslist "álbum 2"grep: can't open 2

46

Page 48: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

musicas: album 1^Artista1~Musica1:Artista2~Musica2musicas: album 2^Artista3~Musica3:Artista4~Musica4"musicas: album 3^Artista5~Musica5:Artista6~Musica5musicas: album 4^Artista7~Musica7:Artista8~Musica8

Que lambança! Onde está o erro? Eu tive o cuidado de colocar o parâmetro passado entreaspas, para o Shell não 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 parâmetro,quando o $1 foi passado pelo Shell para o comando grep, transformou-se em dois argumentos.Desta forma o conteúdo 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 não existir o arquivo 2 gerou o erro, e por encontrar a palavra album em todos os registros demusicas, listou a todos.

Atenção: Sempre que a cadeia de caracteres a ser passada para o comando grep possuirbrancos ou TAB, mesmo que dentro de variáveis, coloque-a sempre entre aspas para evitarque as palavras após o primeiro espaço em branco ou TAB sejam interpretadas como nomesde arquivos.

Por outro lado, é melhor ignorarmos maiúsculas e minúsculas na pesquisa. Resolveríamos osdois problemas se o programa tivesse a seguinte forma:

$ cat muslist#!/bin/bash# Consulta CDs (versao 2)#grep -i "$1"musicas$ muslist "album 2"album2^Artista3~Musica3:Artista4~Musica4

Neste caso, usamos a opção -i do grep, que como já vimos, serve para ignorar maiúsculas eminúsculas, e colocamos o $1 entre aspas, para que o grep continuasse a ver a cadeia de carac-teres resultante da expansão da linha pelo Shell como um único argumento de pesquisa.

Agora repare que o grep localiza a cadeia pesquisada em qualquer lugar do registro, então daforma que estamos fazendo, podemos pesquisar por álbum, por música, por intérprete ou até por

47

Page 49: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

um pedaço de qualquer um destes. Quando conhecermos os comandos condicionais, montare-mos uma nova versão 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 passaro nome do álbum. Esta forma não é nem um pouco amigável!- Tem razão, e por isso vou te mostrar uma outra forma de fazer o que você pediu:

$ cat muslist#!/bin/bash

hspace*1cmtextbf# Consulta CDs (versao 2)#grep -i "$*"musicas$ muslist album 2album2^Artista3~Musica3:Artista4~Musica4

Desta forma, o $*, que significa todos os parâmetros, será substituído pela cadeia album 2 (deacordo com o exemplo anterior, fazendo o que você queria.

Não se esqueça o problema do Shell não é se você pode ou não fazer uma determinada coisa.O problema é decidir qual é a melhor forma de fazê-la, já que para desempenhar qualquer tarefa,a quantidade de opções é enorme.

Ah! Em um dia de verão você foi à praia, esqueceu o CD no carro, aquele "solzinho"de 40graus empenou o seu CD e agora você precisa de uma ferramenta para removê-lo do banco dedados? Não tem problema, vamos desenvolver um script chamado musexc, para excluir estesCDs.

Antes de desenvolver o "bacalho", quero te apresentar a uma opção bastante útil da família decomandos grep. É a opção -v, que quando usada lista todos os registros da entrada, exceto o(s)localizado(s) pelo comando. Vejamos:

Exemplos

$ grep -v "album 2"musicasalbum1^Artista1~Musica1:Artista2~Musica2album3^Artista5~Musica5:Artista6~Musica6album4^Artista7~Musica7:Artista8~Musica8

Conforme eu expliquei antes, o grep do exemplo listou todos os registros de musicas exceto oreferente a album 2, porque atendia ao argumento do comando. Estamos então prontos paradesenvolver o script para remover aquele CD empenado da sua "CDteca". Ele tem a seguintecara:

$ cat musexc#!/bin/bash

48

Page 50: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

# Exclui CDs (versao 1)#grep -v "$1"musicas > /tmp/mus$ $ (dois cifrões juntos)mv -f /tmp/mus$ $ musicas (dois cifrões juntos)

Na primeira linha mandei para /tpm/mus$ $ (dois cifrões juntos) o arquivo musicas, sem os re-gistros que atendessem a consulta feita pelo comando grep. Em seguida, movi (que, no duro,equivale a renomear) /tmp/mus$ $ (dois cifrões juntos) por cima do antigo musicas.

Usei o arquivo /tmp/mus$ $ (dois cifrões juntos) como arquivo de trabalho, porque como já haviacitado no artigo anterior, os dois cifrões juntos contém o PID (Process Identification ou identifica-ção do processo) e desta forma cada um que editar o arquivo musicas o fará em um arquivo detrabalho diferente, desta forma evitando colisões no uso.

- Aê cara, estes programas que fizemos até aqui estão muito primários em virtude da falta deferramentas que ainda temos. Mas é bom, enquanto eu tomo mais um chope, você ir para casapraticar em cima dos exemplos dados porque, eu prometo, chegaremos a desenvolver um sis-tema bacana para controle dos seus CDs.- Quando nos encontrarmos da próxima vez, vou te ensinar como funcionam os comandos condi-cionais e aprimoraremos mais um pouco estes scripts. - Por hoje chega! Já falei demais e precisomolhar a palavra porque estou de goela seca!- Garçom! Mais um sem colarinho!

49

Page 51: Shell Script

Capítulo 7

Parte III

7.1 Trabalhando com cadeias

Pelo título acima não pense você que vou lhe ensinar a ser carcereiro! Estou me referindo acadeia de caracteres!

7.2 O comando cut

Primeiro quero te mostrar, de forma eminentemente prática uma instrução simples de usar e muitoútil: o comando cut. Esta instrução é usada para cortar um determinado pedaço de um arquivo etem duas formas distintas de uso.

7.2.1 O comando cut a opção -c

Com esta opção, o comando tem a seguinte sintaxe:

cut -c PosIni-PosFim [arquivo]

Onde:

PosIni = Posição inicialPosFim = Posição final

$ cat numeros1234567890098765432112345543219876556789

$ cut -c1-5 numeros12345

50

Page 52: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

098761234598765

$ cut -c-6 numeros123456098765123455987655

$ cut -c4- numeros4567890765432145543216556789

$ cut -c1,3,5,7,9 numeros13579086421354297568

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

Como dá para ver, no duro mesmo existem quatro sintaxes distintas: na primeira (-c 1-5), euespecifiquei uma faixa, na segunda (-c -6), especifiquei tudo até uma posição, na terceira (-c 4-)de uma determinada posição em diante e na quarta (-c 1,3,5,7,9), determinadas posições. Aúltima (-c -3,5,-8) virando os olhos foi só para mostrar que podemos misturar tudo.

7.2.2 O comando cut a opção -f

Mas não pense você que acabou por aí! Como você deve ter percebido esta forma de cut é útilpara arquivos com campos de tamanho fixo, mas atualmente o que mais existe são arquivos comcampos de tamanho variáveis, onde cada campo termina com um delimitador. Vamos dar umaolhada no arquivo musicas que começamos a preparar no nosso papo na última vez que viemosaqui no botequim.

$ cat musicasalbum1^Artista1~Musica1:Artista2~Musica2album2^Artista3~Musica3:Artista4~Musica4album3^Artista5~Musica5:Artista6~Musica6album4^Artista7~Musica7:Artista8~Musica8

Então, recapitulando, o seu layout é o seguinte:

51

Page 53: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

nome do album^interprete1 nome da musica1:...:interpreten nome da musican

isto é, o nome do álbum será separado por um circunflexo (^) do resto do registro, que é formadopor diversos grupos compostos pelo intérprete de cada música do CD e a respectiva música in-terpretada. Estes grupos são separados entre si por dois-pontos (:) e internamente, o nome dointérprete será separado por um til (~) do nome da música.

Então para pegarmos os dados referentes a todas as segundas músicas do arquivo musicas,devemos fazer:

$ cut -f2 -d: musicasArtista2~Musica2Artista3~Musica4Artista6~Musica6Artista8~Musica8

Ou seja, cortamos o segundo campo (-f de field em inglês) delimitado (-d) por dois-pontos (:).Mas, se quisermos somente os intérpretes, devemos fazer:

$ cut -f2 -d: musicas |cut -f1 -d~Artista2Artista4Artista6Artista8

Para entender isso, vamos pegar a primeira linha de musicas:

$ head -1 musicaalbum 1^Artista1~Musica1:Artista2~Musica2

Então 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 (:) é album1^Artista1~Musica1 e o segundo, que é o que nos interessa, é Artista2~Musica2.

Vamos então 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 se-gundo é Musica2.Se o raciocínio que fizemos para a primeira linha for aplicado no restante do arquivo, chegaremos

52

Page 54: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

à resposta anteriormente dada.

7.3 O comando tr

Outro comando muito interessante é o tr que serve para substituir, comprimir ou remover carac-teres. Sua sintaxe segue o seguinte padrão:

tr [opções] cadeia1 [cadeia2]

O comando tr copia o texto da entrada padrão (stdin), troca as ocorrência dos caracteres decadeia1 pelo seu correspondente na cadeia2 ou troca múltiplas ocorrências dos caracteres decadeia1 por somente um caracter, ou ainda caracteres da cadeia1.

As principais opções do comando são:

.

7.3.1 Trocando caracteres com tr

Primeiro vou te dar um exemplo bem bobo:$ echo bobo | tr o ababa

Isto é, troquei todas as ocorrências da letra "o"pela letra "a".

Suponha que em um determinado ponto do meu script eu peça ao operador para teclar sn (simou não), e guardo sua resposta na variável $Resp. Ora o conteúdo de $Resp pode estar comletra maiúscula ou minúscula, e desta forma eu teria que fazer diversos testes para saber se aresposta dada foi S, s, N ou n. Então o melhor é fazer: ou

$ Resp=$(echo $Resp | tr SN sn)

e após este comando eu teria certeza que o conteúdo de $Resp seria um s ou um n.

Se o meu arquivo ArqEnt está todo escrito com letras maiúsculas e desejo passá-las para mi-núsculas eu faço:

$ tr A-Z a-z < ArqEnt > /tmp/ArqSai$ mv -f /tmp/ArqSai ArqEnt

Note que neste caso usei a notação A-Z para não escrever ABCD...YZ. Outro tipo de notaçãoque pode ser usada são as escape sequences (prefiro escrever no bom e velho português, mas

53

Page 55: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

nesse caso como eu traduziria? Seqüências de escape? Meio sem sentido, né? Mas vá lá...)que também são reconhecidas por outros comandos e também na linguagem C, e cujo significadovocê verá a seguir:

.

7.3.2 Removendo caracteres com tr

Então deixa eu te contar um "causo": um aluno que estava danado comigo, resolveu complicar aminha vida e em um exercício prático valendo nota que passei para ser feito no computador, meentregou o script com todos os comandos separados por ponto-e-vírgula (lembra que eu disseque o ponto-e-vírgula servia para separar diversos comandos em uma mesma linha?).

Vou dar um exemplo simplificado e idiota de uma "tripa"assim:

$ cat confusoecho leia Programação Shell Linux do Julio Cezar Neves > livro;cat livro;pwd;ls;rm-f livro 2>/dev/null;cd ~

Eu executava o programa e ele funcionava:

$ confusoleia Programação Shell Linux do Julio Cezar Neves/home/jneves/LMconfuso livro musexc musicas musinc muslist numeros

Mas nota de prova é coisa séria (e nota de dólar é mais ainda sorriso) então, para entender oque o aluno havia feito, o chamei e em sua frente executei o seguinte comando:

$ tr ";" "\n" < confusoecho leia Programação Shell Linux do Julio Cezar Nevespwdcd ~ls -lrm -f lixo 2>/dev/null

O cara ficou muito desapontado, porque em 2 ou 3 segundos eu desfiz a gozação que ele perderahoras para fazer.

Mas preste atenção! Se eu estivesse em uma máquina com Unix, eu teria feito:

54

Page 56: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

$ tr ";" "\012� confuso

7.3.3 Xpremendo com tr

Agora veja a diferença entre os dois comandos date: o que fiz hoje e outro que foi executado háduas semanas:

$ date # HojeSun Sep 19 14:59:54 2004$ date # Há duas semanasSun 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 semanasSun Sep 5 10:12:33 2004

Como você pode notar, existem 2 caracteres em branco antes do 5 (dia), o que estraga tudoporque o terceiro pedaço está vazio e o quarto é o dia (5). Então o ideal seria comprimir os espa-ços em brancos sucessivos em somente um espaço para poder tratar as duas cadeias resultantesdo comando date da mesma forma, e isso se faz assim:

$ date | tr -s " "Sun Sep 5 10:12:33 2004

E 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 umamáquina com aquele sistema operacional que pega vírus:

$ cat -ve ArqDoDOS.txtEste arquivo^M$foi gerado pelo^M$DOS/Rwin e foi^M$baixado por um^M$

55

Page 57: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

ftp mal feito.^M$

Dicas:

• Dica 1 - A opção -v do cat mostra os caracteres de controle invisíveis, com a notação ^L,onde ^ é a tecla control e L é a respectiva letra. A opção -e$). mostra o final da linha comoum cifrão

• Dica 2 - Isto ocorre porque no formato DOS (ou rwin), o fim dos registros é formado por umCarriage-Return (\r) e um line-feed (\f). No Linux porém o final do registro tem somente oline-feed.

Vamos então limpar este arquivo.

$ tr -d '\r' < ArqDoDOS.txt > /tmp/ArqDoLinux.txt$ mv -f /tmp/ArqDoLinux.txt ArqDoDOS.txt

Agora vamos ver o que aconteceu:

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

Bem a opção -d do tr remove o caractere especificado de todo o arquivo. Desta forma eu removios caracteres indesejados salvando em um arquivo de trabalho e posteriormente renomeei-o paraa sua designação original.Obs: No Unix eu deveria fazer:

$ tr -d '\015' < ArqDoDOS.txt > /tmp/ArqDoLinux.txt

7.3.4 O Comando if

O que o nosso comando condicional if faz é testar a variável $?. Então vamos ver a sua sintaxe:

if cmdthen

cmd1cmdnelsecmd3cmd4cmdm

fi

56

Page 58: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

ou seja: caso comando cmd tenha sido executado com sucesso, os comandos do bloco do then(cmd1, cmd2 e cmdn) serão executados, caso contrário, os comandos executados serão os dobloco opcional do else (cmd3, cmd4 e cmdm), terminando com um fi.

Vamos ver na prática como isso funciona usando um scriptizinho que serve para incluir usuá-rios no /etc/passwd:

$ cat incusu#!/bin/bash# Versão 1if grep ^$1 /etc/passwdthenecho Usuario '$1' já existeelseif useradd $1thenecho Usuário '$1' incluído em /etc/passwdelseecho "Problemas no cadastramento. Você é root?"fifi

Repare que o if está testando direto o comando grep e esta é a sua finalidade. Caso o if$1seja bem sucedido, ou seja, o usuário (cujo nome está em foi encontrado em /etc/passwd, oscomandos do bloco do then serão executados (neste exemplo é somente o echo) e caso contrário,as instruções do bloco do else é que serão executadas, quando um novo if testa se o comandouseradd foi executado a contento, criando o registro do usuário em /etc/passwd, ou não quandodará a mensagem de erro.

Vejamos sua execução, primeiramente passando um usuário já cadastrado:

$ incusu jnevesjneves:x:54002:1001:Julio Neves:/home/jneves:/bin/bashUsuario 'jneves' ja existe

Como já vimos diversas vezes, mas é sempre bom insistir no tema para que você já fique preca-vido, no exemplo dado surgiu uma linha indesejada, ela é a saída do comando grep. Para evitarque isso aconteça, devemos desviar a saída desta instrução para /dev/null, ficando assim:

$ cat incusu#!/bin/bash# Versão 2if grep ^$1 /etc/passwd > /dev/nullthenecho Usuario '$1' já existeelseif useradd $1then

57

Page 59: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

echo Usuário '$1' incluído em /etc/passwdelseecho "Problemas no cadastramento. Você é root?"fifi

Agora vamos testá-lo como usuário normal (não root):

$ incusu ZeNinguem./incusu[6]: useradd: not foundProblemas no cadastramento. Você é root?

Epa, aquele erro não era para acontecer! Para evitar que isso aconteça devemos mandar tam-bém a saída de erro (strerr, lembra?) do useradd para /dev/null, ficando na versão final assim:

$ cat incusu#!/bin/bash# Versão 3if grep ^$1 /etc/passwd > /dev/null

thenecho Usuario '$1' já existeelseif useradd $1 2> /dev/nullthenecho Usuário '$1' incluído em /etc/passwdelseecho "Problemas no cadastramento. Você é root?"fifi

Depois destas alterações e de fazer um su - (me tornar root) vejamos o seu comportamento:

$ incusu botelhoUsuário 'botelho' incluido em /etc/passwd

E novamente:

$ incusu botelhoUsuário 'botelho' já existe

Lembra que eu falei que ao longo dos nossos papos e chopes os nossos programas iriam seaprimorando? Então vejamos agora como poderíamos melhorar o nosso programa para incluirmúsicas:

$ cat musinc#!/bin/bash# Cadastra CDs (versao 3)#

58

Page 60: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

if grep "^$1$"musicas > /dev/nullthenecho Este álbum já está cadastradoelseecho $1 � musicassort musicas -o musicas?fi

Como você viu, é uma pequena evolução da versão anterior, assim, antes de incluir um regis-tro (que pela versão anterior poderia ser duplicado), testamos se o registro começava (^) eterminava ($) igual ao parâmetro passado ($1). O uso do circunflexo (^) no início da cadeia ecifrão ($) no fim, são para testar se o parâmetro passado (o álbum e seus dados) são exatamenteiguais a algum registro anteriormente cadastrado e não somente igual a um pedaço de algum dosregistros.

Vamos executá-lo passando um álbum já cadastrado:

$ musinc "album 4^Artista7~Musica7:Artista8~Musica8"Este álbum já está cadastrado

E agora um não cadastrado:

$ musinc "album 5^Artista9~Musica9:Artista10~Musica10"$ cat musicasalbum1^Artista1~Musica1:Artista2~Musica2album2^Artista3~Musica3:Artista4~Musica4album3^Artista5~Musica5:Artista6~Musica6album4^Artista7~Musica7:Artista8~Musica8album5^Artista9~Musica9:Artista10~Musica10

- Como você viu, o programa melhorou um pouquinho, mas ainda não está pronto. À medidaque eu for te ensinando a programar em shell, nossa CDteca irá ficando cada vez melhor.- Entendi tudo que você me explicou, mas ainda não sei como fazer um if para testar condições,ou seja o uso normal do comando.- Cara, para isso existe o comando test, ele é que testa condições. O comando if testa o comandotest. Mas isso está meio confuso e como já falei muito, estou precisando de uns chopes para mo-lhar a palavra. Vamos parando por aqui e na próxima vez te explico direitinho o uso do test e dediversas outras sintaxes do if.- Falou! Acho bom mesmo porque eu também já tô ficando zonzo e assim tenho tempo parapraticar esse monte de coisas que você me falou hoje.- Para fixar o que você aprendeu, tente fazer um scriptizinho para informar se um determinadousuário, que será passado como parâmetro esta logado (arghh!) ou não.- Aê Chico, mais dois chopes por favor...

59

Page 61: Shell Script

Capítulo 8

Parte IV

8.1 Diálogo

- E aí cara, tentou fazer o exercício que te pedi para revigorar as idéias?- Claro, que sim! Em programação, se você não treinar, não aprende. Você me pediu para fazerum scriptizinho para informar se um determinado usuário, que será passado como parâmetroesta logado (arghh!) ou não. Eu fiz o seguinte:

$ cat logado#!/bin/bash# Pesquisa se uma pessoa está logada ou nãoif who | grep $1 then echo $1 está logadoelseecho $1 não se encontra no pedaçofi

- Calma rapaz! Já vi que você chegou cheio de tesão, primeiro vamos pedir os nossos cho-pes 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 execução do seu bacalho:

$ logado jnevesjneves pts/0 Oct 18 12:02 (10.2.4.144)jneves está logado

Realmente funcionou. Passei o meu login como parâmetro e ele disse que eu estava logado,porém ele mandou uma linha que eu não pedi. Esta linha é a saída do comando who, e paraevitar que isso aconteça é só mandá-la para o buraco negro que a esta altura você já sabe que éo /dev/null. Vejamos então como ficaria:

$ cat logado#!/bin/bash# Pesquisa se uma pessoa está logada ou não (versão 2)if who | grep $1 > /dev/nullthenecho $1 está logado

60

Page 62: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

elseecho $1 não se encontra no pedaçofi

Agora vamos aos testes:

$ logado jnevesjneves está logado$ logado chicochico não se encontra no pedaço

Atenção: Ah, agora sim! Lembre-se desta pegadinha, a maior parte dos comandos temuma saída padrão e uma saída de erros (o grep é uma das poucos exceções, já que nãodá mensagem de erro quando não acha uma cadeia) e é necessário estarmos atentos pararedirecioná-las para o buraco negro quando necessário.

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á estávamos de goela seca falandosobre o if, você me perguntou como se testa condições. Vejamos então o comando test.

8.2 O comando Test

Bem, todos estamos acostumados a usar o if testando condições, e estas são sempre, maior,menor, maior ou igual, menor ou igual, igual e diferente. Bem, em Shell para testar condições,usamos o comando test, só que ele é muito mais poderoso que o que estamos habituados. Pri-meiramente vou te mostrar as principais opções (existem muitas outras) para testarmos arquivosem disco:

.Veja agora as principais opções para teste de cadeias de caracteres:

61

Page 63: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

.E pensa que acabou? Engano seu! Agora é que vem o que você está mais acostumado, ou sejaas famosas comparações com numéricos. Veja a tabela:

.Além de tudo, some-se a estas opções as seguintes facilidades:

.Ufa! Como você viu tem coisa prá chuchu, e como eu te disse no início, o nosso if é muito maispoderoso que o dos outros. Vamos ver em uns exemplos como isso tudo funciona, primeiramentetestaremos a existência de um diretório:

Exemplos:

if test -d lmbthen

cd lmbelse

mkdir lmbcd lmb

fi

No exemplo, testei se existia um diretório lmb definido, caso negativo (else), ele seria criado.Já sei, você vai criticar a minha lógica dizendo que o script não está otimizado. Eu sei, masqueria que você o entendesse assim, para então poder usar o ponto-de-espantação (!) como umnegador do test. Veja só:

if test ! -d lmbthen

mkdir lmbficd lmb

62

Page 64: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Desta forma o diretório lmb seria criado somente se ele ainda não existisse, e esta negativadeve-se ao ponto-de-exclamação (!) precedendo a opção -d. Ao fim da execução deste frag-mento de script, o programa estaria com certeza dentro do diretório lmb.

Vamos ver dois exemplos para entender a diferença comparação entre números e entre cadeias.

cad1=1cad2=01if test $cad1 = $cad2then

echo As variáveis são iguais.else

echo As variáveis são diferentes.fi

Executando o fragmento de programa acima vem:

As variáveis são diferentes.

Vamos agora alterá-lo um pouco para que a comparação seja numérica:

cad1=1cad2=01if test $cad1 -eq $cad2then

echo As variáveis são iguais.else

echo As variáveis são diferentes.fi

E vamos executá-lo novamente:As variáveis são iguais.

8.3 Continuação do comando test

Como você viu nas duas execuções obtive resultados diferentes porque a cadeia 011, porém,a coisa muda quando as variáveis são testadas numericamente, já que o número 1 é igual aonúmero 01. é realmente diferente da cadeia

Exemplos:

Para mostrar o uso dos conectores -o (OU) e -a (E), veja um exemplo animal feito direto noprompt (me desculpem os zoólogos, mas eu não entendendo nada de reino, filo, classe, ordem,família, gênero e espécie, desta forma o que estou chamando de família ou de gênero tem grandechance de estar incorreto):

63

Page 65: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

$ Familia=felinae$ Genero=gato$ if test $Familia = canidea -a $Genero = lobo -o $Familia = felina -a $Genero= leão> then> echo Cuidado> else> echo Pode passar a mão> fiPode passar a mão

Neste exemplo caso o animal fosse da família canídea E (-a) do gênero lobo, OU (-o) da fa-milia felina E (-a) do gênero leão, seria dado um alerta, caso contrário a mensagem seria deincentivo.

Dicas: Os sinais de maior (>) no início das linhas internas ao if são os prompts de continuação(que estão definidos na variável $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 = onça -o $Genero= lobo> then> echo Cuidado> else> echo Poe passar a mão> fiCuidado

Obviamente a operação redundou em erro, isto foi porque a opção -a tem precedência sobrea -o, e desta forma o que primeiro foi avaliado foi a expressão:

$Familia = canideo -a $Genero = onça

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 são -o, e para que uma série de expressões conectadas entresi por diversos OU lógicos seja verdadeira, basta que uma delas seja, a expressão final resultoucomo VERDADEIRO e o then foi executado de forma errada. Para que isso volte a funcionarfaçamos o seguinte:

64

Page 66: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

$ if test \($Familia = felino -o $Familia = canideo\) -a \($Genero = onça -o $Genero= lobo\)> then> echo Cuidado> else> echo Pode passar a mão> fiPode passar a mão

Desta forma, com o uso dos parênteses agrupamos as expressões com o conector -o, priori-zando as suas execuções e resultando:

VERDADEIRO -a FALSO

Para que seja VERDADEIRO o resultado duas expressões ligadas pelo conector -a é neces-sário que ambas sejam verdadeiras, o que não é o caso do exemplo acima. Assim o resultadofinal foi FALSO sendo então o else corretamente executado.

Se quisermos escolher um CD que tenha faixas de 2 artistas diferentes, nos sentimos tenta-dos a usar um if com o conector -a, mas é sempre bom lembrarmos que o bash nos dá muitorecursos, e isso poderia ser feito de forma muito mais simples com um único comando grep, daseguinte maneira:

$ grep Artista1 musicas |grep Artista2

Da mesma forma para escolhermos CDs que tenham a participação do Artista1 e do Artista2,não é necessário montarmos um if com o conector -o. O egrep (ou grep -E, sendo este maisaconselhável) também resolve isso para nós. Veja como:

$ egrep (Artista1|Artista2) musicas

Ou (nesse caso específico) o próprio grep puro e simples poderia nos quebrar o galho:

$ grep Artista[12] musicas

No egrep acima, foi usada uma expressão regular, onde a barra vertical (|) trabalha como umOU lógico e os parênteses são usados para limitar a amplitude deste OU. Já no grep da linhaseguinte, 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 nós, essa construção de if test ... é muito esquisita, é pouco legível.- É você tem razão, eu também não gosto disso e acho que ninguém gosta. Acho que foi por isso,que o Shell incorporou outra sintaxe que substitui o comando test.

Exemplos

65

Page 67: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Para isso vamos pegar aquele exemplo para fazer uma troca de diretórios, que era assim:

if test ! -d lmbthen

mkdr lmbficd lmb

e utilizando a nova sintaxe, vamos fazê-lo assim:

if [ ! -d lmb ]then

mkdir lmbficd lmb

Ou seja, o comando test pode ser substituído por um par de colchetes ([ ]), separados por espa-ços em branco dos argumentos, o que aumentará enormemente a legibilidade, pois o comandoif irá ficar com a sintaxe semelhante à das outras linguagens e por isso este será o modo que ocomando test será usado daqui para a frente.

8.4 Querida, encolheram o comando condicional

Se você pensa que acabou, está muito enganado. Repare a tabela (tabela verdade) a seguir:

.Ou seja, quando o conector é E e a primeira condição é verdadeira, o resultado final pode serVERDADEIRO ou FALSO, dependendo da segunda condição, já no conector OU, caso a primeiracondição seja verdadeira, o resultado sempre será VERDADEIRO e se a primeira for falsa, o re-sultado dependerá da segunda condição.

Ora, os caras que desenvolveram o interpretador não são bobos e estão sempre tentando oti-mizar ao máximo os algoritmos. Portanto, no caso do conector E, a segunda condição não seráavaliada, caso a primeira seja falsa, já que o resultado será sempre FALSO. Já com o OU, asegunda 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 exem-plo de trocarmos de diretório, que em sua última versão estava assim:

if [ ! -d lmb ]

66

Page 68: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

thenmkdir lmb

ficd lmb

Isso também poderia ser escrito da seguinte maneira:

[ ! -d lmb ] && mkdir lmbcd dir

Ou ainda retirando a negação (!):

[ -d lmb ] || mkdir lmbcd dir

No primeiro caso, se o primeiro comando (o test que está representado pelos colchetes) for bemsucedido, isto é, não existir o diretório lmb, o mkdir será efetuado porque a primeira condição eraverdadeira e o conector era E.

No exemplo seguinte, testamos se o diretório lmb existia (no anterior testamos se ele não existia)e caso isso fosse verdade, o mkdir não seria executado porque o conector era OU. Outra forma:

cd lmb || mkdir lmb

Neste caso, se o cd fosse mal sucedido, seria criado o diretório lmb mas não seria feito o cdpara dentro dele. Para executarmos mais de um comando desta forma, é necessário fazermosum grupamento de comandos, e isso se consegue com o uso de chaves ( ). Veja como seria ocorreto:

cd lmb ||{mkdir lmbcd lmb}

Ainda não está legal, porque caso o diretório não exista, o cd dará a mensagem de erro cor-respondente. Então devemos fazer:

cd lmb 2> /dev/null ||{mkdir lmbcd lmb}

Como você viu o comando if nos permitiu fazer um cd seguro de diversas maneiras. É semprebom lembrarmos que o seguro a que me referi é no tocante ao fato de que ao final da execuçãovocê sempre estará dentro de lmb, desde que você tenha permissão entrar em lmb, permissãopara criar um diretório em ../lmb, haja espaço em disco, ...

67

Page 69: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

8.5 E tome de test

Ufa! Você pensa que acabou? Ledo engano! Ainda tem uma forma de test a mais. Essa é legalporque ela te permite usar padrões para comparação. Estes padrões atendem às normas deGeração de Nome de Arquivos (File Name Generation, que são ligeiramente parecidas com asExpressões Regulares, mas não podem ser confundidas com estas). A diferença de sintaxe destepara o test que acabamos de ver é que esse trabalha com dois pares de colchete da seguinteforma:

[[ expressão ]]

Onde expressão é uma das que constam na tabela a seguir:

.$ echo $H13$ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora inválidaHora inválida$H=12$ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora inválida$

Neste exemplo, testamos se o conteúdo da variável $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 não fosse.

Exemplos:

Para saber se uma variável tem o tamanho de um e somente um caractere, faça:

$ var=a$ [[ $var == ? ]] && echo var tem um caracterevar tem um caractere$ var=aa$ [[ $var == ? ]] && echo var tem um caractere$

Como você pode imaginar, este uso de padrões para comparação, aumenta muito o poderio docomando test. No início deste papo, antes do último chope, afirmamos que o comando if do inter-

68

Page 70: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

pretador Shell é mais poderoso que o seu similar em outras linguagens. Agora que conhecemostodo o seu espectro de funções, diga-me: você concorda ou não com esta assertiva?

8.6 Acaso casa com case

Vejamos um exemplo didático: dependendo do valor da variável $opc o script deverá executaruma uma das opções: inclusão, exclusão, alteração ou fim. Veja como ficaria este fragmento descript:

if [ $opc -eq 1 ]then

inclusaoelif [ $opc -eq 2 ]then

exclusaoelif [ $opc -eq 3 ]then

alteracaoelif [ $opc -eq 4 ]then

exitelse

echo Digite uma opção entre 1 e 4fi

Neste exemplo você viu o uso do elif com um else if, esta á a sintaxe válida e aceita, mas pode-ríamos fazer melhor, e isto seria com o comando case, que tem a sintaxe a seguir:

case $var inpadrao1) cmd1

cmd2cmdn ;;

padrao2) cmd1cmd2cmdn ;;

padraon) cmd1cmd2cmdn ;;

esac

Onde a variável $var é comparada aos padrões padrao1, ..., padraon e caso um deles atenda, obloco de comandos cmd1, ..., cmdn correspondente é executado até encontrar um duplo ponto-e-vírgula (;;), quando o fluxo do programa se desviará para instrução imediatamente após o esac.

Na formação dos padrões, são aceitos os seguintes caracteres:

69

Page 71: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

.Para mostrar como fica melhor, vamos repetir o exemplo anterior, só que desta vez usaremos ocase e não o if ... elif ... else ... fi.

case $opc in1) inclusao ;;2) exclusao ;;3) alteracao ;;4) exit ;;*) echo Digite uma opção entre 1 e 4

esac

Como você deve ter percebido, eu usei o asterisco como a última opção, isto é, se o asteriscoatende a qualquer coisa, então ele servirá para qualquer coisa que não esteja no intervalo de 1 a4. Outra coisa a ser notada é que o duplo ponto-e-vírgula não é necessário antes do esac.

Exemplos:

Vamos agora fazer um script mais radical. Ele te dará bom dia, boa tarde ou boa noite de-pendendo da hora que for executado, mas primeiramente veja estes comandos:

$ dateTue Nov 9 19:37:30 BRST 2004$ date +%H19

O comando date informa a data completa do sistema, mas ele tem diversas opções para seumascaramento. Neste comando, a formatação começa com um sinal de mais (+) e os caracteresde formatação vêm após 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 horaHora=$(date +%H)case $Hora in0? | 1[01]) echo Bom Dia;;1[2-7] ) echo Boa Tarde

70

Page 72: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

;;esacexit

Peguei pesado, né? Que nada vamos esmiuçar a resolução 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 não casou com nenhum dos padrões anteriores.

- Cara, até agora eu falei muito e bebi pouco. Agora eu vou te passar um exercício para vocêfazer em casa e me dar a resposta da próxima vez que nos encontrarmos aqui no botequim, tálegal?- Tá, mas antes informe ao pessoal que está acompanhando este curso conosco como eles po-dem te encontrar para fazer críticas, contar piada, convidar para o chope, curso ou palestra ouaté mesmo para falar mal dos políticos.- É fácil, meu e-mail é [email protected], mas pare de me embromar que eu não vou es-quecer de te passar o script para fazer. É o seguinte: quero que você faça um programa quereceberá como parâmetro o nome de um arquivo e que quando executado salvará este arquivocom o nome original seguido de um til (~) e colocará este arquivo dentro do vi (o melhor editorque se tem notícia) para ser editado. Isso é para ter sempre a última cópia boa deste arquivo casoo cara faça alterações indevidas. Obviamente, você fará as críticas necessárias, como verificarse foi passado um parâmetro, 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!

71

Page 73: Shell Script

Capítulo 9

Parte V

9.1 Comandos de Loop (ou laço)

Muitos problemas requerem mecanismos de repetiçâo nos quais sequências de intruçôes preci-sam ser repetidas por várias vezes usando conjuntos diferentes de dados. Mais comumente, umaseçâo de código que se repete é chamada de laço porque após a execuçâo da última instruçâo oprograma se bifurca e retorna à primeira instrução ou encerra a execução. As instruçôes de loopou laço que veremos sâo o for, o while e o until que veremos daqui em diante. Começaremospelo laço for.

9.2 O Comando for

Se você está acostumado a programar, certamente já conhece o comando for, mas o que vocênâo sabe é que o for, que é uma instruçâo instríseca do Shell (isto significa que o código fonte docomando faz parte do código fonte do Shell, ou seja em bom programês é um built-in), é muitomais poderoso que os seus correlatos das outras linguagens.

Vamos entender a sua sintaxe, primeiramente em português e depois como funciona no duro.

para var em val1 val2 ... valnfaça

cmd1cmd2cmdn

feito

Onde a variável var assume cada um dos valores da lista val1 val2 ... valn e para cada umdesses valores executa o bloco de comandos formado por cmd1, cmd2 e cmdn

9.2.1 Primeira sintaxe do comando for

for var in val1 val2 ... valndo

cmd1cmd2

72

Page 74: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

cmdndone

Vamos direto para os exemplos, para entender direito o funcionamento deste comando. Vamosescrever um script para listar todos os arquivos do nosso diretório 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 diretórioe o comando echo jogou-os para a tela separados por espaços em branco. Visto isso vamos vercomo resolver o problema a que nos propuzemos:

$ cat testefor1#!/bin/bash# 1o. Prog didático para entender o for

for Arq in *doecho -n $Arq: # A opcao -n eh para nao saltar linhadone

Então vamos executá-lo:

$ testefor1ArqDoDOS.txt1:confuso:incusu:logado:musexc:musicas:musinc:muslist:$

Como você viu o Shell transformou o asterísco (que odeia ser chamado de asterístico) em umalista de arquivos separados por espaços em branco. quando o for viu aquela lista, ele disse: "Opa,lista separadas por espaços é comigo mesmo!"

O bloco de comandos a ser executado era somente o echo, que com a opção -n listou a variável$Arq seguida de dois-pontos (:), sem saltar a linha. O cifrão ($) do final da linha da execuçãoé o prompt. que permaneceu na mesma linha também em função da opção -n. Outro exemplosimples (por enquanto):

$ cat testefor2#!/bin/bash# 2o. Prog didático para entender o for

for Palavra in Papo de Botequimdoecho $Palavradone

E executando vem:

73

Page 75: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

$ testefor2PapodeBotequim

Como você viu, este exemplo é tão bobo e simples como o anterior, mas serve para mostraro comportamento básico do for.

Veja só a força do for: ainda estamos na primeira sintaxe do comando e já estou mostrandonovas formas de usá-lo. Lá atrás eu havia falado que o for usava listas separadas por espaçosem branco, mas isso é uma meia verdade, era só para facilitar a compreensão.

No duro, as listas não são obrigatóriamente separadas por espaços mas antes de prosseguir,deixa eu te mostrar como se comporta uma variável do sistema chamada de $IFS. Repare seuconteúdo:

$ echo "$IFS"| od -h0000000 0920 0a0a0000004

Isto é, mandei a variável (protegida da interpretação do Shell pelas aspas) para um dump he-xadecimal (od -h) e resultou:

.Onde o último 0a foi proveniente do <ENTER> dado ao final do comando. Para melhorar a expli-cação, vamos ver isso de outra forma:

$ echo ":$IFS:"| cat -vet: ^I$:$

Preste atenção na dica a seguir para entender a construção deste comando cat:

Dica:No comando cat, a opção -e representa o <ENTER> como um cifrão ($) e a opção -t representao <TAB> como um ^I. Usei os dois-pontos (:) para mostrar o início e o fim do echo. Edesta forma, mais uma vez pudemos notar que os três caracteres estão presentes naquelavariável.

Agora veja você, IFS significa Inter Field Separator ou, traduzindo, separador entre campos. Umavez entendido isso, eu posso afirmar (porque vou provar) que o comando for não usa listas sepa-

74

Page 76: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

radas por espaços em branco, mas sim pelo conteúdo da variável $IFS, cujo valor padrão (default)são esses caracteres que acabamos de ver. Para comprovarmos isso, vamos mostrar um scriptque recebe o nome do artista como parâmetro e lista as músicas que ele executa, mas primeira-mente vamos ver como está o nosso arquivo musicas:

$ cat musicasalbum 1^Artista1~Musica1:Artista2~Musica2album 2^Artista3~Musica3:Artista4~Musica4album 3^Artista5~Musica5:Artista6~Musica6album 4^Artista7~Musica7:Artista1~Musica3album 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 ]thenecho Voce deveria ter passado um parametroexit 1fi

IFS=":"

for ArtMus in $(cut -f2 -d^ musicas)doecho "$ArtMus"| grep $1 && echo $ArtMus | cut -f2 -d~done

O script, como sempre, começa testando se os parâmetros foram passados corretamente, emseguida o IFS foi setado para <ENTER> e dois-pontos (:) (como demonstram as aspas em linhadiferentes), porque é ele que separa os blocos Artistan~Musicam. Desta forma, a variável $Art-Mus 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 parâmetro ($1) no bloco, o segundo cutlistará somente o nome da música. Vamos executá-lo:

$ listartista Artista1Artista1 Musica1Musica1Artista1 Musica3Musica3Artista10 Musica10Musica10

Êpa! Aconteceram duas coisas indesejáveis: os blocos também foram listados e a Musica10

75

Page 77: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

idem. Além do mais, o nosso arquivo de músicas está muito simples, na vida real, tanto a mú-sica quanto o artista têm mais de um nome. Suponha que o artista fosse uma dupla sertanejachamada Perereca & Peteleca (não gosto nem de dar a idéia com receio que isso se torne re-alidadesorriso. Nesta caso o $1 seria Perereca e o resto deste lindo nome seria ignorado napesquisa.

Para que isso não ocorresse, eu deveia passar o nome do artista entre aspas (") ou alterar $1por $@ (que significa todos os parâmetros passados), que é a melhor solução, mas neste casoeu teria que modificar a crítica dos parâmetros e o grep. A nova crítica não seria se eu passeium parâmetro, mas pelo menos um parâmetro e quanto ao grep, veja só o que resultaria após asubstituição do $* (que entraria no lugar do $1) pelos parâmetros:

echo "$ArtMus"| grep perereca & peteleca

O que resultaria em erro. O correto seria:

echo "$ArtMus"| grep -i "perereca & peteleca"

Onde foi colocado a opção -i para que a pesquisa ignorasse maiúsculas e minúsculas e as aspastambém foram inseridas para que o nome do artista fosse visto como uma só cadeia monolítica.

Ainda falta consetar o erro dele ter listado o Artista10. Para isso o melhor é dizer ao grep^)de $ArtMus e logo após vem um til (~). É necessário também que se redirecione a saída do greppara /dev/null para que os blocos não sejam mais listados. Veja então a nova (e definitiva) carado programa: que a cadeia está no início (cuja expressão regular é

$ cat listartista#!/bin/bash# Dado um artista, mostra as suas musicas# versao 2

if [ $# -eq 0 ]thenecho Voce deveria ter passado pelo menos um parametroexit 1fi

IFS=":"

for ArtMus in $(cut -f2 -d^ musicas)doecho "$ArtMus"| grep -i "^$@~" > /dev/null && echo $ArtMus | cut -f2 -ddone

Que executando vem:

$ listartista Artista1

76

Page 78: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Musica1Musica3

9.2.2 Segunda sintaxe do comando for

for vardo

cmd1cmd2cmdn

done

- Ué, sem o in como ele vai saber que valor assumir?- Pois é, né? Esta construção a primeira vista parece xquisita mas é bastante simples. Nestecaso, var assumirá um-a-um cada um dos parâmetros passados para o programa.Vamos logo aos exemplos para entender melhor. Vamos fazer um script que receba como parâ-metro um monte de músicas 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 naoChurrasquinho de Mae"#if [ $# -eq 0 ]thenecho Uso: $0 musica1 [musica2] ... [musican]exit 1fiIFS=":"for Musicadoecho $MusicaStr=$(grep -i "$Musica"musicas) ||{echo "Não encontrada"continue}for ArtMus in $(echo "$Str"| cut -f2 -d^)doecho "$ArtMus"| grep -i "$Musica"| cut -f1 -ddonedone

Da mesma forma que os outros, começamos o exercício com uma crítica sobre os parâmetros

77

Page 79: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

recebidos, em seguida fizemos um for em que a variável $Musica receberá cada um dos parâ-metros passados, colocando em $Str todos os álbuns que contém as músicas passadas. Emseguida, o outro for pega cada bloco Artista Musica nos registros que estão em $Str e lista cadaartista que execute aquela música.

Como sempre vamos executá-lo para ver se funciona mesmo:

$ listamusica musica3 Musica4 "Eguinha Pocotó"musica3Artista3Artista1Musica4Artista4Eguinha PocotóNão encontrada

A listagem ficou feinha porque ainda não sabemos formatar a saída, mas qualquer dia desses,quando você souber posicionar o cursor, fazer negrito, trabalhar com cores e etc, faremos estalistagem novamente usando todas estas perfumarias e ela ficará muito fashion.

A esta altura dos acontecimentos você deve estar se perguntando: "E aquele for tradicional dasoutras linguagens em que ele sai contando a partir de um número, com um determinado incre-mento até alcançar uma condição?"

E é aí que eu te respondo: "Eu não te disse que o nosso for é mais porreta que os outros?"Parafazer 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 "> done1 2 3 4 5 6 7 8 9

Neste a variável i assumiu os inteiros de 1 a 9 gerados pelo comando seq e a opção -nechofoi usada para não saltar linha a cada número listado (sinto-me ecologicamente correto por nãogastar um monte de papel da revista quando isso pode ser evitado). Ainda usando o for com seq:do

$ for i in $(seq 3 9)> do> echo -n "$i "> done4 5 6 7 8 9

Ou ainda na forma mais completa do seq:

$ for i in $(seq 0 3 9)> do

78

Page 80: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

> echo -n "$i "> done0 3 6 9

2 - A outra forma de fazer o desejado é com uma sintaxe muito semelhante ao for da lingua-gem C, como veremos mais adiante.

9.2.3 Terceira sintaxe do comando for

for ((var=ini; cond; incr))do

cmd1cmd2cmdn

done

Onde:

var=ini - Significa que a variável var começará de um valor inicial ini;cond - Siginifica que o loop ou laço do for será executado enquanto var não atingir a condiçãocond;incr - Significa o incremento que a variável var sofrerá em cada passada do loop.Como sempre vamos aos exemplos que a coisa fica mais fácil:

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

Neste caso a variável i partiu do valor inicial 1, o bloco de comando (neste caso somente oecho) será executado enquanto i menor ou igual (<)= a 9 e o incremento de i1 a cada passadado loop. será de 1.

Repare que no for propriamente dito (e não no bloco de comandos) não coloquei um cifrão ($)antes do i, e a notação para incrementar (i++) é diferente do que vimos até agora. Isto é porque ouso de parênteses duplos (assim como o comando let) chama o interpretador aritmético 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, porém omitindo a última parte do escopo do for, passando-a para obloco de comandos.

$ for ((; i<=9;))> do> let i++> echo -n "$i "> done

79

Page 81: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

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, reparetambém que quando usei o let, não foi necessário sequer inicializar a variável $i. Veja só oscomandos a seguir dados diretamente no prompt para mostrar o que acabo de falar:

$ echo $j

$ let j++$ echo $j1

Ou seja, a variável $j sequer existia e no primeiro let assumiu o valor 0 (zero) para, após oincremento, ter o valor 1.

Veja só como as coisas ficam simples:

$ for arq in *> do> let i++> echo "$i -> \$Arq"

> done1 -> ArqDoDOS.txt12 -> confuso3 -> incusu4 -> listamusica5 -> listartista6 -> logado7 -> musexc8 -> musicas9 -> musinc10 -> muslist11 -> testefor112 -> testefor2

- Pois é amigo, tenho certeza que você já tomou um xarope do comando for. Por hoje chega,na próxima vez que nos encontrarmos falaremos sobre outras instruções de loop, mas eu gosta-ria que até lá você fizesse um pequeno script para contar a quantidade de palavras de um arquivotexto, cujo nome seria recebido por parâmetro.

OBS: Essa contagem tem de ser feita usando o comando for para se habituar ao seu uso. Nãovale usar o wc -w.

80

Page 82: Shell Script

Capítulo 10

Parte VI

10.1 Um pouco mais de for e matemática

Voltando à vaca fria, na última vez que aqui estivemos, terminamos o nosso papo mostrando oloop 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 trabalhacom o conceito de "Expansão Aritmética"(Arithmetic Expansion) que é acionado por uma cons-trução da forma

$((expressão))oulet expressão

No último for citado usei a expansão das duas formas, mas não poderíamos seguir adiante semsaber que a expressão pode ser de uma das listadas a seguir:

81

Page 83: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

.- Mas você pensa que o papo de loop (ou laço) se encerra no comando for? Ledo engano amigo,vamos a partir de agora ver mais dois.

10.2 O Comando while

Todos os programadores conhecem este comando, porque ele é comum a todas as linguagens enelas, o que normalmente ocorre é que um bloco de comandos é executado, enquanto (enquantoem ingles é while) uma determinada condição for verdadeira. Pois bem, isto é o que ocorre naslinguagens caretas! Em programação Shell, o bloco de comandos é executado enquanto um co-mando for verdadeiro. E é claro, se quiser testar uma condição use o comando while junto com ocomando test, exatamente como você aprendeu a fazer no if, lembra?

Então a sintaxe do comando fica assim:

while comandodocmd1cmd2...cmdndone

e desta forma o bloco de comandos formado pelas instruções cmd1, cmd2,... e cmdncomandofor bem sucedida. é executado enquanto a execução da instrução.

Suponha a seguinte cena: tem uma tremenda gata me esperando e eu preso no trabalho sempoder sair porque o meu chefe, que é um pé no saco (aliás chefe-chato é uma redundância,né?sorriso, ainda estava na sua sala, que fica bem na minha passagem para a rua.

Ele começou a ficar com as antenas (provavelmente instaladas na cabeça dele pela esposa)ligadas depois da quinta vez que passei pela sua porta e olhei para ver se já havia ido embora.Então voltei para a minha mesa e fiz, no servidor, um script assim:

$ cat logaute.sh#!/bin/bash

# Espero que a Xuxa não tenha# copyright de xefe e xato

while who | grep xefedosleep 30doneecho O xato se mandou, não hesite, dê exit e vá a luta

Neste scriptizinho, o comando while testa o pipeline composto pelo who e pelo grepgrep loca-

82

Page 84: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

lizar a palavra xefe na saída do who. Desta forma, o script dormirá por 30 segundos enquanto ochefe estiver logado (Argh!). Assim que ele se desconectar do servidor, o fluxo do script sairá doloop e que será verdadeiro enquanto o e dará a tão ansiada mensagem de liberdade.

Quando o executei adivinha o que aconteceu?

$ logaute.shxefe 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 saída do grep, o que não seria legal jáque poluiria a tela do meu micro e a mensagem esperada poderia passar desapercebida. Paraevitar isso já sabemos que a saída do pipeline tem que ser redirecionada para /dev/null.

$ cat logaute.sh#!/bin/bash

# Espero que a Xuxa não tenha# copyright de xefe e xato sorrisowhile who | grep xefe > /dev/nulldosleep 30doneecho O xato se mandou, não hesite, dê exit e vá a luta

Agora quero montar um script que receba o nome (e eventuais parâmetros) de um programaque será executado em background e que me informe do seu término. Mas, para você entendereste exemplo, primeiro tenho de mostar uma nova variável do sistema. Veja estes comandosdiretos 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 mostrarque a variável $! guarda o PID (Process IDentification) do último processo em background, masrepare após a linha do done, que a variável reteve o valor mesmo após o término deste processo.

Bem sabendo isso já fica mais fácil de monitorar qualquer processo em background. Veja sócomo:

$ cat monbg.sh

83

Page 85: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

#!/bin/bash

# Executa e monitora um# processo em background$1 & # Coloca em backgroundwhile ps | grep -q $!do sleep 5doneecho Fim do Processo $1

Este script é bastante similar ao anterior, mas tem uns macetes a mais, veja só: ele tem queser executado em background para não prender o prompt mas o $!background após o monbg.shpropriamente dito. Repare também a opção -q (quiet) do grep, ela serve para tranformá-lo numcomando mineiro, isto é, para o grepwhile ps | grep $! > /dev/null, como nos exemplos quevimos até agora. será o do programa passado como parâmetro já que ele foi colocado em "traba-lhar em silêncio". O mesmo resultado poderia ser obtido se a linha fosse

Dica: Não esqueça: o Bash disponibiliza a variável $! que possui o PID (ProcessIDentification) 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 pe-quena dica do comando read (que é quem pega o dado da tela) que seja o suficiente para resolvereste nosso problema. Em uma outra rodada de chope vou te ensinar tudo sobre o assunto, inclu-sive 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 apareça escrito na tela, e quando o ope-rador teclar o dado, ele irá para a variável var. Por exemplo:

$ read -p "Título do Álbum: "Tit

Bem, uma vez entendido isso, vamos à especificação do nosso problema: faremos um programaque inicialmente lerá o nome do álbum e em seguida fara um loop de leitura, pegando a músicae o artista. Este loop termina quando for informada uma música vazia, isto é, ao ser solicitadaa digitação da música, o operador dá um simples <ENTER>. Para facilitar a vida do operador,vamos oferecer como default o mesmo nome do artista da música anterior (já que é normal queo álbum seja todo do mesmo artista) até que ele deseje alterá-lo. Vamos ver como ficou:

$ cat musinc#!/bin/bash# Cadastra CDs (versao 4)#clearread -p "Título do Álbum: "Tit

84

Page 86: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

[ "$Tit"] || exit 1 # Fim da execução se título vazioif grep "^$Tit\^" musicas > /dev/nullthenecho Este álbum já está cadastradoexit 1fiReg="$Tit^"Cont=1oArt=while truedoecho Dados da trilha $Cont:read -p "Música: "Mus[ "$Mus"] || break # Sai se vazioread -p "Artista: $oArt // "Art[ "$Art"] && oArt="$Art"# Se vazio Art anteriorReg="$Reg$oArt $Mus:"# Montando registroCont=$((Cont + 1))# A linha anterior tb poderia ser ((Cont++))doneecho "$Reg�> musicassort musicas -o musicas$ cat musinc#!/bin/bash# Cadastra CDs (versao 4)#clearread -p "Título do Álbum: "Tit[ "$Tit"] || exit 1 # Fim da execução se título vazioif grep "^$Tit\^" musicas > /dev/nullthenecho Este álbum já está cadastradoexit 1fiReg="$Tit^" Cont=1oArt=while truedoecho Dados da trilha $Cont:read -p "Música: "Mus[ "$Mus"] || break # Sai se vazioread -p "Artista: $oArt // "Art[ "$Art"] && oArt="$Art"# Se vazio Art anteriorReg="$Reg$oArt $Mus:"# Montando registroCont=$((Cont + 1))# A linha anterior tb poderia ser ((Cont++))doneecho "$Reg�> musicassort musicas -o musicas

85

Page 87: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Este exemplo, começa com a leitura do título do álbum, que se não for informado, terminará aexecução do programa. Em seguida um grep procura no início (^) de cada registro de musicas,o título informado seguido do separador (^) (que está precedido de uma contrabarra (\) paraprotegê-lo da interpretação do Shell).

Para ler os nomes dos artistas e as músicas do álbum, foi montado um loop de while simples,cujo único destaque é o fato de estar armazenando o artista da música anterior na variável $oArtque só terá o seu conteúdo alterado, quando algum dados for informado para a variável $Art, istoé, quando não 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, principal-mente para leitura de arquivos, porém nos falta bagagem para prosseguir. Depois que aprender-mos a ler, veremos esta instrução mais a fundo.

Dica: Leitura de arquivo significa ler um-a-um todos os registros, o que é sempre umaoperação lenta. Fique atento para não usar o while quando seu uso for desnecessário.O Shell tem ferramentas como o sed e a família grep que vasculham arquivos de formaotimizada sem ser necessário o uso de comandos de loop para fazê-lo registro a registro(ou até palavra a palavra).

10.3 O Comando Until

O comando until funciona exatamente igual ao while, porém ao contrário. Disse tudo mas nãodisse nada, né? É o seguinte: ambos testam comandos; ambos possuem a mesma sintaxe eambos atuam em loop, porém enquanto o while executa o bloco de intruções do loop enquantoum comando for bem sucedido, o until executa o bloco do loop até que o comando seja bemsucedido. Parece pouca coisa mas a diferença é fundamental.

A sintaxe do comando é praticamente a mesma do while. Veja:

until comandodo

cmd1cmd2...cmdn

done

E desta forma o bloco de comandos formado pelas instruções cmd1, cmd2,... e cmdncomandoseja bem sucedida. é executado até que a execução da instrução.

Como eu te disse, o while e until funcionam de forma antagônica e isso é muito fácil de de-monstrar: em uma guerra sempre que se inventa uma arma, o inimigo busca uma solução paraneutralizá-la. Baseado neste principio belicoso que o meu chefe, desenvolveu, no mesmo servi-dor que eu executava o logaute.sh um script para controlar o meu horário de chegada.

86

Page 88: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Um dia deu um problema da rede, ele me pediu para dar uma olhada no micro dele e me dei-xou 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 juliodosleep 30doneecho $(date "+ Em %d/%m às %H:%Mh") > relapso.log

Olha que safado! O cara estava montando um log com os horários que eu chegava, e aindapor cima chamou o arquivo que me monitorava de relapso.log! O que será que ele quis dizer comisso?

Neste script, o pipeline who | grep julio, será bem sucedido somente quando juliowho, isto é,quando eu me "logar"no servidor. Até que isso aconteça, o comando sleep, que forma o bloco deinstruções 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:

for encontrado no comandoEm 20/01 às 11:23h

Quando vamos cadastrar músicas, o ideal seria que pudéssemos cadastrar diversos CDs, e naúltima versão que fizemos do musinc, isso não ocorre, a cada CD que cadastramos o programatermina. Vejamos como melhorá-lo:

$ cat musinc#!/bin/bash# Cadastra CDs (versao 5)#Para=until [ "$Para"]doclearread -p "Título do Álbum: "Titif [ ! "$Tit"] # Se titulo vazio...thenPara=1 # Liguei flag de saídaelseif grep "^$Tit\^" musicas > /dev/nullthenecho Este álbum já está cadastradoexit 1fi

87

Page 89: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Reg="$Tit^"Cont=1oArt=while [ "$Tit"]doecho Dados da trilha $Cont:read -p "Música: "Mus[ "$Mus"] || break # Sai se vazioread -p "Artista: $oArt // "Art[ "$Art"] && oArt="$Art"# Se vazio Art anteriorReg="$Reg$oArt $Mus:"# Montando registroCont=$((Cont + 1))# A linha anterior tb poderia ser ((Cont++))doneecho "$Reg�> musicassort musicas -o musicasfidone

Nesta versão, um loop maior foi adicionado antes da leitura do título, que só terminará quando avariável $Para deixar de ser vazia. Caso o título do álbum não seja informado, a variável $Parareceberá valor (no caso coloquei 1 mas poderia ter colocado qualquer coisa. O importante é quenão seja vazia) para sair deste loop, terminando desta forma o programa. No resto, o script éidêntico à sua versão anterior.

10.4 Atalhos no loop

Nem sempre um ciclo de programa, compreendido entre um do e um done, sai pela porta dafrente. Em algumas oportunidades, temos que colocar um comando que aborte de forma con-trolada este loop. De maneira inversa, algumas vezes desejamos que o fluxo de execução doprograma 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 se-guinte forma:

O que eu não havia dito anteriormente é que nas suas sintaxes genéricas eles aparecem daseguinte forma:

break [qtd loop]econtinue [qtd loop]

Onde qtd loop representa a quantidade dos loops mais internos sobre os quais os comandosirão atuar. Seu valor default é 1.

88

Page 90: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

.Duvido que você nunca tenha deletado um arquivo e logo após deu um tabefe na testa se xin-gando porque não devia tê-lo removido. Pois é, na décima vez que fiz esta besteira, criei um scriptpara simular uma lixeira, isto é, quando mando remover um (ou vários) arquivo(s), o programa"finge"que removeu-o, mas no duro o que fez foi mandá-lo(s) para o diretório /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 removerthenecho "Erro -> Uso: erreeme arq [arq] ... [arq]"echo "O uso de metacaracteres é permitido. Ex. erreeme arq*"exit 1fi

89

Page 91: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

MeuDir="/tmp/$LOGNAME"# Variavel do sist. Contém o nome do usuário.if [ ! -d $MeuDir ] # Se não existir o meu diretório sob o /tmp...thenmkdir $MeuDir # Vou cria-lofi

if [ ! -w $MeuDir ] # Se não posso gravar no diretório...thenecho Impossivel salvar arquivos em $MeuDir. Mude permissao...exit 2fi

Erro=0 # Variavel para indicar o cod. de retorno do prgfor Arq # For sem o "in"recebe os parametros passadosdoif [ ! -f $Arq ] # Se este arquivo não existir...thenecho $Arq nao existe.Erro=3continue # Volta para o comando forfi

DirOrig='dirname $Arq' # Cmd. dirname informa nome do dir de $Arqif [ ! -w $DirOrig ] # Verifica permissão de gravacaoo no diretóriothenecho Sem permissao de remover no diretorio de $ArqErro=4continue # Volta para o comando forfi

if [ "$DirOrig"= "$MeuDir"] # Se estou "esvaziando a lixeira"...thenecho $Arq ficara sem copia de segurancarm -i $Arq # Pergunta antes de remover[ -f $Arq ] || echo $Arq removido # Será que o usuario removeu?continuefi

cd $DirOrig # Guardo no fim do arquivo o seu diretoriopwd � $Arq # original para usa-lo em um script de undeletemv $Arq $MeuDir # Salvo e removidoecho $Arq removido

90

Page 92: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

doneexit $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 parâmetrosinformados, mas como o script pode ter recebido diversos arquivos para remover, a cada arquivoque não se encaixa dentro do especificado, há um continue, para que a sequência volte para oloop do for de forma a receber outros arquivos. Quando você está no Windows (com perdão damá palavra) e tenta remover aquele monte de lixo com nomes esquisitos como HD04TG.TMP, seder erro em um deles, os outros não são removidos, não é? Então, o continue foi usado paraevitar que um impropério desses ocorra, isto é, mesmo que dê erro na remoção de um arquivo, oprograma continuará removendo os outros que foram passados.- Eu acho que a esta altura você deve estar curioso para ver o programa que restaura o arquivoremovido, não é? Pois então aí vai vai um desafio: faça-o em casa e me traga para discutirmosno nosso próximo encontro aqui no boteco.- Poxa, mas nesse eu acho que vou dançar, pois não sei nem como começar...- Cara, este programa é como tudo que se faz em Shell, extremamente fácil, é para ser feito em,no máximo 10 linhas. Não se esqueça que o arquivo está salvo em /tmp/$LOGNAME e que a suaúltima linha é o diretório em que ele residia antes de ser "removido". Também não se esqueça decriticar se foi passado o nome do arquivo a ser removido.- É eu vou tentar, mas sei não...- Tenha fé irmão, eu tô te falando que é mole! Qualquer dúvida é só me passar um e-mail [email protected]. Agora chega de papo que eu já estou de goela seca de tanto falar. Meacompanha no próximo 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!

91

Page 93: Shell Script

Capítulo 11

Parte VII

11.1 O comando tput

O maior uso deste comando é para posicionar o cursor na tela, mas também é muito usado paraapagar dados da tela, saber a quantidade de linhas e colunas para poder posicionar corretamenteum campo, apagar um campo cuja crítica detectou como errado. Enfim, quase toda a formataçãoda tela é feita por este comando.

Uns poucos atributos do comando tput podem eventualmente não funcionar se o modelo de ter-minal definido pela variável $TERM não tiver esta facilidade incorporada.

Na tabela a seguir, apresenta os principais atributos do comando e os efeitos executados so-bre o terminal, mas veja bem existem muito mais do que esses, veja só:

92

Page 94: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

.$ tput it8

Neste exemplo eu recebi o tamanho inicial da <TAB> ( Initial T ab), mas me diga: para queeu quero saber isso? Se você quiser saber tudo sobre o comando tput (e olha que é coisa quenão acaba mais), veja em:http://www.cs.utah.edu/dept/old/texinfo/tput/tput.html#SEC4.

Vamos fazer um programa bem besta (e portanto fácil) para mostrar alguns atributos deste co-mando. É o famoso e famigerado Alô Mundo só que esta frase será escrita no centro da tela eem vídeo reverso e após isso, o cursor voltará para a posição em que estava antes de escreveresta tão criativa frase. Veja:

$ cat alo.sh#!/bin/bash# Script bobo para testar# o comando tput (versao 1)

Colunas='tput cols' # Salvando quantidade colunasLinhas='tput lines' # Salvando quantidade linhasLinha=$((Linhas / 2)) # Qual eh a linha do meio da tela?Coluna=$(((Colunas - 9) / 2)) # Centrando a mensagem na telatput sc # Salvando posicao do cursor

93

Page 95: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

tput cup $Linha $Coluna # Posicionando para escrevertput rev # Video reversoecho Alô Mundotput sgr0 # Restaura video ao normaltput rc # Restaura cursor aa posição original

Como o programa já está todo comentado, acho que a única explicação necessária seria paraa linha em que é criada a variável Coluna e o estranho ali é aquele número 9, mas ele é o tama-nho da cadeia que pretendo escrever (Alô Mundo).

Desta forma este programa somente conseguiria centrar cadeias de 9 caracteres, mas veja isso:

$ var=Papo$ echo $#var4$ var="Papo de Botequim"$ echo $#var16

Ahhh, melhorou! Então agora sabemos que a construção $#variavel devolve a quantidade decaracteres de variavel. Assim sendo, vamos otimizar o nosso programa para que ele escrevaem vídeo reverso, no centro da tela a cadeia passada como parâmetro e depois o cursor volte àposição que estava antes da execução do script.

$ cat alo.sh#!/bin/bash# Script bobo para testar# o comando tput (versao 2)

Colunas='tput cols' # Salvando quantidade colunasLinhas='tput lines' # Salvando quantidade linhasLinha=$((Linhas / 2)) # Qual eh a linha do meio da tela?Coluna=$(((Colunas - $#1) / 2)) #Centrando a mensagem na telatput sc # Salvando posicao do cursortput cup $Linha $Coluna # Posicionando para escrevertput rev # Video reversoecho $1tput sgr0 # Restaura video ao normaltput rc # Restaura cursor aa posição original

Este script é igual ao anterior, só que trocamos o valor fixo da versão anterior (9), por $#1, ondeeste 1 é o $1 ou seja, esta construção devolve o tamanho do primeiro parâmetro passado para oprograma. Se o parâmetro que eu quiser passar tiver espaços em branco, teria que colocá-lo todoentre aspas, senão o $1$1 por $*, que como sabemos é o conjunto de todos os parâmetros. En-

94

Page 96: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

tão aquela linha ficaria assim: seria somente o primeiro pedaço. Para evitar este aborrecimento,é só substituir o

Coluna='$(((Colunas - $#*) / 2))' #Centrando a mensagem na tela

e a linha echo $1 passaria a ser echo $*. Mas não esqueça de qdo executar, passar a fraseque vc deseja centrar como parâmetro.

11.2 E agora podemos ler os dados na tela

Bem a partir de agora vamos aprender tudo sobre leitura, só não posso ensinar a ler cartas e bú-zios porque se eu soubesse, estaria rico, num pub londrino tomando scotch e não em um botecodesses tomando chope. Mas vamos em frente.

Da última vez que nos encontramos aqui eu já dei uma palinha sobre o comando read. Paracomeçarmos a sua analise mais detalhada. veja só isso:

$ read var1 var2 var3Papo de Botequim$ echo $var1Papo$ echo $var2de$ echo $var3Botequim$ read var1 var2Papo de Botequim$ echo $var1Papo$ echo $var2de Botequim

Como você viu, o read recebe uma lista separada por espaços em branco e coloca cada itemdesta lista em uma variável. Se a quantidade de variáveis for menor que a quantidade de itens, aúltima variável recebe o restante.

Eu disse lista separada por espaços em branco? Agora que você já conhece tudo sobre o $IFS(Inter Field Separator) que eu te apresentei quando falávamos do comando for, será que aindaacredita nisso? Vamos testar direto no prompt:

$ oIFS="$IFS"$ IFS=:$ read var1 var2 var3Papo de Botequim$ echo $var1Papo de Botequim

95

Page 97: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

$ echo $var2

$ echo $var3

$ read var1 var2 var3Papo:de:Botequim$ echo $var1Papo$ echo $var2de$ echo $var3Botequim$ IFS="$oIFS"

Viu, estava furado! O read lê uma lista, assim como o for, separada pelos caracteres da va-riável $IFS. Então veja como isso pode facilitar a sua vida:

$ grep julio /etc/passwdjulio: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"julio500544Julio C. Neves - 7070/home/julio/bin/bash$ IFS="$oIFS"# Restaurando IFS

Como você viu, a saída do grep foi redirecionada para o comando read que leu todos os camposde uma só tacada. A opção -e do echo foi usada para que o \n new line fosse entendido comoum salto de linha e não como um literal.

Sob o Bash existem diversas opções do read que servem para facilitar a sua vida. Veja a ta-bela a seguir:

.E agora direto aos exemplos curtos para demonstrar estas opções.

96

Page 98: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Para ler um campo "Matrícula":

$ echo -n "Matricula: "; read Mat # -n nao salta linhaMatricula: 12345$ echo $Mat12345

Ou simplificando com a opção -p:

$ read -p "Matricula: "MatMatricula: 12345$ echo $Mat12345

Para ler uma determinada quantidade de caracteres:

$ read -n5 -p"CEP: "Num ; read -n3 -p- ComplCEP: 12345-678$$ echo $Num12345$ echo $Compl678

Neste exemplo fizemos dois read: um para a primeira parte do CEP e outra para o seu com-plemento, deste modo formatando a entrada de dados. O cifrão ($) após o último algarismoteclado, é porque o read não tem o new-line implícito 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 com-pleto e só me deu tempo de teclar um J (aquele colado no Eta), mas serviu para mostrar duascoisas:

1. O comando após o par de barras verticais (||) (o ou lógico, lembra-se?) será executado casoa digitação não tenha sido concluída no tempo estipulado;

2. A variável Nom permaneceu vazia. Ela será valorada somente quando o <ENTER> forteclado.

Para ler um dado sem ser exibido na tela:

97

Page 99: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

$ read -sp "Senha: "Senha: $ echo $REPLYsegredo

Aproveitei um erro para mostrar um macete. Quando escrevi a primeira linha, esqueci de colocaro nome da variável que iria receber a senha, e só notei quando ia listar o seu valor. Felizmentea variável $REPLY do Bash, possui a última cadeia lida e me aproveitei disso para não perder aviagem. Teste você mesmo o que acabei de fazer.

Mas o exemplo que dei, era para mostrar que a opção -s impede o que está sendo teclado de irpara 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.

11.3 Vamos ler arquivos?

Como eu já havia lhe dito, e você deve se lembrar, o while testa um comando e executa um blocode instruções enquanto este comando for bem sucedido. Ora quando você está lendo um arquivoque lhe dá permissão de leitura, o read só será mal sucedido quando alcançar 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 Linhado

echo $Linhadone < arquivo

2 - Redirecionando a saída de um cat para o while, da seguinte maneira:

cat arquivo |while read Linhado

echo $Linhadone

Cada um dos processos tem suas vantagens e desvantagens:

Vantagens do primeiro processo:

• É mais rápido;

• Não necessita de um subshell para assisti-lo;

Desvantagem do primeiro processo:

• Em um bloco de instruções grande, o redirecionamento fica pouco visível o que por vezesprejudica a vizualização do código;

98

Page 100: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Vantagem do segundo processo:

• Como o nome do arquivo está antes do while, é mais fácil a vizualização do código.

Desvantagens do segundo processo:

• O Pipe (|) chama um subshell para interpretá-lo, tornando o processo mais lento, pesadoe por vezes problemático (veja o exemplo a seguir).

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/ whilewhile read LinhadoUltimo="$Linha"echo -$Ultimo-"doneecho "Acabou, Último=:$Ultimo:"

Vamos ver sua execução:

$ 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 próprias linhas com um sinal de menos (-) antes eoutro depois de cada, e no final exibe o conteúdo da variável $Ultimo. Repare no entanto que oconteúdo desta variável permanece como (vazio).

- Ué será que a variável não foi atualizada?- Foi, e isso pode ser comprovado porque a linha echo -$Ultimo-"lista corretamente as linhas.

99

Page 101: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

- Então porque isso aconteceu?- Por que como eu disse, o bloco de instruções redirecionado pelo pipe (|) é executado em umsubshell e lá as variáveis são atualizadas. Quando este subshell termina, as atualizações dasvariáveis vão para os píncaros do inferno junto com ele. Repare que vou fazer uma pequenamudança nele, passando o arquivo por redirecionamento de entrada (<) e as coisas passarão afuncionar na mais perfeita ordem:

$ cat redirread.sh#!/bin/bash# redirread.sh# Exemplo de read passando arquivo por pipe.

Ultimo="(vazio)"while read LinhadoUltimo="$Linha"echo -$Ultimo-"done < $0 # Passando o arq. do script ($0) p/ whileecho "Acabou, Último=:$Ultimo:"

E veja a sua perfeita execução:

$ 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 importantemacete que vou mostrar utilizando um exemplo prático. Suponha que você queira listar na tela umarquivo e a cada dez registros esta listagem pararia para que o operador pudesse ler o conteúdoda tela e ela só voltasse a rolar (scroll) após o operador digitar qualquer tecla. Para não gastarpapel (da Linux Magazine) pra chuchu, vou fazer esta listagem na horizontal e o meu arquivo(numeros), tem 30 registros somente com números seqüênciais. Veja:

$ seq 30 > numeros$ cat 10porpag.sh#!/bin/bash

100

Page 102: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

# Prg de teste para escrever# 10 linhas e parar para ler# Versão 1

while read Numdolet ContLin++ # Contando...echo -n "$Num "# -n para nao saltar linha((ContLin % 10)) > /dev/null || readdone < numeros

Na tentativa de fazer um programa genérico criamos a variável $ContLin (por que na vida real,os registros não são somente números seqüenciais) e parávamos para ler quando o resto dadivisão por 10 fosse zero (mandando a saída para /dev/null de forma a não aparecer na tela,sujando-a). Porém, quando fui executar deu a seguinte zebra:

Na tentativa de fazer um programa genérico criamos a variável $ContLin (por que na vida real, osregistros não são somente números seqüenciais) e parávamos para ler quando o resto da divisãopor 10 fosse zero (mandando a saída para/dev/null de forma a não aparecer na tela, sujando-a).Porém, quando fui executar deu a seguinte zebra:

$ 10porpag.sh1 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 número 11 e a listagem não parou no read. O que houve foi que toda aentrada do loop estava redirecionada do arquivo numeros e desta forma, a leitura foi feita emcima deste arquivo, desta forma perdendo o 11 (e também o 22).

Vamos mostrar então como deveria ficar para funcionar a contento:

$ cat 10porpag.sh#!/bin/bash# Prg de teste para escrever# 10 linhas e parar para ler# Versão 2

while read Numdolet ContLin++ # Contando...echo -n "$Num "# -n para nao saltar linha((ContLin % 10)) > /dev/null || read < /dev/ttydone < numeros

Observe que agora a entrada do read foi redirecionada por /dev/tty, que nada mais é senãoo terminal corrente, explicitando desta forma que aquela leitura seria feita do teclado e não denumeros. É bom realçar que isto não acontece somente quando usamos o redirecionamento deentrada, se houvéssemos usado o redirecionamento via pipe (|), o mesmo teria ocorrido.

101

Page 103: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Veja agora a sua execução:

$ 10porpag.sh1 2 3 4 5 6 7 8 9 1011 12 13 14 15 16 17 18 19 2021 22 23 24 25 26 27 28 29 30

Isto está quase bom mas falta um pouco para ficar excelente. Vamos melhorar um pouco oexemplo para que você o reproduza e teste (mas antes de testar aumente o número de registrosde 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# Versão 3

clearwhile read Numdo((ContLin++)) # Contando...echo "$Num"((ContLin % ('tput lines' - 3))) ||

read -n1 -p"Tecle Algo � /dev/tty # para ler qq caractereclear # limpa a tela apos leitura

done < numeros

A mudança substancial feita neste exemplo é com relação à quebra de página, 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-n1 para ler somente um caracteresem ser necessariamente um <ENTER> e a opção -p para dar a mensagem.

- Bem meu amigo, por hoje é só porque acho que você já está de saco cheio...- Num tô não, pode continuar...- Se você não estiver eu estou... Mas já que você está tão empolgado com o Shell, vou te deixarum exercício de apredizagem para você melhorar a sua CDteca que é bastante simples. Rees-creva 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 serão teclados pelo operador.

Não se esqueça, qualquer dúvida ou falta de companhia para um chope é só mandar um e-mail para [email protected]. Vou aproveitar também para mandar o meu jabá: diga para osamigos que quem estiver afim de fazer um curso porreta de programação em Shell (de 40 horas)que mande um e-mail para [email protected] para informar-se. Valeu!

102

Page 104: Shell Script

Capítulo 12

Parte VIII

12.1 Funções

- Chico! Agora traz dois chopes, sendo um sem colarinho, para me dar inspiração.

Pergunta (){# A função recebe 3 parâmetros 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=$#Msglocal Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linhatput cup $LinhaMesg $Colecho "$Msg"tput cup $LinhaMesg $((Col + TamMsg + 1))read -n1 SN[ ! $SN ] && SN=$2 # Se vazia coloca default em SNecho $SN | tr A-Z a-z # A saída de SN será em minúsculatput cup $LinhaMesg $Col; tput el # Apaga msg da telareturn # Sai da função}

Como podemos ver, uma função é definida quando fazemos nome_da_função () e todo o seucorpo está entre chaves ({}). Assim como conversamos aqui no Boteco sobre passagem de pa-râmetros, as funções os recebem da mesma forma, isto é, são parâmetros posicionais ($1, $2, ...,$n) e todas as regras que se aplicam a passagem de parâmetros para programas, também valempara funções, mas é muito importante realçar que os parâmetros passados para um programanão se confundem com aqueles que este passou para suas funções. Isso significa, por exemplo,que o $1 de um script é diferente do $1 de uma de suas funções

Repare que as variáveis $Msg, $TamMsg e $Col são de uso restrito desta rotina, e por isso

103

Page 105: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

foram criadas como local. A finalidade disso é simplesmente para economizar memória, já queao sair da rotina, elas serão devidamente detonadas da partição e caso não tivesse usado esteartifício, permaneceriam residentes.

A linha de código que cria local Msg, concatena ao texto recebido ($1) um abre parênteses, aresposta default ($2) em caixa alta, uma barra, a outra resposta ($3) em caixa baixa e finalizafechando o parênteses. Uso esta convenção para, ao mesmo tempo, mostrar as opções disponí-veis e realçar a resposta oferecida como default.

Quase ao fim da rotina, a resposta recebida ($SN) é passada para caixa baixa de forma queno corpo do programa não se precise fazer este teste.

Veja agora como ficaria a função para dar uma mensagem na tela:

function MandaMsg{# A função recebe somente um parâmetro# com a mensagem que se deseja exibir,# para não obrigar ao programador passar# a msq entre aspas, usaremos $* (todos# os parâmetro, lembra?) e não $1.local Msg="$*"local TamMsg=$#Msglocal Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linhatput cup $LinhaMesg $Colecho "$Msg"read -n1tput cup $LinhaMesg $Col; tput el # Apaga msg da telareturn # Sai da função}

Esta é uma outra forma de definir uma função: não a chamamos como no exemplo anteriorusando uma construção com a sintaxe nome_da_função (), mas sim como function nome_da_função.Quanto ao mais, nada difere da anterior, exceto que, como consta dos comentários, usamos avariável $* que como já sabemos é o conjunto de todos os parâmetros passados, para que oprogramador não precise usar aspas envolvendo a mensagem que deseja passar para a função.

Para terminar com este blá-blá-blá vamos ver então as alterações que o programa necessitaquando usamos o conceito de funções:

$ cat musinc6#!/bin/bash# Cadastra CDs (versao 6)#

# Área de variáveis globaisLinhaMesg?=$(('tput lines' - 3)) # Linha que msgs serão dadas para operador

104

Page 106: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

TotCols?=$(tput cols) # Qtd colunas da tela para enquadrar msgs

# Área de funçõesPergunta ()

{# A função recebe 3 parâmetros 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?=$#Msglocal Col=$(((TotCols? - TamMsg?) / 2)) # Centra msg na linhatput cup $LinhaMesg $Colecho "$Msg"tput cup $LinhaMesg $((Col + TamMsg? + 1))read -n1 SN[ ! $SN ] && SN=$2 # Se vazia coloca default em SNecho $SN | tr A-Z a-z # A saída de SN será em minúsculatput cup $LinhaMesg $Col; tput el # Apaga msg da telareturn # Sai da função}

function MandaMsg?{# A função recebe somente um parâmetro# com a mensagem que se deseja exibir,# para não obrigar ao programador passar# a msg entre aspas, usaremos $* (todos# os parâmetros, lembra?) e não $1.local Msg="$*"local TamMsg?=${#Msg}local Col=$(((TotCols? - TamMsg?) / 2)) # Centra msg na linhatput cup $LinhaMesg $Colecho "$Msg"read -n1tput cup $LinhaMesg $Col; tput el # Apaga msg da telareturn # Sai da função}

# O corpo do programa propriamente dito começa aquiclearecho "Inclusao de Músicas==== == ===

105

Page 107: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Título do Álbum:| Este campo foi

Faixa< criado somente para|orientar o preenchimentoNome da Música:

Intérprete:"# Tela montada com um único echowhile true

dotput cup 5 38; tput el # Posiciona e limpa linharead Album[ ! "$Album"] && # Operador deu{Pergunta "Deseja Terminar"s n[ $SN = "n"] && continue # Agora só testo a caixa baixaclear; exit # Fim da execução}grep -iq "^$Album\^" musicas 2> /dev/null &&{MandaMsg? Este álbum já está cadastradocontinue # Volta para ler outro álbum}Reg="$Album^" # $Reg receberá os dados de gravaçãooArtista= # Guardará artista anteriorwhile truedo((Faixa++))tput cup 7 38echo $Faixa

tput cup 9 38 # Posiciona para ler musicaread Musica[ "$Musica"] || # Se o operador tiver dado ...{Pergunta "Fim de Álbum?"s n[ "$SN"= n ] && continue # Agora só testo a caixa baixabreak # Sai do loop para gravar dados}

106

Page 108: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

tput cup 11 38 # Posiciona para ler Artista[ "$oArtista"]&& echo -n "($oArtista) "# Artista anterior é defaultread Artista[ "$Artista"] && oArtista="$Artista"Reg="$Reg$oArtista $Musica:"# Montando registrotput cup 9 38; tput el # Apaga Musica da telatput cup 11 38; tput el # Apaga Artista da teladoneecho "$Reg�> musicas # Grava registro no fim do arquivosort musicas -o musicas # Classifica o arquivodone

Repare que a estruturação do _script_está conforme o gráfico a seguir:

Variáveis GlobaisFunções

Corpo do Programa

Esta estruturação é 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 variável para ser vista simultane-amente pelo script e suas funções deve ser declarada (ou inicializada) antes de qualquer coisa.As funções por sua vez devem ser declaradas antes do corpo do programa propriamente ditoporque no ponto em que o programador mencionou seu nome, o interpretador Shell já o haviaantes localizado e registrado que era uma função.

Uma coisa bacana no uso de funções é fazê-las o mais genérico possível de forma que elassirvam para outras aplicações, sem necessidade de serem reescritas. Essas duas que acaba-mos de ver têm uso generalizado, pois é difícil um script que tenha uma entrada de dados peloteclado que não use uma rotina do tipo da MandaMsg ou não interage com o operador por algosemelhante à Pergunta.

Conselho de amigo: crie um arquivo e cada função nova que você criar, anexe-a a este arquivo.Ao final de um tempo você terá uma bela biblioteca de funções que lhe poupará muito tempo deprogramação.

12.2 O comando source

Vê se você nota algo de diferente na saída do ls a seguir:

$ ls -la .bash_profile-rw-r�r� 1 Julio unknown 4511 Mar 18 17:45 .bash_profile

Não olhe a resposta não, volte a prestar atenção! Bem, já que você está mesmo sem sacode pensar e prefere ler a resposta, vou te dar uma dica: acho que você sabe que o .bash_profileé um dos programas que são automaticamente "executados"quando você se loga (ARRGGHH!Odeio este termo). Agora que te dei esta dica olhe novamente para a saída do ls e me diga o quehá de diferente nela.

107

Page 109: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Como eu disse o .bash_profile é "executado"em tempo de logon e repare que não tem nenhumdireito de execução. Isso se dá porque o se você o executasse como qualquer outro script careta,quando terminasse sua execução todo o ambiente por ele gerado morreria junto com o Shell sobo qual ele foi executado (você se lembra que todos os scripts são executados em subshells, né?).

Pois é. É para coisas assim que existe o comando source, também conhecido por . (ponto).Este comando faz com que não seja criado um novo Shell (um subshell) para executar o pro-grama que que lhe é passado como parâmetro.

Melhor um exemplo que 453 palavras. Veja este scriptizinho a seguir:

$ cat script_bobocd ..ls

Ele simplesmente deveria ir para o diretório acima do diretório atual. Vamos executar uns co-mandos envolvendo o script_bobo e vamos analisar os resultados:

$ pwd/home/jneves$ script_bobojneves juliana paula silvie$ pwd

/home/jneves

Se eu mandei ele subir um diretório, porque não subiu? Subiu sim! O subshellscript tanto subiuque listou os diretórios dos quatro usuários 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:que foi criado para executar o

$ source script_bobojneves juliana paula silvie$ pwd/home$ cd -/home/jneves$ . script_bobojneves juliana paula silvie$ pwd/home

Ahh! Agora sim! Sendo passado como parâmetro do comando source ou .script foi executadono Shell corrente deixando neste, todo o ambiente criado. Agora damos um rewind para o inícioda explicação sobre este comando. Lá falamos do .bash_profile, e a esta altura você já devesaber que a sua incumbência é, logo após o login, deixar o ambiente de trabalho preparado parao usuário, e agora entendemos que é por isso mesmo que ele é executado usando este artifício.

108

Page 110: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

E agora você deve estar se perguntando se é só para isso que este comando serve, e eu lhedigo que sim, mas isso nos traz um monte de vantagens e uma das mais usadas é tratar funçõescomo rotinas externas. Veja uma outra forma de fazer o nosso programa para incluir CDs noarquivo musicas:

$ cat musinc7#!/bin/bash# Cadastra CDs (versao 6)#

# Área de variáveis globaisLinhaMesg?=$(('tput lines' - 3)) # Linha que msgs serão dadas para operadorTotCols?=$(tput cols) # Qtd colunas da tela para enquadrar msgs

# O corpo do programa propriamente dito começa aquiclearecho "Inclusao de Músicas==== == ===

Título do Álbum:| Este campo foi

Faixa< criado somente para|orientar o preenchimentoNome da Música:

Intérprete:"# Tela montada com um único echowhile true

dotput cup 5 38; tput el # Posiciona e limpa linharead Album[ ! "$Album"] && # Operador deu{source pergunta.func "Deseja Terminar"s n[ $SN = "n"] && continue # Agora só testo a caixa baixa

109

Page 111: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

clear; exit # Fim da execução}grep -iq "^$Album\^" musicas 2> /dev/null &&{. mandamsg.func Este álbum já está cadastradocontinue # Volta para ler outro álbum}Reg="$Album^" # $Reg receberá os dados de gravaçãooArtista= # Guardará artista anteriorwhile truedo((Faixa++))tput cup 7 38echo $Faixa

tput cup 9 38 # Posiciona para ler musicaread Musica[ "$Musica"] || # Se o operador tiver dado ...{. pergunta.func "Fim de Álbum?"s n[ "$SN"= n ] && continue # Agora só testo a caixa baixabreak # Sai do loop para gravar dados}tput cup 11 38 # Posiciona para ler Artista[ "$oArtista"]&& echo -n "($oArtista) "# Artista anterior é defaultread Artista[ "$Artista"] && oArtista="$Artista"Reg="$Reg$oArtista $Musica:"# Montando registrotput cup 9 38; tput el # Apaga Musica da telatput cup 11 38; tput el # Apaga Artista da teladoneecho "$Reg�> musicas # Grava registro no fim do arquivosort musicas -o musicas # Classifica o arquivodone

Agora o programa deu uma boa encolhida e as chamadas de função foram trocadas por ar-quivos externos chamados pergunta.func e mandamsg.func, que assim podem ser chamados porqualquer outro programa, desta forma reutilizando o seu código.

Por motivos meramente didáticos as execuções de pergunta.func e mandamsg.funcsource e por. (ponto) indiscriminadamente, embora prefira o source por ser mais visível desta forma dandomaior legibilidade ao código e facilitando sua posterior manutenção.

Veja agora como ficaram estes dois arquivos:

$ cat pergunta.func# A função recebe 3 parâmetros na seguinte ordem:# $1 - Mensagem a ser dada na tela

110

Page 112: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

# $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=$#MsgCol=$(((TotCols - TamMsg) / 2)) # Centra msg na linhatput cup $LinhaMesg $Colecho "$Msg"tput cup $LinhaMesg $((Col + zTamMsg + 1))read -n1 SN[ ! $SN ] && SN=$2 # Se vazia coloca default em SNecho $SN | tr A-Z a-z # A saída de SN será em minúsculatput cup $LinhaMesg $Col; tput el # Apaga msg da tela$ cat mandamsg.func# A função recebe somente um parâmetro# com a mensagem que se deseja exibir,# para não obrigar ao programador passar# a msq entre aspas, usaremos $* (todos# os parâmetro, lembra?) e não $1.Msg="$*"TamMsg=$#MsgCol=$(((TotCols - TamMsg) / 2)) # Centra msg na linhatput cup $LinhaMesg $Colecho "$Msg"read -n1tput cup $LinhaMesg $Col; tput el # Apaga msg da tela

Em ambos os arquivos, fiz somente duas mudanças que veremos nas observações a seguir,porém tenho mais três a fazer:

1. As variáveis não estão sendo mais declaradas como local, porque está é uma diretiva quesó pode ser usada no corpo de funções e portanto estas variáveis permanecem no ambientedo Shell, poluindo-o;

2. O comando return não está mais presente mas poderia estar sem alterar em nada a lógica,uma vez que ele só serviria para indicar um eventual erro via um código de retorno previa-mente estabelecido (por exemplo return 1, return 2, ...), sendo que o return e return 0 sãoidênticos e significam rotina executada sem erros;

3. O comando que estamos acostumados a usar para gerar código de retorno é o exit, masa saída de uma rotina externa não pode ser feita desta forma, porque por estar sendoexecutada no mesmo Shell que o script chamador, o exit simplesmente encerraria esteShell, terminando a execução de todo o script;

4. De onde surgiu a variável LinhaMesg? Ela veio do musinc7, porque ela havia sido declaradaantes da chamada das rotinas (nunca esquecendo que o Shell que está interpretando oscript e estas rotinas é o mesmo);

5. Se você decidir usar rotinas externas, não se avexe, abunde os comentários (principalmente

111

Page 113: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

sobre a passagem dos parâmetros) para facilitar a manutenção e seu uso por outros pro-gramas no futuro.

- Bem, agora você já tem mais um monte de novidade para melhorar os scriptslistartista no qualvocê passava o nome de um artista como parâmetro e ele devolvia as suas músicas? Ele eraassim:

$ cat listartista#!/bin/bash# Dado um artista, mostra as suas musicas# versao 2

if [ $# -eq 0 ]thenecho Voce deveria ter passado pelo menos um parametroexit 1fi

IFS=":"for ArtMus in $(cut -f2 -d^ musicas)doecho "$ArtMus"| grep -i "^$*~" > /dev/null && echo $ArtMus verb=|= cut -f2 -ddone

- Claro que me lembro!...- Então para firmar os conceitos que te passei, faça ele com a tela formatada, em loop, de formaque ele só termine quando receber um <ENTER> puro no nome do artista. Ahhh! Quando alistagem atingir a antepenúltima linha da tela, o programa deverá dar uma parada para que ooperador possa lê-las, isto é, suponha que a tela tenha 25 linhas. A cada 22 músicas listadas(quantidade de linhas menos 3) o programa aguardará que o operador tecle algo para então pros-seguir. Eventuais mensagens de erro devem ser passadas usando a rotina mandamsg.func queacabamos de desenvolver.- Chico, manda mais dois, o meu é com pouca pressão...

112

Page 114: Shell Script

Capítulo 13

Parte IX

13.1 Envenenando a escrita

- Ufa! Agora você já sabe tudo sobre leitura, mas sobre escrita está apenas engatinhando. Já seique você vai me perguntar:- Ora, não é com o comando echo e com os redirecionamentos de saída que se escreve?É, com estes comandos você escreve 90% das coisas necessárias, porém se precisar de es-crever algo formatado eles lhe darão muito trabalho. Para formatar a saída veremos agora umainstrução 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 especificação de formato e 3 - seqüência de escape no padrão da linguagem C.Argumento - é a cadeia a ser impressa sob o controle do formato.

Cada um dos caracteres utilizados para especificação de formato é precedido pelo caracter %e logo a seguir vem a especificação de formato de acordo com a tabela:

.As seqüências de escape padrão da linguagem C são sempre precedidas por um contra-barra (\)e as reconhecidas pelo comando printf são:

113

Page 115: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

.Não acabou por aí não! Tem muito mais coisa sobre a instrução, mas como é muito cheio dedetalhes e, portanto, chato para explicar e, pior ainda para ler ou estudar, vamos passar diretoaos exemplos com seus comentários, que não estou aqui para encher o saco de ninguém.

$ printf "%c1 caracter"1$ Errado! Só listou 1 caractere e não saltou linha ao final$ printf "%c\n1 caracter"1 Saltou linha mas ainda não listou a cadeia inteira$ printf "%c caractere\n"11 caractere Esta é a forma correta o %c recebeu o 1$ a=2$ printf "%c caracteres\n"$a2 caracteres O %c recebeu o valor da variável $a$ printf "%10c caracteres\n"$a 2 caracteres$ printf "%10c\n"$a caracteres2c

Repare que nos dois últimos exemplos, em virtude do %c, só foi listado um caracter de cadacadeia. O 10 à frente do c, não significa 10 caracteres. Um número seguindo o sinal de percen-tagem (%) significa o tamanho que a cadeia terá após a execução do comando.

E tome de exemplo:

$ printf "%d\n"3232$ printf "%10d\n"3232 Preenche com brancos à esquerda e não com zeros$ printf "%04d\n"320032 04 após % significa 4 dígitos 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.332.300000 O default do %f é 6 decimais$ printf "%.2f\n"32.3

114

Page 116: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

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"1012 Converteu o 10 para octal$ printf "%03o\n"10octal, né?$ printf "%s\n"PetelecaPeteleca$ printf "%15s\n"PetelecaPeteleca Peteleca com 15 caracteres enchidos com brancos$ printf "%-15sNeves\n"PetelecaPeteleca Neves O menos (-) encheu à direita com brancos$ printf "%.3s\n"PetelecaPet 3 trunca as 3 primeiras$ printf "%10.3sa\n"PetelecaPeta Pet com 10 caracteres concatenado com a (após o s)$ printf "EXEMPLO %x\n"45232EXEMPLO b0b0 Transformou para hexa mas os zeros não combinam$ printf "EXEMPLO %X\n"45232EXEMPLO B0B0 Assim disfarçou melhor (repare o X maiúsculo)$ printf "%X %XL%X\n"49354 192 10C0CA C0LA

O último exemplo não é 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 espaço em branco seguido por outro %XL. O %X192 dando comoresultado C0 que com o L fez C0L; converteu o

3. E finalmente o último %X transformou o 10 em A.

Conforme vocês podem notar, a instrução printf é bastante completa e complexa (ainda bem queo echo resolve quase tudo).

Creio que quando resolvi explicar o printf através de exemplos, acertei em cheio pois não sa-beria como enumerar tantas regrinhas sem tornar a leitura enfadonha.

13.2 Principais Variáveis do Shell

O Bash possui diversas variáveis que servem para dar informações sobre o ambiente ou alterá-lo.Seu número é muito grande e não pretendo mostrar todas, mas uma pequena parte que pode lheajudar na elaboração de scripts. Então aí vão as principais:

Principais variáveis do Bash

CDPATH Contém os caminhos que serão pesquisados para tentar localizar um diretório especifi-cado. Apesar desta variável ser pouco conhecida, seu uso deve ser incentivado por poupar

115

Page 117: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

muito trabalho, principalmente em instalações com estrutura de diretórios com bastante ní-veis.

HISTSIZE Limita o número de instruções que cabem dentro do arquivo de histórico de coman-dos (normalmente .bash_history mas efetivamente é o que está armazenado na variável$HISTFILE). Seu valor default é 500.

HOSTNAME O nome do host corrente (que também pode ser obtido com o comando uname -n).LANG Usada para determinar a língua falada no pais (mais especificamente categoria dolocale).

LINENO O número da linha do script ou da função que está sendo executada, seu uso princi-pal é para dar mensagens de erro juntamente com as variáveis $0 (nome do programa) e$FUNCNAME (nome da função em execução)

LOGNAME Armazena o nome de login do usuário.

MAILCHECK Especifica, em segundos, a freqüência que o Shell verificará a presença de corres-pondências nos arquivos indicados pela variáveis $MAILPATH ou $MAIL. O tempo padrão é60 segundos. Uma vez este tempo expirado, o Shell fará esta verificação antes de exibir opróximo prompt primário (definido em $PS1). Se esta variável estiver sem valor ou com umvalor menor ou igual a zero, a verificação de novas correspondências não será efetuada.

PATH Caminhos que serão pesquisados para tentar localizar um arquivo especificado. Comocada script é um arquivo, caso use o diretório corrente (.) na sua variável $PATH, você nãonecessitará de usar o ./scrp para que scrp seja executado. Basta fazer scrp. Este é o modoque procedo aqui no Botequim.

PIPESTATUS É uma variável do tipo vetor (array) que contém uma lista valores de código deretorno do último pipeline executado, isto é, um array que abriga cada um dos $? de cadainstrução do último pipeline.

PROMPT_COMMAND Se esta variável receber uma instrução, toda vez que você der um <EN-TER> direto no prompt principal ($PS1), este comando será executado. É útil quando seestá repetindo muito uma determinada instrução.

PS1 É o prompt principal. No "Papo de Botequim"usamos os seus defaults: $ para usuáriocomum e # para root, mas é muito freqüente que ele esteja customizado. Uma curiosidadeé que existe até concurso de quem programa o $PS1 mais criativo. (clique para dar umagooglada)

PS2 Também chamado prompt de continuação, é aquele sinal de maior (>) que aparece apósum <ENTER> sem o comando ter sido encerrado.

PWD Possui o caminho completo ($PATH) do diretório corrente. Tem o mesmo efeito do comandopwd.

RANDOM Cada vez que esta variável é acessada, devolve um número inteiro, que é um randô-mico entre 0 e 32767.

REPLY Use esta variável para recuperar o último campo lido, caso ele não tenha nenhuma va-riável associada.

116

Page 118: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

SECONDS Esta variável contém a quantidade de segundos que o Shell corrente está de pé.Use-a somente para esnobar um usuários daquilo que chamam de sistema operacional,mas necessita de boots freqüentes. )

TMOUT Se tiver um valor maior do que zero, este valor será tomado como o padrão de timeoutdo comando read. No prompt, este valor é interpretado como o tempo de espera por umaação antes de expirar a sessão. Supondo que a variável contenha 30, o Shell dará logoutapós 30 segundos de prompt sem nenhuma ação.

• CDPATH

$ echo $CDPATH.:..: :/usr/local$ pwd/home/jneves/LM$ cd bin$ pwd/usr/local/bin

Como /usr/local estava na minha variável $CDPATH, e não existia o diretório bin em nenhum dosseus antecessores (., .. e ~), o cd foi executado para /usr/local/bin

• LANG

$ dateThu Apr 14 11:54:13 BRT 2005$ LANG=pt_BR dateQui Abr 14 11:55:14 BRT 2005

Com a especificação da variável LANG=pt_BR (português do Brasil), a data passou a ser infor-mada no padrão brasileiro. É interessante observarmos que não foi usado ponto-e-vírgula (;)para separar a atribuição de LANG do comando date.

• PIPESTATUS

$ whojneves 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 usuário botelho não estava "logado", em seguida executamosum pipeline que procurava por ele. Usa-se a notação [*] em um array para listar todos os seus ele-mentos, e desta forma vimos que a primeira instrução (who) foi bem sucedida (código de retorno0) e a seguinte (grep), não (código de retorno 1).

• RANDOM

Para gerar randomicamente um inteiro entre 0 e 100, fazemos:$ echo $((RANDOM%101))

117

Page 119: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

73

Ou seja pegamos o resto da divisão por 101 do número randômico gerado, porque o resto dadivisão de qualquer número por 101 varia entre 0 e 100.

• REPLY

$ whojneves 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$ read -p "Digite S ou N: "Digite S ou N: N$ echo $REPLYN

Eu sou do tempo que memória era um bem precioso que custava muuuuito caro. Então parapegar um S ou um N, não costumo a alocar um espaço especial e assim sendo, pego o que foidigitado na variável \$REPLY.

13.3 Expansão de parâmetros

Bem, muito do que vimos até agora são comandos externos ao Shell. Eles quebram o maiorgalho, facilitam a visualização, manutenção e depuração do código, mas não são tão eficientesquanto os intrínsecos (built-ins). Quando o nosso problema for performance, devemos dar pre-ferência ao uso dos intrínsecos e a partir de agora vou te mostrar algumas técnicas para o teuprograma pisar no acelerador.

Na tabela e exemplos a seguir, veremos uma série de construções chamadas expansão (ou subs-tituição) de parâmetros (Parameter Expansion), que substituem instruções como o cut, o expr, otr, o sed e outras de forma mais ágil.

118

Page 120: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

• Se em uma pergunta o S é oferecido como valor default (padrão) e a saída vai para avariável $SN, após ler o valor podemos fazer:

SN=$(SN:-S}

Desta forma se o operador deu um simples <ENTER> para confirmar que aceitou o valor de-fault, após executar esta instrução, a variável terá o valor S, caso contrário, terá o valor digitado.

• Para sabermos o tamanho de uma cadeia:

$ cadeia=0123

$ echo $#cadeia4

• Para extrair de uma cadeia da posição um até o final fazemos:

$ cadeia=abcdef

$ echo $cadeia:1bcdef

Repare que a origem é zero e não um.

• Na mesma variável $cadeia do exemplo acima, para extrair 3 caracteres a partir da 2ª posi-ção:

$cde

Repare que novamente que a origem da contagem é zero e não um.

119

Page 121: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

• Para suprimir tudo à esquerda da primeira ocorrência de uma cadeia, faça:

$ 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 ocorrência da expressão*' ', ou seja, tudo até o primeiro espaço em branco.

Estes exemplos também poderiam ser escritos sem protegermos o espaço da interpretação doShell (mas prefiro protegê-lo para facilitar a legibilidade do código), veja:

$ echo $cadeia#*de Botequim

$ echo "Conversa "$cadeia#*Conversa de Botequim

Repare que na construção de expr é permitido o uso de metacaracteres.

• Utilizando o mesmo valor da variável $cadeia, observe como faríamos para termos somenteBotequim:

$ echo $cadeia##*' 'Botequim

$ echo "Vamos 'Chopear' no "$cadeia##*' 'Vamos 'Chopear' no Botequim

Desta vez suprimimos à esquerda de cadeia a maior ocorrência da expressão expr. Assim comono caso anterior, o uso de metacaracteres é permitido.

Outro exemplo mais útil: para que não apareça o caminho (path) completo do seu programa(que, como já sabemos está contido na variável $0) em uma mensagem de erro, inicie o seu textoda seguinte forma:

echo Uso: $0##*/ texto da mensagem de erro

Neste exemplo seria suprimido à esquerda tudo até a última barra (/) do caminho (path), destaforma sobrando somente o nome do programa.

• O uso do percentual (%) é como se olhássemos o jogo-da-velha (#) no espelho, isto é, sãosimétricos. Então vejamos um exemplo para provar isso:

120

Page 122: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

$ echo $cadeiaPapo de Botequim

$ echo $cadeia%' '*Papo de

$ echo $cadeia%%' '*Papo

• Para trocar primeira ocorrência de uma subcadeia em uma cadeia por outra:

$ echo $cadeia/de/no

Papo no Botequim

$ echo $cadeia/de /Papo Botequim

Neste caso preste a atenção quando for usar metacaracteres, eles são gulosos! Eles semprecombinarão com a maior possibilidade, veja o exemplo a seguir onde a intenção era trocar Papode Botequim por Conversa de Botequim:

$ echo $cadeiaPapo de Botequim

$ echo $cadeia/*o/ConversaConversatequim

A idéia era pegar tudo até o primeiro o, mas o que foi trocado foi tudo até o último o. Isto po-deria ser resolvido de diversas maneiras, veja algumas:

$ echo $cadeia/*po/ConversaConversa de Botequim

$ echo $cadeia/????/ConversaConversa de Botequim

• Trocando todas as ocorrências de uma subcadeia por outra. Quando fazemos:

$ echo $cadeia//o/aPapa de Batequim

Trocamos todos as letras o por a. Outro exemplo mais útil é para contarmos a quantidade dearquivos existentes no diretório corrente. Observe a linha a seguir:

$ ls | wc -l30

Viu? O wc produz um monte de espaços em branco no início. Para tirá-los podemos fazer:

121

Page 123: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

$ QtdArqs=$(ls | wc -l) # QtdArqs recebe a saída do comando

$ echo $QtdArqs// /30

No último exemplo, como eu sabia que a saída era composta de brancos e números, monteiesta expressão para trocar todos os espaços por nada. Repare que após as duas primeiras bar-ras existe um espaço em branco.

Outra forma de fazer a mesma coisa seria:

$ echo $QtdArqs/* /30

• Trocando uma subcadeia no início ou no fim de uma variável. Para trocar no início fazemos:

$ echo $Passaroquero quero

$ echo "Como diz o sulista - "$Passaro/#quero/nãoComo diz o sulista - não quero

Para trocar no final fazemos:

$ echo "Como diz o nordestino - "$Passaro/%quero/nãoComo diz o nordestino - quero não

- Agora já chega, o papo hoje foi muito chato porque foi muita decoreba, mas o principal é você terentendido o que te falei e, quando precisar, consulte estes guardanapos em que rabisquei estasdicas e depois guarde-os para consultas futuras. Mas voltando à vaca fria: tá na hora de tomaroutro e ver o jogo do mengão. Na próxima vou te dar moleza e só vou cobrar o seguinte: peguea rotina pergunta.func, (a que na qual falamos no início do nosso bate papo de hoje) e otimize-apara que a variável $SN receba o valor default por expansão de parâmetros, como vimos.- Chico, vê se não esquece de mim e enche meu copo.

122

Page 124: Shell Script

Capítulo 14

Parte X

14.1 O comando eval

- Vou te dar um problema que eu duvido que você resolva:

$ var1=3$ var2=var1

- Te dei estas duas variáveis, 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 deexecução e resultará em var1, montando echo$var1 que produzirá 3...

- A é? Então execute para ver se está correto.

$ echo $'echo $var2'$var1

- Ué! Que foi que houve? O meu raciocínio me parecia bastante lógico...- O seu raciocínio realmente foi lógico, o problema é que você esqueceu de uma das primeirascoisas que te falei aqui no Boteco e vou repetir. O Shell usa a seguinte ordem para resolver umalinha de comandos:

• Resolve os redirecionamentos;

• Substitui as variáveis pelos seus valores;

• Resolve e substitui os meta caracteres;

• Passa a linha já toda esmiuçada para execução.

Desta forma, quando chegou na fase de resolução de variáveis, que como eu disse é anteriorà execução, a única variável existente era $var2 e por isso a tua solução produziu como saída

123

Page 125: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

$var1. O comando echo identificou isso como uma cadeia e não como uma variável.

Problemas deste tipo são relativamente freqüentes e seriam insolúveis caso não existisse a ins-trução eval, cuja sintaxe é:

eval cmd

Onde cmd é uma linha de comando qualquer que você poderia inclusive executar direto no promptdo terminal. Quando você põe o eval na frente, no entanto, o que ocorre é que o Shell trata cmdcomo se seus dados fossem parâmetros do eval e em seguida o eval executa a linha recebida,submetendo-a ao Shell, dando então na prática duas passadas em cmd.

Desta forma se executássemos o comando que você propôs colocando o eval à sua frente, tería-mos a saída esperada, veja:

$ eval echo $'echo $var2'3

Este exemplo também poderia ter sido feito da seguinte maneira:

\$ eval echo \$$var23

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:

$ $var210porpag1.sh alo2.sh listamusica logaute.sh10porpag2.sh confuso listartista mandamsg.func10porpag3.sh contpal.sh listartista3 monbg.shalo1.sh incusu logado

Agora vamos colocar em var2 o seguinte: ls $var1; e em var1 vamos colocar l*, vejamos:

$ var2='ls $var1'$ var1='l*'$ $var2ls: $var1: No such file or directory$ eval $var2listamusica listartista listartista3 logado logaute.sh

Novamente, no tempo de substituição das variáveis, $var1 ainda não havia se apresentado ao

124

Page 126: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Shell para ser resolvida, desta forma só nos resta executar o comando eval para dar as duaspassadas necessárias.

Uma vez um colega de uma excelente lista sobre Shell Script, colocou uma dúvida: queria fa-zer um menu que numerasse e listasse todos os arquivos com extensão .sh e quando o operadorescolhesse uma opção, o programa correspondente seria executado. A minha proposta foi a se-guinte:

$ cat fazmenu#!/bin/bash## Lista numerando os programas com extensão .sh no# diretório corrente e executa o escolhido pelo operador#clear; i=1printf "%11s\t%s\n\n"Opção ProgramaCASE='case $opt in'for arq in *.shdoprintf "\t%03d\t%s\n"$i $arqCASE="$CASE"$(printf "%03d)\t %s;;"$i $arq)i=$((i+1))doneCASE="$CASE*) . erro;;esac"read -n3 -p "Informe a opção desejada: "optechoeval "$CASE"

Parece complicado porque usei muito printf para formatação da tela, mas é bastante simples,vamos entendê-lo: o primeiro printf foi colocado para fazer o cabeçalho e logo em seguidacomecei a montar dinamicamente a variável $CASE, na qual ao final será feito um eval para exe-cução 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 readvocê colocar uma linha echo "$CASE", verá que o comando case montado dentro da variável estátodo indentado. Frescura, né?. Na saída do for, foi adicionada uma linha à variável $CASE, parano caso de se fazer uma opção inválida, ser executada uma função externa para dar mensagensde erro.

Vamos executá-lo para ver a saída gerada:

$ fazmenu.shOpcao Programa001 10porpag1.sh002 10porpag2.sh003 10porpag3.sh

125

Page 127: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

004 alo1.sh005 alo2.sh006 contpal.sh007 fazmenu.sh008 logaute.sh009 monbg.sh010 readpipe.sh011 redirread.shInforme a opção desejada:

Neste programa seria interessante darmos uma opção de término, e para isso seria necessário ainclusão de uma linha após o loop de montagem da tela e alterarmos a linha na qual fazemos aatribuição final do valor da variável $CASE. Vejamos como ele ficaria:

$ cat fazmenu#!/bin/bash## Lista numerando os programas com extensão .sh no# diretório corrente e executa o escolhido pelo operador#clear; i=1printf "%11s\t%s\n\n" Opção ProgramaCASE='case $opt in'for arq in *.shdoprintf "\t%03d\t%s\n" $i $arqi=$((i+1))doneprintf "\t%d\t%s\n\n"999 "Fim do programa"# Linha incluidaCASE="$CASE999) exit;; # Linha alterada*) ./erro;;esac"read -n3 -p "Informe a opção desejada: "optechoeval "$CASE"

14.2 Sinais de Processos

Existe no Linux uma coisa chamada sinal (signal). Existem diversos sinais que podem ser manda-dos para (ou gerados por) processos em execução. Vamos de agora em diante dar uma olhadinhanos sinais mandados para os processos e mais à frente vamos dar uma passada rápida pelossinais gerados por processos.

126

Page 128: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

14.3 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). Além do comandokill, algumas seqüências de teclas também podem gerar sig. A tabela a seguir mostra os sinaismais importantes para monitorarmos:

.Além destes sinais, existe o famigerado -9 ou SIGKILL que, para o processo que o está recebendo,equivale a meter o dedo no botão de desliga do computador o que seria altamente indesejáveljá que muitos programas necessitam "limpar o meio de campo"ao seu término. Se o seu finalocorrer de forma prevista, ou seja se tiver um término normal, é muito fácil de fazer esta limpeza,porém se o seu programa tiver um fim brusco muita coisa pode ocorrer:

• É possível que em um determinado espaço de tempo, o seu computador esteja cheio dearquivos de trabalho inúteis

• Seu processador poderá ficar atolado de processos zombies e defuncts gerados por pro-cessos filhos que perderam os pais;

• É necessário liberar sockets abertos para não deixar os clientes congelados;

• Seus bancos de dados poderão ficar corrompidos porque sistemas gerenciadores de ban-cos de dados necessitam de um tempo para gravar seus buffers em disco (commit).

Enfim, existem mil razões para não usar um kill com o sinal -9 e para monitorar fins anormaisde programas.

14.4 O trap não atrapalha

Para fazer a monitoração descrita acima existe o comando trap cuja sintaxe é:

trap "cmd1; cmd2; cmdn"S1 S2 ... SNou

trap 'cmd1; cmd2; cmdn' S1 S2 ... SN

Onde os comandos cmd1, cmd2, cmdn serão executados caso o programa receba os sinais S1

127

Page 129: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

S2 ... SN.

As aspas (") ou os apóstrofos (') só são necessários caso o trap possua mais de um co-mando cmd associado. Cada um dos cmd pode ser também uma função interna, uma externa ououtro script.

Para entender o uso de aspas (") e apóstrofos (') vamos recorrer a um exemplo que trataum fragmento de um script que faz um ftp para uma máquina remota ($RemoComp), na qual ousuário é $Fulano, sua senha é $Segredo e vai transmitir o arquivo contido em $Arq. Suponhaainda que estas quatro variáveis foram recebidas em uma rotina anterior de leitura e que estescript é muito usado por diversas pessoas da instalação. Vejamos este trecho de código:

ftp -ivn $RemoComp � FimFTP � /tmp/$ $ 2� /tmp/$ $user $Fulano $Segredobinaryget $ArqFimFTP

Repare que, tanto as saídas do dos diálogos do ftp, como os erros encontrados, estão sendoredirecionados para /tmp/$ $, o que é uma construção bastante normal para arquivos temporá-rios usados em scripts com mais de um usuário, porque $ $ é a variável que contém o númerodo processo (PID), que é único, e com este tipo de construção evita-se que dois ou mais usuáriosdisputem 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 início, como um de seus primeiros comandos, fazer:

trap "rm -f /tmp/$ $ ; exit"0 1 2 3 15

Desta forma, caso houvesse uma interrupção brusca (sinais 1, 2, 3 ou 15) antes do programaencerrar (no exit dentro do comando trap), ou um fim normal (sinal 0), o arquivo /tmp/$ $ seriaremovido.

Caso na linha de comandos do trap não houvesse a instrução exit, ao final da execução destalinha o fluxo do programa retornaria ao ponto em que estava quando recebeu o sinal que originoua execução deste trap.

Este trap poderia ser subdividido, ficando da seguinte forma:

trap "rm -f /tmp/$ $"0trap "exit"1 2 3 15

Assim ao receber um dos sinais o programa terminaria, e ao terminar, geraria um sinal 0, queremoveria o arquivo. Caso seu fim seja normal, o sinal também será gerado e o rm será execu-tado.

Note também que o Shell pesquisa a linha de comandos uma vez quanto o trap é interpre-

128

Page 130: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

tado (e é por isso que é usual colocá-lo no início do programa) e novamente quando um dossinais listados é recebido. Então, no último exemplo, o valor de $ $ será substituído no momentoque o comando trap foi lido da primeira vez, já que as aspas (") não protegem o cifrão ($) dainterpretação do Shell.

Se você desejasse que a substituição fosse realizada somente quando recebesse o sinal, o co-mando deveria ser colocado entre apóstrofos ('). Assim, na primeira interpretação do trap, oShell não veria o cifrão ($), porém os apóstrofos (') seriam removidos e finalmente o Shell pode-ria substituir o valor da variável. 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 execução pelo primeiro, e por serem dois pro-cessos, terão dois PID distintos.

• 1º Caso: O ftp encontra-se em script1Neste caso, o argumento do comando trap deveria vir entre aspas (") porque caso ocor-resse uma interrupção (<CTRL+C> ou <CTRL+\>) no script2, a linha só seria interpretadaneste momento e o PID do script2 seria diferente do encontrado em /tmp/$ $ (não esqueçaque $ $ é a variável que contém o PID do processo ativo);

• 2º Caso: O ftp acima encontra-se em script2Neste caso, o argumento do comando trap deveria estar entre apóstrofos ('), pois caso ainterrupção se desse durante a execução de script1, o arquivo não teria sido criado, casoocorresse durante a execução de script2, o valor de $ $ seria o PID deste processo, quecoincidiria com o de /tmp/$ $.

O comando trap, quando executado sem argumentos, lista os sinais que estão sendo monitora-dos no ambiente, bem como a linha de comando que será executada quando tais sinais foremrecebidos.

Se a linha de comandos do trap for nula (vazia), isto significa que os sinais especificados de-vem ser ignorados quando recebidos. Por exemplo, o comando:

trap "" 2

Especifica que o sinal de interrupção (<CTRL+C>) deve ser ignorado. No caso citado, quandonão se deseja que sua execução seja interrompida. No último exemplo note que o primeiro ar-gumento deve ser especificado para que o sinal seja ignorado, e não é equivalente a escrever oseguinte, cuja finalidade é retornar o sinal 2 ao seu estado padrão (default):

trap 2

Se você ignora um sinal, todos os Subshells irão ignorar este sinal. Portanto, se você especi-fica qual ação deve ser tomada quando receber um sinal, então todos os Subshells irão tambémtomar a ação quando receberem este sinal, ou seja, os sinais são automaticamente exportados.Para o sinal que temos mostrado (sinal 2), isto significa que os Subshells serão encerrados.

129

Page 131: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Suponha que você execute o comando:

trap 2

e então execute um Subshell, que tornará a executar outro script como um Subshell. Se forgerado um sinal de interrupção, este não terá efeito nem sobre o Shell principal nem sobre osSubshell por ele chamados, já que todos eles ignorarão o sinal.

Outra forma de restaurar um sinal ao seu default é fazendo:

trap &#65533; sinal

Em korn shell (ksh) não existe a opção -s do comando read para ler uma senha. O que cos-tumamos fazer é usar o comando stty com a opção -echo que inibe a escrita na tela até que seencontre um stty echo para restaurar esta escrita. Então, se estivéssemos usando o interpreta-dor ksh, a leitura da senha teria que ser feita da seguinte forma:

echo -n "Senha: "stty -echoread Senhastty echo

O problema neste tipo de construção é que caso o operador não soubesse a senha, ele pro-vavelmente daria um <CTRL+C> ou um <CTRL+\> durante a instrução read para descontinuar oprograma e, caso ele agisse desta forma, o que quer que ele escrevesse, não apareceria na telado seu terminal. Para evitar que isso aconteça, o melhor a fazer é:

echo -n "Senha: "trap "stty echoexit"2 3stty -echoread Senhastty echotrap 2 3

Para terminar este assunto, abra uma console gráfica e escreva no prompt de comando o se-guinte:

$ 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 cor-rente. Surpreso? É o Shell orientado a eventos... )

Mais unzinho porque não pude resistir. Agora escreva assim:

$ trap "echo já era"17Em seguida faça:$ sleep 3 &

130

Page 132: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Você acabou de criar um subshell que irá dormir durante três segundos em background. Aofim deste tempo, você receberá a mensagem já era, porque o sinal 17 é emitido a cada vez queum subshell termina a sua execução.

Para devolver estes sinais aos seus defaults, faça:

$ trap 17 28Ou

$ trap ? 17 28

Acabamos de ver mais dois sinais que não são tão importante como os que vimos anteriormente,mas vou registrá-los na tabela a seguir:

.Muito legal este comando, né? Se você descobrir algum caso bacana de uso de sinais, por favorme informe por e-mail porque é muito rara a literatura sobre o assunto.

14.5 O comando getopts

O comando getopts recupera as opções e seus argumentos de uma lista de parâmetros de acordocom a sintaxe POSIX.2, isto é, letras (ou números) após um sinal de menos (-) seguidas ou nãode um argumento; no caso de somente letras (ou números) elas podem ser agrupadas. Vocêdeve usar este comando para "fatiar"opções e argumento passados para o seu script.

Sintaxe:

getopts cadeiadeopcoes nome

A cadeiadeopcoes deve explicitar uma cadeia de caracteres com todas as opções reconheci-das pelo script, assim se ele reconhece as opções -a =-b= e -c, cadeiadeopcoes deve ser abc.Se você deseja que uma opção seja seguida por um argumento, ponha dois-pontos (:) depois daletra, como em a:bc. Isto diz ao getopts que a opção -a tem a forma:

-a argumento

Normalmente um ou mais espaços em branco separam o parâmetro da opção; no entanto, ge-topts também manipula parâmetros que vêm colados à opção como em:

-aargumento

131

Page 133: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

cadeiadeopcoes não pode conter interrogação (?).

O nome constante da linha de sintaxe acima, define uma variável que cada vez que o comandogetopts for executado, receberá a próxima opção dos parâmetros posicionais e a colocará na va-riável nome.

getopts coloca uma interrogação (?) na variável definida em nome se achar uma opção nãodefinida em cadeiadeopcoes ou se não achar o argumento esperado para uma determinada op-ção.

Como já sabemos, cada opção passada por uma linha de comandos tem um índice numérico,assim, a primeira opção estará contida em $1, a segunda em $2, e assim por diante. Quandoo getopts obtém uma opção, ele armazena o índice do próximo parâmetro a ser processado navariável OPTIND.

Quando uma opção tem um argumento associado (indicado pelo : na cadeiadeopcoes), getoptsarmazena o argumento na variável OPTARG. Se uma opção não possui argumento ou o argu-mento esperado não foi encontrado, a variável OPTARG será "matada"(unset).

O comando encerra sua execução quando:

• Encontra um parâmetro que não começa por menos (-);

• O parâmetro especial – marca o fim das opções;

• Quando encontra um erro (por exemplo, uma opção não reconhecida).

O exemplo abaixo é meramente didático, servindo para mostrar, em um pequeno fragmento decódigo 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.

while getopts 'P:h' OPT_LETRAdoecho "getopts fez a variavel OPT_LETRA igual a '$OPT_LETRA'"echo "OPTARG eh '$OPTARG'"doneused_up='expr $OPTIND - 1'echo "Dispensando os primeiros $OPTIND-1 = $used_up argumentos"shift $used_up

132

Page 134: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

echo "O que sobrou da linha de comandos foi '$*'"

Para entendê-lo melhor, vamos executá-lo como está sugerido em seu cabeçalho:

$ getoptst.sh -h -Pimpressora arq1 arq2getopts 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 argumentosO que sobrou da linha de comandos foi 'arq1 arq2'

Desta forma, sem ter muito trabalho, separei todas as opções com seus respectivos argumentos,deixando somente os parâmetros que foram passados pelo operador para posterior tratamento.

Repare que se tivéssemos escrito a linha de comando com o argumento (impressora) separadoda opção (-P), o resultado seria exatamente o mesmo, exceto pelo $OPTIND, já que neste casoele identifica um conjunto de três opções/argumentos e no anterior somente dois. Veja só:

$ getoptst.sh -h -P impressora arq1 arq2getopts 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 argumentosO que sobrou da linha de comandos foi 'arq1 arq2'

Repare, no exemplo a seguir, que se passarmos uma opção inválida, a variável $OPT_LETRAreceberá um ponto-de-interrogação (?) e a $OPTARG será "apagada"(unset).

$ getoptst.sh -f -Pimpressora arq1 arq2 # A opção -f não é valida./getoptst.sh: illegal option � fgetopts 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 argumentosO que sobrou da linha de comandos foi 'arq1 arq2'

- Me diz uma coisa: você não poderia ter usado um case para evitar o getopts?- Poderia sim, mas para que? Os comandos estão aí para serem usados... O exemplo dado foididático, mas imagine um programa que aceitasse muitas opções e seus parâmetros poderiamou não estar colados às opções, suas opções também poderiam ou não estar coladas, ia ser umcase infernal e com getopts é só seguir os passos acima.- É... Vendo desta forma acho que você tem razão. É porque eu já estou meio cansado com tantainformação nova na minha cabeça. Vamos tomar a saideira ou você ainda quer explicar algumaparticularidade do Shell?- Nem um nem outro, eu também já cansei mas hoje não vou tomar a saideira porque estou indo

133

Page 135: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

dar aula na UniRIO, que é a primeira universidade federal que está preparando no uso de Soft-ware Livre, seus alunos do curso de graduação em informática.Mas antes vou te deixar um problema para te encucar: quando você varia o tamanho de uma tela,no seu centro não aparece dinamicamente em vídeo reverso a quantidade de linhas e colunas?Então! Eu quero que você reproduza isso usando a linguagem Shell.- Chico, traz rapidinho a minha conta! Vou contar até um e se você não trouxer eu me mando!

134

Page 136: Shell Script

Capítulo 15

Parte XI

15.1 Named Pipes

Um outro tipo de pipe é o named pipe, que também é chamado de FIFO. FIFO é um acrônimode First In First Out que se refere à propriedade em que a ordem dos bytes entrando no pipe é amesma que a da saída. O name em named pipe é, na verdade, o nome de um arquivo. Os arqui-vos tipo named pipes são exibidos pelo comando ls como qualquer outro, com poucas diferenças,veja:

$ ls -l pipe1prw-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 bitspipe, fun-cionam como um arquivo normal. Nos sistemas mais modernos uma barra vertical (|) colocadoao fim do nome do arquivo, é outra dica, e nos sistemas LINUX, onde a opção de cor está habili-tada, o nome do arquivo é escrito em vermelho por default.

Nos sistemas mais antigos, os named pipes são criados pelo programa mknod, normalmentesituado no diretório /etc.

Nos sistemas mais modernos, a mesma tarefa é feita pelo mkfifo. O programa mkfifo recebeum ou mais nomes como argumento e cria pipes com estes nomes. Por exemplo, para criar umnamed pipe com o nome pipe1, faça:

$ mkfifo pipe1

Como sempre, a melhor forma de mostrar como algo funciona é dando exemplos. Suponhaque nós tenhamos criado o named pipe mostrado anteriormente. Vamos agora trabalhar comduas sessões ou duas consoles virtuais ou uma de cada. Em uma delas faça:

$ ls -l > pipe1

e em outra faça:

$ cat < pipe1

135

Page 137: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Voilá! A saída do comando executado na primeira console foi exibida na segunda. Note quea ordem em que os comandos ocorreram não importa.

Se você prestou atenção, reparou que o primeiro comando executado, parecia ter "pendurado,congelado". Isto acontece porque a outra ponta do pipe ainda não estava conectada, e entãoo sistema operacional suspendeu o primeiro processo até que o segundo "abrisse"o pipe. Paraque um processo que usa pipe não fique em modo de wait, é necessário que em uma ponta dopipe tenha um processo "tagarela"e na outra um "ouvinte"e no exemplo que demos, o lscat era o"orelhão".

Uma aplicação muito útil dos named pipes é permitir que programas sem nenhuma relação pos-sam se comunicar entre si, os named pipes também são usados para sincronizar processos, jáque em um determinado ponto você pode colocar um processo para "ouvir"ou para "falar"em umdeterminado 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 bloqueioem arquivos de forma a evitar perda/corrupção de informações devido a atualizações simultâneas(concorrência). Vejamos exemplos para ilustrar estes casos.

15.2 Sincronização de processos

Suponha que você dispare paralelamente dois programas (processos) cujos diagramas de blocosde suas rotinas são como a figura a seguir:

.Os dois processos são disparados em paralelo e no BLOCO1 do Programa1 as três classifica-ções são disparadas da seguinte maneira:

for Arq in BigFile1 BigFile2 BigFile3do

if sort $Arqthen

Manda=va

136

Page 138: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

elseManda=parebreak

fidoneecho $Manda > pipe1[ $Manda = pare ] &&

{echo Erro durante a classificação dos arquivosexit 1}

...

Assim sendo, o comando if testa cada classificação que está sendo efetuada. Caso ocorra qual-quer problema, as classificações seguintes serão abortadas, uma mensagem contendo a cadeiapare é enviada pelo pipe1 e programa1 é descontinuado com um fim anormal.

Enquanto o Programa1 executava o seu primeiro bloco (as classificações) o Programa2 executavao seu BLOCO1, processando as suas rotinas de abertura e menu paralelamente ao Programa1,ganhando desta forma um bom intervalo de tempo.

O fragmento de código do Programa2 a seguir, mostra a transição do seu BLOCO1BLOCO2:

OK='cat pipe1'if [ $OK = va ]then

...Rotina de impressão...

else # Recebeu "pare"em OKexit 1

fi

Após a execução de seu primeiro bloco, o Programa2 passará a "ouvir"o pipe1, ficando paradoaté que as classificações do Programa1 terminem, testando a seguir a mensagem passada pelopipe1 para decidir se os arquivos estão íntegros para serem impressos, ou se o programa deveráser descontinuado. Desta forma é possível disparar programas de forma assíncrona e sincronizá-los quando necessário, ganhando bastante tempo de processamento.

15.3 Bloqueio de arquivos

Suponha que você escreveu uma CGI (Common Gateway Interface) em Shell para contar quantoshits recebe uma determinada URL e a rotina de contagem está da seguinte maneira:

Hits="$(cat page.hits 2> /dev/null)"|| Hits=0echo $((Hits=Hits++)) > page.hits

137

Page 139: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Desta forma se a página receber dois ou mais acessos concorrentes, um ou mais poderá(ão)ser perdido(s), basta que o segundo acesso seja feito após a leitura da arquivo page.hits e antesda sua gravação, isto é, basta que o segundo acesso seja feito após o primeiro ter executado aprimeira linha do script e antes de executar a segunda.

Então o que fazer? Para resolver o problema de concorrência vamos utilizar um named pipe.Criamos o seguinte script que será o daemon que receberá todos os pedidos para incrementaro contador. Note que ele vai ser usado por qualquer página no nosso site que precise de umcontador.

$ cat contahits.sh#!/bin/bash

PIPE="/tmp/pipe_contador"# arquivo named pipe# dir onde serao colocados os arquivos contadores de cada paginaDIR="/var/www/contador"

[ -p "$PIPE"] || mkfifo "$PIPE"

while :dofor URL in $(cat < $PIPE)doFILE="$DIR/$(echo $URL | sed 's,.*/�')"# OBS1: no sed acima, como precisava procurar# uma barra,usamos vírgula como separador.# OBS2: quando rodar como daemon comente a proxima linhaecho "arquivo = $FILE"

n="$(cat $FILE 2> /dev/null)"|| n=0echo $((n=n+1)) > "$FILE"donedone

Como só este script altera os arquivos, não existe problema de concorrência.

Este script será um daemon, isto é, rodará em background. Quando uma página sofrer umacesso, 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 página que quisermos adicionar o contador acrescentamos a seguintelinha:

<!�#exec cmd="echo$REQUEST_URI > /tmp/pipe_contador-->

Note que a variável$REQUEST_URI contém o nome do arquivo que o navegador (browser) requisitou.

138

Page 140: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Este último exemplo, é fruto de uma idéia que troquei com o amigo e mestre em Shell, ThobiasSalazar Trevisan que escreveu o script e colocou-o em sua excelente URL. Aconselho a todosque 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 mos-trar um uso diferente a partir de agora.

15.4 Substituição de processos

Acabei de mostrar um monte de dicas sobre named pipes, agora vou mostrar que o Shell tam-bém usa os named pipes de uma maneira bastante singular, que é a substituição de processos(process substitution). Uma substituição de processos ocorre quando você põe um comando ouum pipeline de comandos entre parênteses e um < ou um > grudado na frente do parêntese daesquerda. Por exemplo, teclando-se o comando: $ cat <(ls -l)

Resultará no comando ls -l executado em um subshell como é normal (por estar entre parên-teses), porém redirecionará a saída para um named pipeShell cria, nomeia e depois remove.Então o cat terá um nome de arquivo válido para ler (que será este named pipe e cujo dispositivológico associado é /dev/fd/63), e teremos a mesma saída que a gerada pela listagem do ls -l,porém dando um ou mais passos que o usual, isto é, mais onerosa para o computador.

Como poderemos constatar isso? Fácil... 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é? Então suponha que vocêtenha 2 diretórios: dir e dir.bkp e deseja saber se os dois estão iguais (aquela velha dúvida: seráque meu backup está atualizado?). Basta comparar os dados dos arquivos dos diretórios com ocomando 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 comparação foi efetuada em todas as linhas de todos os arquivos de am-bos os diretórios. Para acelerar o processo, poderíamos compara somente a listagem longa deambos os diretórios, pois qualquer modificação que um arquivo sofra, é mostrada na data/horade alteração e/ou no tamanho do arquivo. Veja como ficaria:

$ cmp <(ls -l dir) <(ls -l dir.bkp) >/dev/null || echo backup furado

139

Page 141: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Este é um exemplo meramente didático, mas são tantos os comandos que produzem mais deuma linha de saída, que serve como guia para outros. Eu quero gerar uma listagem dos meusarquivos, numerando-os e ao final dar o total de arquivos do diretório corrente:

while read arqdo((i++)) # assim nao eh necessario inicializar iecho "$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 comandowhile, a forma mais comum de resolver esse problema seria:

ls | while read arqdo((i++)) # assim nao eh necessario inicializar iecho "$i: $arq"doneecho "No diretorio corrente ('pwd') existem $i arquivos"

Quando executasse o script, pareceria estar tudo certo, porém no comando echodone, vocêverá que o valor de $i foi perdido. Isso deve-se ao fato desta variável estar sendo incrementadaem um subshell criado pelo pipe (|) e que terminou no comando done, levando com ele todas asvariáveis criadas no seu interior e as alterações feitas em todas as variáveis, inclusive as criadasexternamente.

Somente para te mostrar que uma variável criada fora do subshell e alterada em seu interiorperde as alterações feitas ao seu final, execute o script a seguir:

#!/bin/bashLIST= # Criada no shell principalls | while read FILE # Inicio do subshelldoLIST="$FILE $LIST"# Alterada dentro do subshelldone # Fim do subshellecho :$LIST:

Ao final da execução você verá que aperecerão apenas dois dois-pontos (::). Mas no início desteexemplo eu disse que era meramente didático porque existem formas melhores de fazer a mesmatarefa. Veja só estas duas:

$ ls | ln

ou então, usando a própria substituição de processos:

$ cat -n <(ls)

140

Page 142: Shell Script

CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF

Um último exemplo: você deseja comparar arq1 e arq2 usando o comando comm, mas estecomando necessita que os arquivos estejam classificados. Então a melhor forma de proceder é:

$ comm <(sort arq1) <(sort arq2)

Esta forma evita que você faça as seguintes operações:

$ 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 .À saúde de todos nós: Tim, Tim.

- Chico, fecha a minha conta porque vou mudar de botequim.Não se esqueça, qualquer dúvida ou falta de companhia para um chope ou até para falar maldos políticos é só mandar um e-mail para [email protected]. Vou aproveitar também paramandar o meu jabá: diga para os amigos que quem estiver afim de fazer um curso porreta deprogramação em Shell que mande um e-mail para [email protected] para informar-se.

Valeu!

141