Shell Script
Versão 1.0.0
Sumário
I Sobre essa Apostila 4
II Informações Básicas 6
III GNU Free Documentation License 11
IV Shell Script 20
1 O que é Shell Script 21
2 Plano de ensino 22
2.1 Objetivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.2 Público Alvo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.3 Pré-requisitos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.4 Descrição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.5 Metodologia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.6 Programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.7 Avaliação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.8 Bibliografia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3 Introdução 25
4 Expressões Regulares 26
4.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.2 Comando grep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.3 Os Metacaracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.4 Metacaracteres quantificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.5 Metacaracteres tipo âncora . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.5.1 Circunflexo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.5.2 Cifrão - o fim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.5.3 Borda - a limítrofe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.6 Outros metacaracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.7 Explicando-os melhor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.7.1 Escape . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.7.2 Ou . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.7.3 Grupo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
4.7.4 Retrovisor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
5 Parte I 35
5.1 Diálogo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.2 O ambiente Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5.3 O ambiente Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
5.4 Uma rápida passagem nos principais sabores de Shell . . . . . . . . . . . . . . . . . 36
5.5 Explicando o funcionamento do Shell . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5.6 Caracteres para remoção do significado . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.7 Caracteres de redirecionamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.7.1 Caracteres de redirecionamento . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.7.2 Redirecionamento da Saída Padrão . . . . . . . . . . . . . . . . . . . . . . . 40
5.7.3 Redirecionamento da Saída de erro Padrão . . . . . . . . . . . . . . . . . . . 41
5.7.4 Redirecionamento da Entrada Padrão . . . . . . . . . . . . . . . . . . . . . . 42
5.8 Redirecionamento de Comandos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.9 Caracteres de Ambiente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
6 Parte II 48
6.1 Diálogo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
6.2 Eu fico com o grep, você com a gripe . . . . . . . . . . . . . . . . . . . . . . . . . . 48
6.3 A família grep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
6.4 Exemplos da família grep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
6.5 Vamos montar uma cdteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
6.6 Passando parâmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
6.7 Macetes paramétricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
7 Parte III 59
7.1 Trabalhando com cadeias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
7.2 O comando cut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
7.2.1 O comando cut a opção -c . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
7.2.2 O comando cut a opção -f . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
7.3 O comando tr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
7.3.1 Trocando caracteres com tr . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
7.3.2 Removendo caracteres com tr . . . . . . . . . . . . . . . . . . . . . . . . . . 63
7.3.3 Xpremendo com tr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
7.3.4 O Comando if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
8 Parte IV 69
8.1 Diálogo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
8.2 O comando Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
8.3 Continuação do comando test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
8.4 Encolheram o comando condicional . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
8.5 E tome de test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
8.6 Acaso casa com case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
2
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
9 Parte V 81
9.1 Comandos de Loop (ou laço) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
9.2 O Comando for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
9.2.1 Primeira sintaxe do comando for . . . . . . . . . . . . . . . . . . . . . . . . . 81
9.2.2 Segunda sintaxe do comando for . . . . . . . . . . . . . . . . . . . . . . . . . 86
9.2.3 Terceira sintaxe do comando for . . . . . . . . . . . . . . . . . . . . . . . . . 88
10 Parte VI 90
10.1 Um pouco mais de for e matemática . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
10.2 O Comando while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
10.3 O Comando Until . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
10.4 Atalhos no loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
11 Parte VII 101
11.1 O comando tput . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
11.2 E agora podemos ler os dados na tela . . . . . . . . . . . . . . . . . . . . . . . . . . 103
11.3 Vamos ler arquivos? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
12 Parte VIII 112
12.1 Funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
12.2 O comando source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
13 Parte IX 122
13.1 Envenenando a escrita . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
13.2 Principais Variáveis do Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
13.3 Expansão de parâmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
14 Parte X 132
14.1 O comando eval . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
14.2 Sinais de Processos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
14.3 Sinais assassinos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
14.4 O trap não atrapalha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
14.5 O comando getopts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
15 Parte XI 144
15.1 Named Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
15.2 Sincronização de processos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
15.3 Bloqueio de arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
15.4 Substituição de processos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
3
Parte I
Sobre essa Apostila
4
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 (http://www.cdtc.org.br.)
O formato original deste material bem como sua atualização está disponível dentro da licença
GNU Free Documentation License, cujo teor integral encontra-se aqui reproduzido na seção de
mesmo nome, tendo inclusive uma versão traduzida (não oficial).
A revisão e alteração vem sendo realizada pelo CDTC (suporte@cdtc.org.br) desde outubro
de 2006. Críticas e sugestões construtivas serão bem-vindas a qualquer hora.
Autores
A autoria deste é de responsabilidade de Waldemar Silva Júnior (waldemar@cdtc.org.br).
O texto original faz parte do projeto Centro de Difusão de Tecnologia e Conhecimento que
vêm sendo realizado pelo ITI (Instituto Nacional de Tecnologia da Informação) em conjunto com
outros parceiros institucionais, e com as universidades federais brasileiras que tem produzido e
utilizado Software Livre apoiando inclusive a comunidade Free Software junto a outras entidades
no país.
Informações adicionais podem ser obtidas através do email ouvidoria@cdtc.org.br, ou da
home page da entidade, através 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 responsabilizam
direta ou indiretamente por qualquer prejuízo oriundo da utilização do material aqui contido.
Licença
Copyright ©2006, Instituto Nacional de Tecnologia da Informação (cdtc@iti.gov.br) .
Permission is granted to copy, distribute and/or modify this document under the terms
of the GNU Free Documentation License, Version 1.1 or any later version published by
the 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 Documentation
License.
5
Parte II
Informações Básicas
6
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 do
desenvolvimento 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 e
de código fonte aberto, identificando e mobilizando grupos de formadores de opinião dentre os
servidores públicos e agentes políticos da União Federal, estimulando e incentivando o mercado
nacional a adotar novos modelos de negócio da tecnologia da informação e de novos negócios
de comunicação com base em software não-proprietário e de código fonte aberto, oferecendo
treinamento 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 como
incentivadores e defensores dos 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 de
produtos de software não proprietários e do 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ê comece
seu 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 o
roteiro acima.
Licença
Copyright ©2006, Instituto Nacional de Tecnologia da Informação (cdtc@iti.gov.br).
7
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
É dada permissão para copiar, distribuir e/ou modificar este documento sob os termos
da Licença de Documentação Livre GNU, Versão 1.1 ou qualquer versão posterior
públicada pela Free Software Foundation; com o Capitulo Invariante SOBRE ESSA
APOSTILA. 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 seus
colegas de turma respeitando-os e se fazendo ser respeitado pelos mesmos;
• 5. Organização pessoal: planejar e organizar tudo é fundamental para facilitar a sua revisão
e a sua recuperação de materiais;
• 6. Vontade para realizar as atividades no tempo correto: anotar todas as suas obrigações e
realizá-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, aprendizagens
e 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ão
controla 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ções
que sejam pertinentes ao curso (avisos, notícias). As mensagens postadas nele são enviadas a
8
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
todos participantes. Assim, se o monitor ou algum outro participante tiver uma informação que
interesse ao grupo, favor postá-la aqui.
Porém, se o que você deseja é resolver alguma dúvida ou discutir algum tópico específico do
curso. É recomendado que você faça uso do Fórum de dúvidas gerais que lhe dá recursos mais
efetivos para esta prática.
. O fórum de dúvidas gerais tem por objetivo disponibilizar um meio fácil, rápido e interativo
para solucionar suas dúvidas e trocar experiências. As mensagens postadas nele são enviadas
a todos participantes do curso. Assim, fica muito mais fácil obter respostas, já que todos podem
ajudar.
Se você receber uma mensagem com algum tópico que saiba responder, não se preocupe com a
formalizaçã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 podem
ser 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, por
pessoas 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 das
ferramentas 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 respectivos
valores e atitudes, incentiva mas é honesto, imparcial, amável, positivo, respeitador, aceita as
idéias dos estudantes, é paciente, pessoal, tolerante, apreciativo, compreensivo e pronto a ajudar.
9
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
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 tutor
ou instrutor:
• fornece explicações claras acerca do que ele espera e do estilo de classificação que irá
utilizar;
• 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 um
reparo de um estudante: ’os comentários deixam-nos com uma sensação de crítica, de
ameaç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
Parte III
GNU Free Documentation License
11
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
(Traduzido pelo João S. O. Bueno através do CIPSGA em 2001)
Esta é uma tradução não oficial da Licença de Documentação Livre GNU em Português Brasi-
leiro. Ela não é publicada pela Free Software Foundation, e não se aplica legalmente a distribuição
de textos que usem a GFDL - apenas o texto original em Inglês da GNU FDL faz isso. Entretanto,
nós esperamos que esta tradução ajude falantes de português a entenderem melhor a GFDL.
This is an unofficial translation of the GNU General Documentation License into Brazilian Por-
tuguese. It was not published by the Free Software Foundation, and does not legally state the
distribution terms for software that uses the GFDL–only the original English text of the GFDL does
that. However, we hope that this translation will help Portuguese speakers understand the GFDL
better.
Licença de Documentação Livre GNU Versão 1.1, Março de 2000
Copyright (C) 2000 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
É permitido a qualquer um copiar e distribuir cópias exatas deste documento de licença, mas
não é permitido alterá-lo.
INTRODUÇÃO
O propósito desta Licença é deixar um manual, livro-texto ou outro documento escrito "livre"no
sentido de liberdade: assegurar a qualquer um a efetiva liberdade de copiá-lo ou redistribui-lo,
com ou sem modificações, comercialmente ou não. Secundariamente, esta Licença mantém
para o autor e editor uma forma de ter crédito por seu trabalho, sem ser considerado responsável
pelas modificações feitas por terceiros.
Esta Licença é um tipo de "copyleft"("direitos revertidos"), o que significa que derivações do
documento precisam ser livres no mesmo sentido. Ela complementa a GNU Licença Pública Ge-
ral (GNU GPL), que é um copyleft para software livre.
Nós fizemos esta Licença para que seja usada em manuais de software livre, por que software
livre precisa de documentação livre: um programa livre deve ser acompanhado de manuais que
provenham as mesmas liberdades que o software possui. Mas esta Licença não está restrita a
manuais de software; ela pode ser usada para qualquer trabalho em texto, independentemente
do assunto ou se ele é publicado como um livro impresso. Nós recomendamos esta Licença prin-
cipalmente para trabalhos cujo propósito seja de introdução ou referência.
APLICABILIDADE E DEFINIÇÕES
Esta Licença se aplica a qualquer manual ou outro texto que contenha uma nota colocada pelo
detentor dos direitos autorais dizendo que ele pode ser distribuído sob os termos desta Licença.
O "Documento"abaixo se refere a qualquer manual ou texto. Qualquer pessoa do público é um
12
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
licenciado e é referida como "você".
Uma "Versão Modificada"do Documento se refere a qualquer trabalho contendo o documento
ou uma parte dele, quer copiada exatamente, quer com modificações e/ou traduzida em outra
língua.
Uma "Seção Secundária"é um apêndice ou uma seção inicial do Documento que trata ex-
clusivamente da relação dos editores ou dos autores do Documento com o assunto geral do
Documento (ou assuntos relacionados) e não contém nada que poderia ser incluído diretamente
nesse assunto geral (Por exemplo, se o Documento é em parte um livro texto de matemática, a
Seção Secundária pode não explicar nada de matemática).
Essa relação poderia ser uma questão de ligação histórica com o assunto, ou matérias relaci-
onadas, ou de posições legais, comerciais, filosóficas, éticas ou políticas relacionadas ao mesmo.
As "Seções Invariantes"são certas Seções Secundárias cujos títulos são designados, como
sendo de Seções Invariantes, na nota que diz que o Documento é publicado sob esta Licença.
Os "Textos de Capa"são certos trechos curtos de texto que são listados, como Textos de Capa
Frontal ou Textos da Quarta Capa, na nota que diz que o texto é publicado sob esta Licença.
Uma cópia "Transparente"do Documento significa uma cópia que pode ser lida automatica-
mente, representada num formato cuja especificação esteja disponível ao público geral, cujos
conteúdos possam ser vistos e editados diretamente e sem mecanismos especiais com editores
de texto genéricos ou (para imagens compostas de pixels) programas de pintura genéricos ou
(para desenhos) por algum editor de desenhos grandemente difundido, e que seja passível de
servir como entrada a formatadores de texto ou para tradução automática para uma variedade
de formatos que sirvam de entrada para formatadores de texto. Uma cópia feita em um formato
de arquivo outrossim Transparente cuja constituição tenha sido projetada para atrapalhar ou de-
sencorajar modificações subsequentes pelos leitores não é Transparente. Uma cópia que não é
"Transparente"é chamada de "Opaca".
Exemplos de formatos que podem ser usados para cópias Transparentes incluem ASCII sim-
ples sem marcações, formato de entrada do Texinfo, formato de entrada do LaTex, SGML ou XML
usando uma DTD disponibilizada publicamente, e HTML simples, compatível com os padrões, e
projetado para ser modificado por pessoas. Formatos opacos incluem PostScript, PDF, formatos
proprietários que podem ser lidos e editados apenas com processadores de texto proprietários,
SGML ou XML para os quais a DTD e/ou ferramentas de processamento e edição não estejam
disponíveis para o público, e HTML gerado automaticamente por alguns editores de texto com
finalidade apenas de saída.
A "Página do Título"significa, para um livro impresso, a página do título propriamente dita,
mais quaisquer páginas subsequentes quantas forem necessárias para conter, de forma legível,
o material que esta Licença requer que apareça na página do título. Para trabalhos que não
tenham uma página do título, "Página do Título"significa o texto próximo da aparição mais proe-
minente do título do trabalho, precedendo o início do corpo do texto.
13
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
FAZENDO CÓPIAS EXATAS
Você pode copiar e distribuir o Documento em qualquer meio, de forma comercial ou não
comercial, desde que esta Licença, as notas de copyright, e a nota de licença dizendo que esta
Licença se aplica ao documento estejam reproduzidas em todas as cópias, e que você não acres-
cente nenhuma outra condição, quaisquer que sejam, às desta Licença.
Você não pode usar medidas técnicas para obstruir ou controlar a leitura ou confecção de
cópias subsequentes das cópias que você fizer ou distribuir. Entretanto, você pode aceitar com-
pensação em troca de cópias. Se você distribuir uma quantidade grande o suficiente de cópias,
você também precisa respeitar as condições da seção 3.
Você também pode emprestar cópias, sob as mesmas condições colocadas acima, e também
pode exibir cópias publicamente.
FAZENDO CÓPIAS EM QUANTIDADE
Se você publicar cópias do Documento em número maior que 100, e a nota de licença do
Documento obrigar Textos de Capa, você precisará incluir as cópias em capas que tragam, clara
e legivelmente, todos esses Textos de Capa: Textos de Capa da Frente na capa da frente, e
Textos da Quarta Capa na capa de trás. Ambas as capas também precisam identificar clara e
legivelmente você como o editor dessas cópias. A capa da frente precisa apresentar o título com-
pleto com todas as palavras do título igualmente proeminentes e visíveis. Você pode adicionar
outros materiais às capas. Fazer cópias com modificações limitadas às capas, tanto quanto estas
preservem o título do documento e satisfaçam a essas condições, pode ser tratado como cópia
exata em outros aspectos.
Se os textos requeridos em qualquer das capas for muito volumoso para caber de forma
legível, você deve colocar os primeiros (tantos quantos couberem de forma razoável) na capa
verdadeira, e continuar os outros nas páginas adjacentes.
Se você publicar ou distribuir cópias Opacas do Documento em número maior que 100, você
precisa ou incluir uma cópia Transparente que possa ser lida automaticamente com cada cópia
Opaca, ou informar, em ou com, cada cópia Opaca a localização de uma cópia Transparente
completa do Documento acessível publicamente em uma rede de computadores, à qual o público
usuário de redes tenha acesso a download gratuito e anônimo utilizando padrões públicos de
protocolos de rede. Se você utilizar o segundo método, você precisará tomar cuidados razoavel-
mente prudentes, quando iniciar a distribuição de cópias Opacas em quantidade, para assegurar
que esta cópia Transparente vai permanecer acessível desta forma na localização especificada
por pelo menos um ano depois da última vez em que você distribuir uma cópia Opaca (direta-
mente ou através de seus agentes ou distribuidores) daquela edição para o público.
É pedido, mas não é obrigatório, que você contate os autores do Documento bem antes de
redistribuir qualquer grande número de cópias, para lhes dar uma oportunidade de prover você
com uma versão atualizada do Documento.
14
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
MODIFICAÇÕES
Você pode copiar e distribuir uma Versão Modificada do Documento sob as condições das se-
ções 2 e 3 acima, desde que você publique a Versão Modificada estritamente sob esta Licença,
com a Versão Modificada tomando o papel do Documento, de forma a licenciar a distribuição
e modificação da Versão Modificada para quem quer que possua uma cópia da mesma. Além
disso, você precisa fazer o seguinte na versão modificada:
A. Usar na Página de Título (e nas capas, se houver alguma) um título distinto daquele do Do-
cumento, e daqueles de versões anteriores (que deveriam, se houvesse algum, estarem listados
na seção "Histórico do Documento"). Você pode usar o mesmo título de uma versão anterior se
o editor original daquela versão lhe der permissão;
B. Listar na Página de Título, como autores, uma ou mais das pessoas ou entidades responsá-
veis pela autoria das modificações na Versão Modificada, conjuntamente com pelo menos cinco
dos autores principais do Documento (todos os seus autores principais, se ele tiver menos que
cinco);
C. Colocar na Página de Título o nome do editor da Versão Modificada, como o editor;
D. Preservar todas as notas de copyright do Documento;
E. Adicionar uma nota de copyright apropriada para suas próprias modificações adjacente às
outras notas de copyright;
F. Incluir, imediatamente depois das notas de copyright, uma nota de licença dando ao público
o direito de usar a Versão Modificada sob os termos desta Licença, na forma mostrada no tópico
abaixo;
G. Preservar nessa nota de licença as listas completas das Seções Invariantes e os Textos de
Capa requeridos dados na nota de licença do Documento;
H. Incluir uma cópia inalterada desta Licença;
I. Preservar a seção entitulada "Histórico", e seu título, e adicionar à mesma um item dizendo
pelo menos o título, ano, novos autores e editor da Versão Modificada como dados na Página de
Título. Se não houver uma sessão denominada "Histórico"no Documento, criar uma dizendo o
título, ano, autores, e editor do Documento como dados em sua Página de Título, então adicionar
um item descrevendo a Versão Modificada, tal como descrito na sentença anterior;
J. Preservar o endereço de rede, se algum, dado no Documento para acesso público a uma
cópia Transparente do Documento, e da mesma forma, as localizações de rede dadas no Docu-
mento para as versões anteriores em que ele foi baseado. Elas podem ser colocadas na seção
"Histórico". Você pode omitir uma localização na rede para um trabalho que tenha sido publicado
pelo menos quatro anos antes do Documento, ou se o editor original da versão a que ela se refira
der sua permissão;
K. Em qualquer seção entitulada "Agradecimentos"ou "Dedicatórias", preservar o título da
15
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
seção e preservar a seção em toda substância e fim de cada um dos agradecimentos de contri-
buidores e/ou dedicatórias dados;
L. Preservar todas as Seções Invariantes do Documento, inalteradas em seus textos ou em
seus títulos. Números de seção ou equivalentes não são considerados parte dos títulos da seção;
M. Apagar qualquer seção entitulada "Endossos". Tal sessão não pode ser incluída na Versão
Modificada;
N. Não reentitular qualquer seção existente com o título "Endossos"ou com qualquer outro
título dado a uma Seção Invariante.
Se a Versão Modificada incluir novas seções iniciais ou apêndices que se qualifiquem como
Seções Secundárias e não contenham nenhum material copiado do Documento, você pode optar
por designar alguma ou todas aquelas seções como invariantes. Para fazer isso, adicione seus
títulos à lista de Seções Invariantes na nota de licença da Versão Modificada. Esses títulos preci-
sam ser diferentes de qualquer outro título de seção.
Você pode adicionar uma seção entitulada "Endossos", desde que ela não contenha qual-
quer coisa além de endossos da sua Versão Modificada por várias pessoas ou entidades - por
exemplo, declarações de revisores ou de que o texto foi aprovado por uma organização como a
definição oficial de um padrão.
Você pode adicionar uma passagem de até cinco palavras como um Texto de Capa da Frente
, e uma passagem de até 25 palavras como um Texto de Quarta Capa, ao final da lista de Textos
de Capa na Versão Modificada. Somente uma passagem de Texto da Capa da Frente e uma de
Texto da Quarta Capa podem ser adicionados por (ou por acordos feitos por) qualquer entidade.
Se o Documento já incluir um texto de capa para a mesma capa, adicionado previamente por
você ou por acordo feito com alguma entidade para a qual você esteja agindo, você não pode
adicionar um outro; mas você pode trocar o antigo, com permissão explícita do editor anterior que
adicionou a passagem antiga.
O(s) autor(es) e editor(es) do Documento não dão permissão por esta Licença para que seus
nomes sejam usados para publicidade ou para assegurar ou implicar endossamento de qualquer
Versão Modificada.
COMBINANDO DOCUMENTOS
Você pode combinar o Documento com outros documentos publicados sob esta Licença, sob
os termos definidos na seção 4 acima para versões modificadas, desde que você inclua na com-
binação todas as Seções Invariantes de todos os documentos originais, sem modificações, e liste
todas elas como Seções Invariantes de seu trabalho combinado em sua nota de licença.
O trabalho combinado precisa conter apenas uma cópia desta Licença, e Seções Invariantes
Idênticas com multiplas ocorrências podem ser substituídas por apenas uma cópia. Se houver
múltiplas Seções Invariantes com o mesmo nome mas com conteúdos distintos, faça o título de
16
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
cada seção único adicionando ao final do mesmo, em parênteses, o nome do autor ou editor
origianl daquela seção, se for conhecido, ou um número que seja único. Faça o mesmo ajuste
nos títulos de seção na lista de Seções Invariantes nota de licença do trabalho combinado.
Na combinação, você precisa combinar quaisquer seções entituladas "Histórico"dos diver-
sos documentos originais, formando uma seção entitulada "Histórico"; da mesma forma combine
quaisquer seções entituladas "Agradecimentos", ou "Dedicatórias". Você precisa apagar todas as
seções entituladas como "Endosso".
COLETÂNEAS DE DOCUMENTOS
Você pode fazer uma coletânea consitindo do Documento e outros documentos publicados
sob esta Licença, e substituir as cópias individuais desta Licença nos vários documentos com
uma única cópia incluida na coletânea, desde que você siga as regras desta Licença para cópia
exata de cada um dos Documentos em todos os outros aspectos.
Você pode extrair um único documento de tal coletânea, e distribuí-lo individualmente sob
esta Licença, desde que você insira uma cópia desta Licença no documento extraído, e siga esta
Licença em todos os outros aspectos relacionados à cópia exata daquele documento.
AGREGAÇÃO COM TRABALHOS INDEPENDENTES
Uma compilação do Documento ou derivados dele com outros trabalhos ou documentos se-
parados e independentes, em um volume ou mídia de distribuição, não conta como uma Ver-
são Modificada do Documento, desde que nenhum copyright de compilação seja reclamado pela
compilação. Tal compilação é chamada um "agregado", e esta Licença não se aplica aos outros
trabalhos auto-contidos compilados junto com o Documento, só por conta de terem sido assim
compilados, e eles não são trabalhos derivados do Documento.
Se o requerido para o Texto de Capa na seção 3 for aplicável a essas cópias do Documento,
então, se o Documento constituir menos de um quarto de todo o agregado, os Textos de Capa
do Documento podem ser colocados em capas adjacentes ao Documento dentro do agregado.
Senão eles precisarão aparecer nas capas de todo o agregado.
TRADUÇÃO
Tradução é considerada como um tipo de modificação, então você pode distribuir traduções
do Documento sob os termos da seção 4. A substituição de Seções Invariantes por traduções
requer uma permissão especial dos detentores do copyright das mesmas, mas você pode incluir
traduções de algumas ou de todas as Seções Invariantes em adição às versões orignais dessas
Seções Invariantes. Você pode incluir uma tradução desta Licença desde que você também in-
clua a versão original em Inglês desta Licença. No caso de discordância entre a tradução e a
17
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
versão original em Inglês desta Licença, a versão original em Inglês prevalecerá.
TÉRMINO
Você não pode copiar, modificar, sublicenciar, ou distribuir o Documento exceto como expres-
samente especificado sob esta Licença. Qualquer outra tentativa de copiar, modificar, sublicen-
ciar, ou distribuir o Documento é nula, e resultará automaticamente no término de seus direitos
sob esta Licença. Entretanto, terceiros que tenham recebido cópias, ou direitos de você sob esta
Licença não terão suas licenças terminadas, tanto quanto esses terceiros permaneçam em total
acordo com esta Licença.
REVISÕES FUTURAS DESTA LICENÇA
A Free Software Foundation pode publicar novas versões revisadas da Licença de Documen-
tação Livre GNU de tempos em tempos. Tais novas versões serão similares em espirito à versão
presente, mas podem diferir em detalhes ao abordarem novos porblemas e preocupações. Veja
http://www.gnu.org/copyleft/.
A cada versão da Licença é dado um número de versão distinto. Se o Documento especificar
que uma versão particular desta Licença "ou qualquer versão posterior"se aplica ao mesmo, você
tem a opção de seguir os termos e condições daquela versão específica, ou de qualquer versão
posterior que tenha sido publicada (não como rascunho) pela Free Software Foundation. Se o
Documento não especificar um número de Versão desta Licença, você pode escolher qualquer
versão já publicada (não como rascunho) pela Free Software Foundation.
ADENDO: Como usar esta Licença para seus documentos
Para usar esta Licença num documento que você escreveu, inclua uma cópia desta Licença
no documento e ponha as seguintes notas de copyright e licenças logo após a página de título:
Copyright (c) ANO SEU NOME.
É dada permissão para copiar, distribuir e/ou modificar este documento sob os termos da Licença
de Documentação Livre GNU, Versão 1.1 ou qualquer versão posterior publicada pela Free Soft-
ware Foundation; com as Seções Invariantes sendo LISTE SEUS TÍTULOS, com os Textos da
Capa da Frente sendo LISTE, e com os Textos da Quarta-Capa sendo LISTE. Uma cópia da li-
cença está inclusa na seção entitulada "Licença de Documentação Livre GNU".
Se você não tiver nenhuma Seção Invariante, escreva "sem Seções Invariantes"ao invés de
dizer quais são invariantes. Se você não tiver Textos de Capa da Frente, escreva "sem Textos de
Capa da Frente"ao invés de "com os Textos de Capa da Frente sendo LISTE"; o mesmo para os
Textos da Quarta Capa.
Se o seu documento contiver exemplos não triviais de código de programas, nós recomenda-
mos a publicação desses exemplos em paralelo sob a sua escolha de licença de software livre,
18
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
tal como a GNU General Public License, para permitir o seu uso em software livre.
19
Parte IV
Shell Script
20
Capítulo 1
O que é Shell Script
Shell é o nome que se dá à linha de comando em modo texto dos sistemas operacionais
Linux e UNIX. E, portanto, os shell scripts são um meio de se juntar uma porção de comandos
shell em um só 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 de sistemas Unix e Linux é mais poderosa.
21
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
em geral.
2.3 Pré-requisitos
Os usuários deverão ser, necessariamente, funcionários públicos e ter conhecimentos básicos
para 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 Moodle
como ferramenta de aprendizagem. O curso tem duração de uma semana e possui um conjunto
de atividades (lições, fóruns, glossários, questionários e outros) que deverão ser executadas de
acordo com as instruções fornecidas. O material didático está disponível on-line de acordo com
as datas pré-estabelecidas em cada tópico.
2.5 Metodologia
O curso está dividido da seguinte maneira:
Duração Descrição do Módulo
1 semana Expressões Regulares
2 semana Ambiente Shell e Comandos de manipulação de caracteres
Todo o material está no formato de lições, e estará disponível ao longo do curso. Ao final de
cada semana do curso será disponibilizada a prova referente ao módulo estudado anteriormente
22
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
que também conterá perguntas sobre os textos indicados. Utilize o material de cada semana e
os exemplos disponibilizados para se preparar para prova.
As lições contêm o conteúdo principal. Elas poderão ser acessadas quantas vezes forem neces-
sárias, desde que estejam 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 seja
menor 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ções
quanto a da avaliação serão consideradas para a nota final. Todos os módulos ficarão visíveis
para 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á ser
enviada ao 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.
2.7 Avaliação
Toda a avaliação será feita on-line.
Aspectos a serem considerados na avaliação:
• Expressões Regulares;
23
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
• 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 e
obtenção do certificado o participante deverá obter nota final maior ou igual a 6.0 de acordo
com 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
24
Capítulo 3
Introdução
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ções
e, ao término delas, realizar algumas avaliações. Todas essas atividades valem pontuação. Para
um 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 que aprender brincando. É interessante que já esteja
instalado o Linux e que deixe o terminal sempre aberto. Assim facilita acompanhar o raciocínio
da leitura. Qualquer dúvida quanto ao comando pode ser resolvida digitando no Shell:
$ man nomedocomando
ou ainda discutindo concosco no fórum. Assim aprendemos juntos a funcionalidade de cada
um.
Bom trabalho!
25
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 a
todas 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]ato
pode 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 ser
praticado.
4.2 Comando grep
Para não precisar listar todo o conteúdo de um arquivo por completo para apenas saber os
dados do usuário "root", pode-se usar o grep para pesquisar e retornar somente a linha dele. O
26
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 sistema
UNIX/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/passwd
root:x:0:0:root:/root:/bin/bash
4.3 Os Metacaracteres
Cada metacaracteres é uma ferramenta que tem uma função específica. Servem para dar
mais poder às pesquisas, informando padrões e posições impossíveis de se especificar usando
somente 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 um
individualmente, 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 importa
com quem seja. Pode ser um número, uma letra, um TAB, um , o que vier ele traça, pois o ponto
casa qualquer coisa.
Exemplos:
"n.o"casaria: não, nao, ...
27
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 lista
Bem mais exigente que o ponto, a lista não casa com qualquer um. Ela sabe exatamente o que
quer, e nada diferente daquilo, a lista casa com quem ela conhece. Toda "lista"(os colchetes e
seu 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 negada
A lista negada é exatamente igual à lista, podendo ter caracteres literais, intervalos e classes
POSIX. 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 os
componentes listados.
Observe que a diferença em sua notação é que o primeiro caractere da lista é um circunflexo, ele
indica que esta é uma lista negada. Então, se [0-9] são números, [^0-9] é qualquer coisa fora
nú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ício
de uma linha. Mas tem de ser alguma coisa. Só porque ela é uma lista negada isso não significa
que 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írgula
ou 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 Metacaracteres quantificadores
Os quantificadores servem para indicar o número de repetições permitidas para a entidade
imediatamente anterior. Essa entidade pode ser um caractere ou metacaractere. Em outras pa-
lavras, eles dizem a quantidade de repetições que o átomo anterior pode ter, quantas vezes ele
pode aparecer.
São eles:
opcional ?
asterisco *
mais +
28
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
chaves
Opcional
É útil para procurar palavras no singular e plural e pode ser tornar opcionais caracteres e me-
tacaracteres.
Exemplos:
Expressão Casa com
Ondas? Onda Ondas
Senadora? 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 menos
uma 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ão
precisas 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 que
isso.
0,1 zero ou 1 (igual ao opcional)
0, zero ou mais (igual ao asterisco)
1, um ou mais (igual ao mais)
3 exatamente
29
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
4.5 Metacaracteres tipo âncora
São aqueles que não casam caracteres ou definem quantidades, ao invés disso eles marcam
uma 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
da lista (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
no final 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"seguida
de um fim de linha.
Esse cifrão é o mesmo caractere que é utilizado para identificar as variáveis do shell, como $PWD
e $HOME. Para evitar possíveis problemas com a expansão de variáveis, é preciso "proteger"a
expressão regular passada ao grep. A proteção é feita colocando-se a ER entre ’aspas simples’
fazendo:
$ grep ’bash$’ /etc/passwd
Veja outros exemplos:
30
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 útil
para casar palavras exatas, e não partes de palavras. Palavra aqui é um conceito que engloba
apenas 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!
• diab - - - dia, melodia, bom-dia!
• bdiab — dia, bom-dia!
4.6 Outros metacaracteres
Vamos ver outros metacaracteres, que têm funções específicas e não relacionadas entre si,
portanto não podem ser agrupados em outra classe fora a tradicional "outros". Mas atenção, isso
não quer dizer que eles são inferiores, pelo contrário, o poder das ERs é multiplicado com seu
uso 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*
31
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-se 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
os parê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,
podemos 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ê: "ou
boa-tarde, ou boa-noite", ou seja "ou isso ou aquilo". Lembre que a lista também é uma espécie
de 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
tem o dom de juntar vários tipos de sujeitos em um mesmo local. Dentro de um grupo podemos
ter um ou mais caracteres, metacarateres e inclusive outros grupos. Como em uma expressão
matemática, os parênteses definem um grupo e seu conteúdo pode ser visto como um bloco na
expressã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ência
limitada 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 que
casamos várias repetições da palavra, como ai, aiai, aiaiai, ... E assim podemos agrupar tudo o
32
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 um
supermercado 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 hiper
temos um trecho per comum aos dois, então podíamos "alternativizar"apenas as diferenças su e
hi:
(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 de
1 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":
http://www.redhat.com/docs/manuals/linux/RHL-7.2-Manual/custom-guide/rescuemode.html ([A-
33
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
Za-z]+)-1
Viu o poder dessa ER? Ela casa palavras repetidas separadas por um traço, como o próprio
quero-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 palavras
repetidas 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 pre-
cisamos usar as bordas para completar nossa ER:
b([A-Za-z]+) 1b
http://www.redhat.com/docs/manuals/linux/RHL-7.2-Manual/custom-guide/rescuemode.html Como
já dito, podemos usar no máximo nove retrovisores. Vamos ver uns exemplos com mais de um
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 vezes nem sabe. O brinde é o
trecho de texto casado pela ER que está no grupo, que fica guardado em um cantinho especial 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ém
como 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 a
ER.
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.
34
Capítulo 5
Parte I
5.1 Diálogo
Diálogo entre 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 operacional
que você tem que dar dez boots por dia e não tem domínio nenhum sobre o que está acontecendo
no seu computador. Mas deixa isso prá lá, vou te explicar o que é Shell e os componentes de sua
família e ao final da explanação você dirá: "Meu Deus do Shell! Porque eu não optei pelo Linux
antes?".
5.2 O ambiente Linux
Para você entender o que é e como funciona o Shell, primeiro será mostrado como funciona
o ambiente em camadas do Linux. Dê uma olhada no gráfico abaixo:
35
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 pelos com-
ponentes físicos do seu computador. Envolvendo esta, vem a camada do kernel que é o cerne do
Linux, seu núcleo, é quem coloca o hardware para funcionar fazendo seu gerenciamento e con-
trole. Os programas e comandos que envolvem o kernel, dele se utilizam para realizar as tarefas
aplicativas para que foram desenvolvidos. Fechando tudo isso vem o Shell que leva este nome
porque em inglês, Shell significa concha, carapaça, isto é, fica entre o usuário e o sistema opera-
cional, de forma que tudo que interage com o sistema operacional, tem que passar pelo seu crivo.
http://www.redhat.com/docs/manuals/linux/RHL-7.2-Manual/custom-guide/rescuemode.html
5.3 O ambiente Shell
Bom já que para chegar ao núcleo do Linux, no seu kernel que é o que interessa a todo apli-
cativo, é necessária a filtragem do Shell, vamos entender como ele funciona de forma a tirar o
máximo proveito das inúmeras facilidades que ele nos oferece.
O Linux por definição é um sistema multiusuário - não podemos nunca esquecer disto - e para per-
mitir o acesso de determinados usuários e barrar a entrada de outros, existe um arquivo chamado
/etc/passwd que além de fornecer dados para esta função de "leão-de-chácara"do Linux, tam-
bé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 receberá ao se "logar"(ARGH!!!).
Lembra que foi falado de Shell, família, irmão? Pois é, vamos começar a entender isto: o Shell,
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 sistema
operacional Unix foram aparecendo. Atualmente existem diversos sabores de Shell, dentre estes
é destacado o sh (Bourne Shell), o ksh (Korn Shell), bash (Bourne Again Shell) e o csh (C Shell).
5.4 Uma rápida passagem 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 de
Standard Shell por ter sido durante vários anos o único e até hoje é o mais utilizado até porque
ele foi portado para todos os ambientes Unix e distros Linux.
Korn Shell (ksh)
Desenvolvido por David Korn, também da Bell Labs, é um superset do sh, isto é, possui todas as
facilidades do sh e a elas agregou muitas outras. A compatibilidade total com o sh vem trazendo
muitos 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
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. C
36
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
Shell (csh)
Desenvolvido por Bill Joy da Berkley University é o Shell mais utilizado em ambientes *BSD e
Xenix. A estruturação de seus comandos é bem similar à da linguagem C. Seu grande pecado foi
ignorar a compatibilidade com o sh, partindo por um caminho próprio.
Além destes Shells existem outros, mas, neste curso, será abordado somente sobre os três pri-
meiros tratando-os genericamente por Shell e assinalando as especificidades de cada um que
porventura hajam.
Atenção: Quando foi dito que o último campo do /etc/passwd informa ao sistema
qual é o Shell que o usuário 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á imediatamente um 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 resolverá vá-
rias coisas de forma a não onerar o kernel com tarefas repetitivas, aliviando-o para tratar assuntos
mais nobres. Como cada usuário possui o seu próprio Shell interpondo-se entre ele e o Linux, é
o Shell quem interpreta os comandos que são teclados e examina as suas sintaxes, passando-os
esmiuçados para execução.
O Shell é um interpretador (ou será intérprete) que traz consigo uma poderosa linguagem com
comandos de alto nível, que permite construção de loops (laços), de tomadas de decisão e de
armazenamento de valores em variáveis, como será mostrado.
Será explicado 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 Comandos
Neste exame, o Shell identifica os caracteres especiais (reservados) que têm significado para
interpretação da linha, logo após verifica se a linha passada é um comando ou uma atribuição.
Comando
Quando 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 esta
tarefa.
Atribuição
Se o Shell encontra dois campos separados por um sinal de igual ()= sem espaços em branco
37
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
entre eles, identifica esta seqüência como uma atribuição.
Exemplos:
$ ls linux
linux
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 = 1000
bash: 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: = e
1000.
Resolução de Redirecionamentos
Após identificar os componentes da linha que você teclou, o Shell parte para a resolução de
redirecionamentos. O Shell tem incorporado ao seu elenco de vantagens o que chamamos de
redirecionamento, que pode ser de entrada (stdin), de saída (stdout) ou dos erros (stderr), con-
forme será explicado a seguir.
Substituição de Variáveis
Neste 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 ele
será 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 kernel
Completadas as tarefas anteriores, o Shell monta a linha de comandos, já com todas as subs-
tituições feitas, chama o kernel para executá-la em um novo Shell (Shell filho), ganhando um
número de processo (PID ou Process Identification) e permanece inativo durante a execução do
programa. Uma vez encerrado este processo (juntamente com o Shell filho), recebe novamente
o controle e, exibindo um prompt, mostra que está pronto para executar outros comandos.
38
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
5.6 Caracteres para remoção do significado
É isso mesmo, quando não se deseja que o Shell interprete um caractere especial, deve-se
"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 existe
o arquivo linux*.
Contrabarra ou Barra Invertida ()
Idêntico aos apóstrofos exceto que a barra invertida inibe a interpretação somente do caractere
que a segue.
Suponha que você, acidentalmente, tenha criado um arquivo chamado * (asterisco) - que alguns
sabores de Unix permitem - e deseja removê-lo. Se você fizesse:
$ rm *
Estaria cometendo um grande erro, pois o rm removeria todos os arquivos do diretório corrente.
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 ($), uma
crase (`), ou uma barra invertida (), estes caracteres serão interpretados pelo Shell.
Não precisa se preocupar, não foi dado 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
39
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 é
chamada Entrada Padrão ou stdin e seu default é o teclado do terminal. Analogamente, a saída
do comando é chamada Saída Padrão ou stdout e seu default é a tela do terminal. Para a tela
també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 (st-
dout). Caso a entrada não seja definida, ele espera os dados da stdin. Como não foi especificada
a entrada, ele está esperando-a pelo teclado (Entrada Padrão) e como também não foi citada a
saída, o que será teclado irá para a tela (Saída Padrão) fazendo desta forma, um programa 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)
seguido do nome do arquivo para o qual se deseja mandar a saída. Vamos transformar o pro-
grama gago em um editor de textos.
$ cat > Arq
O cat continua sem ter a entrada especificada, portanto está aguardando que os dados sejam
teclados, porém a sua saída está sendo desviada para o arquivo Arq. Assim sendo, tudo que
esta sendo teclado esta indo para dentro de Arq, de forma que fizemos o editor de textos mais
curto e ruim do planeta.
Se fizermos, novamente:
$ cat > Arq
Os dados contidos em Arq serão perdidos, já que antes do redirecionamento o ShellArq estava
vazio. Para colocar mais informações no final do arquivo, deveriamos ter feito: criará um $
cat » Arq
Atenção: Como já haviamos lhe dito, o Shell resolve a linha e depois manda o
comando para a execução. Assim, se você redirecionar a saída de um arquivo
40
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
para ele próprio, primeiramente o Shell "esvazia"este arquivo e depois manda o
comando para execução, desta forma você acabou de perder o conteúdo do seu
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, os erros 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
dados ao final de um arquivo, e o segundo redireciona a Saída de Erro Padrão (stderr) para
um 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$$. Para
nã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 isso
não aconteça, deve-se fazer:
$ rm /tmp/seraqueexiste$ $ 2> /dev/null (dois cifrões juntos)
Sobre o exemplo visto, há duas dicas:
Dica 1:
O $ $ (dois cifrões juntos) contêm o PID, isto é, o número do seu processo. Como o Linux
é multiusuário, é bom anexar sempre o $ $ (dois cifrões juntos) ao nome dos
arquivos que 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 o usasse (criando-o então) seria o seu dono e todos os outros ganhariam um erro
quando tentassem gravar algo nele.
Para que você teste a Saída de Erro Padrão direto no prompt do seu Shell, vamos dar mais
um exemplo. Faça:
$ ls naoexiste
bash: naoexiste no such file or directory
$ ls naoexiste 2> arquivodeerros
$
hspace*1cmtextbf$ cat arquivodeerros
bash: naoexiste no such file or directory
Neste exemplo, vimos que quando fizemos um ls em naoexiste, ganhamos uma mensagem
de erro. Após, redirecionarmos a Saída de Erro Padrão para arquivodeerros e executarmos o
41
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
mesmo comando, recebemos somente o prompt na tela. Quando listamos o conteúdo do arquivo
para o qual foi redirecionada a Saída de Erro Padrão, vimos que a mensagem de erro tinha sido
armazenada nele. Faça este teste.
Dica 2:
- Quem é esse tal de /dev/null?
- Em Unix existe um arquivo fantasma. Chama-se /dev/null. Tudo que é mandado para
este arquivo some, assemelha-se a um Buraco Negro. No caso do exemplo, como não nos
interessava guardar a possível mensagem de erro oriunda do comando rm, redirecionamos-
a para este arquivo.
É interessante notar que estes caracteres de redirecionamento são cumulativos, isto é, se no
exemplo 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 para que serve isso? - você irá perguntar.
-Com esse exemplo você entenderá:
Suponha que você queira mandar um email para o seu chefe. Ao invés de sair redigindo o email
direto no prompt da tela de forma a tornar impossível a correção de uma frase anterior onde, sem
querer, escreveu um "nós vai", você edita um arquivo com o conteúdo da mensagem e após umas
quinze verificações sem constatar nenhum erro, decide enviá-lo e para tal, faz:
$ mail chefe < arquivocommailparaochefe
O seu chefe, então, receberá o conteúdo do arquivocommailparaochefe.
Um outro tipo de redirecionamento que o Shell te permite é o chamado here document. Ele é
representado por « (menor menor) e serve para indicar ao Shell que o escopo de um comando
começa na linha seguinte e termina quando encontra uma linha cujo conteúdo seja unicamente o
label que segue o sinal «.
Veja o fragmento de script a seguir, com uma rotina de ftp:
$ ftp -ivn hostremoto « fimftp
user $Usuário $Senha
binary
get arquivoremoto
fimftp
Aqui, temos muitos detalhes interessantes:
1. As opções que foram usadas para o ftp (-ivn) servem para ele ir listando tudo que está acon-
tecendo (-v de verbose), para não perguntar se você tem certeza de que deseja transmitir
42
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
cada arquivo (-i de interactive), e finalmente a opção -n serve para dizer ao ftp para ele
não solicitar o usuário e sua senha, pois esses serão informados pela instrução específica
(user).
2. Quando foi usado o « fimftp, estava dizendo o seguinte para o intérprete: "Shell, não faça
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 resolverá antes do redirecionamento. Mas a grande vantagem
desse tipo de construção é que ela permite que comandos também sejam interpretados dentro
do escopo do here document, o que também contraria o que foi dito. Depois será explicado como
isso funciona. Agora não dá, pois está faltando ferramenta.
1. O comando user é do repertório de instruções do ftp e serve para passar o usuário e a senha
que haviam sido lidos em uma rotina anterior a esse fragmento de código e colocados,
respectivamente, 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 para
saber se está em ASCII, EBCDIC, ...
3. O get arquivoremoto diz ao ftp para pegar esse arquivo em hostremoto e trazê-lo para o
nosso host local. Se fosse para mandar o arquivo, 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 atento
quanto a isso, por que este tipo de erro cria problemas no programador, até que seja de-
tectado. Lembre-se: um label que se preze tem que ter uma linha inteira só para ele.
5.8 Redirecionamento de Comandos
Os redirecionamentos que falamos até aqui sempre se referiam a arquivos, isto é mandavam
para arquivo, recebiam de arquivo, simulavam arquivo local e etc. O que veremos a partir de
agora redireciona a saída de um comando para a entrada de outro, é muito utilizado e ajuda bas-
tante. Seu nome é pipe (que em inglês significa tubo, já que ele encana a saída de um comando
para a entrada de outro) e sua representação é uma barra vertical (|).
http://www.redhat.com/docs/manuals/linux/RHL-7.2-Manual/custom-guide/rescuemode.html $
ls | wc -l
21
O comando ls passou a lista de arquivos para o comando wc, que quando está com a opção
-l conta a quantidade de linhas que recebeu. Desta forma, podemos afirmar categoricamente que
no diretório existiam 21 arquivos.
$ cat /etc/passwd |sort | lp
43
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
Esta linha de comandos manda a listagem do arquivo /etc/passwd para a entrada do comando
sort. Este a classifica e manda-a para o lp que é o gerenciador do spool de impressão.
5.9 Caracteres de Ambiente
Normalmente, ao priorizar uma expressão você a coloca entre parênteses, por causa da arit-
mética é normal pensarmos desta forma. Mas em Shell o que prioriza mesmo são as crases (`)
e não os parênteses. Vamos 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 posso
fazer:
$ who | wc -l
8
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 quero é que ele esteja no meio de uma frase.
Para mandar frases para a tela, uso o comando echo, ficando assim:
$ echo "Existem who | wc -l usuários conectados"
Existem who | wc -l usuários conectados
Não funcionou, e não foi por causa das aspas que eu coloquei, mas sim por que eu teria que
ter executado o who | wc -l antes do echo. Para resolver este problema, tenho que priorizar esta
segunda parte do comando com o uso de crases, fazendo assim:
$ echo "Existem ’who | wc -l’ 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 conectados
Existem 8 usuários conectados
Como foi dito antes, as aspas protegem tudo que está dentro dos seus limites, da interpreta-
ção do Shell. Como para o Shell basta um espaço em branco como separador, os vários espaços
serão trocado por um único após a retirada das aspas.
Quando estiver no Shell, você deve sempre dar um comando em cada linha. Para agrupar co-
mandos em uma mesma linha, terá que separá-los por ponto-e-vírgula(;). Então:
http://www.redhat.com/docs/manuals/linux/RHL-7.2-Manual/custom-guide/rescuemode.html $pwd
; cd /etc; pwd; cd -; pwd
/home/meudir
44
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
/etc/
/home/meudir
Neste exemplo, listamos o nome do diretório corrente com o comando pwd, mudamos para o
diretório /etc, novamente listamos o nome do diretório e, finalmente, voltamos para o diretório
onde estava anteriormente (cd -), listando seu nome. Repare que foi colocado o ponto-e-vírgula
(;) de todas as formas possíveis para mostrar que não importa se existem espaços em branco
antes ou após este caractere.
Finalmente, vamos ver o caso dos parênteses. Veja só o caso a seguir, bem parecido com o
exemplo anterior:
$ (pwd ; cd /etc ; pwd;)
/home/meudir
/etc/
$ pwd
/home/meudir
- Quequeiiisso minha gente? Eu estava no /home/meudir, mudei para o /etc, constatei que
estava neste diretório com o pwd seguinte, e quando o agrupamento de comandos terminou, eu
vi que 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 é que
ele 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ênteses
foram executados, o novo Shell que estava no diretório /etcShell anterior cujo diretório corrente
era /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:
$ mail suporte « FIM
http://www.redhat.com/docs/manuals/linux/RHL-7.2-Manual/custom-guide/rescuemode.html >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
document. Os comandos entre crases (‘) serão priorizados e, portanto, o Shell os executará an-
tes da instrução mail. Quando o suporte receber o e-mail, verá que os comandos date e ls foram
executados imediatamente antes do comando mail, recebendo então uma fotografia do ambiente
no momento em que a correspondência foi enviada.
45
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 conceito
de prompt secundário, ou de continuação de comando que é enviado para a tela quando há uma
quebra 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 vem
sendo utilizada como forma de priorização de execução de comandos, tal qual as crases (‘). São
as 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. Aqui
dentro do Botequim, usarei ambas as formas, indistintamente.
Vejamos novamente o exemplo dado para as crases sob esta nova ótica:
$ echo Existem $(who | grep wc -l) usuários conectados
Existem 8 usuários conectados
Veja só este caso:
$ Arqs=ls
$ echo $Arqs
ls
Neste exemplo, eu fiz uma atribuição (=) e executei uma instrução. O que eu queria era que a
variá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ção
do ls. Para fazer o que desejamos é necessário que eu priorize a execução deste comando em
detrimento 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 colocar
dentro 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:
46
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 May
24 19:43 arq2 -rw-r–r– 1 jneves jneves 1866 Jan 22 2003 arql
- Pô, saiu tudo embolado!
- Pois é cara, como eu já te disse, se você deixar o Shell "ver"os espaços em branco, sempre
que houver diversos espaços juntos, eles serão trocados por apenas um. Para que a listagem
saia 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, vou
lhe explicar uma série de instruções típicas de programação Shell. Tchau! Ahh! Só mais uma
coisinha 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
47
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 final-
mente esta 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 vi nada prático nisso... - Calma rapaz, o que te falei até agora, serve como base ao que
há de vir daqui pra frente. Vamos usar estas ferramentas que vimos para montar programas es-
truturados, 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 em inglês com este nome... - É claro, grep é um acrônimo Global Regular Expres-
sion Print, que usa expressõ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.). Por falar em expressões regulares (ou regexp), o Aurélio Marinho Jargas tem
todas as dicas em sua página (inclusive tutorias) que abordam o tema. Se você está mesmo a
fim de aprender a programar em Shell, Perl, Python, ... Acho bom você ler estes artigos para te
ajudar no que está para vir.
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 "¨para
caracteres como "*"para que o bash não o entenda como um curinga para nomes de arquivos. A
definiçã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 do comando grep. Vejamos: Pesquisando em um arquivo:
$ grep rafael /etc/passwd
Pesquisando em vários arquivos:
$ grep grep *.sh
48
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
Pesquisando na saída de comando:
$ who | grep Pelegrino
No 1º exemplo, o mais simples, procurei a palavra rafael em qualquer lugar do arquivo /etc/passwd.
Se quisesse procurá-la como um login name, isto é, somente no início dos registros deste arquivo,
eu deveria fazer:
$ grep '^rafael’ /etc/passwd
E para que serve este circunflexo e os apóstrofos? O circunflexo (^), se você tivesse lido os
artigos anteriores sobre expressões regulares que te falei, saberia que servem para limitar a
pesquisa ao início de cada linha, e os apóstrofos (’) servem para o Shell não interpretar este
circunflexo, deixando-o passar incólume para o comando grep.
O grep aceita como entrada, a saída de outro comando redirecionado por um pipe (isto é muito
comum em Shell e é um tremendo acelerador de execução de comando já que atua como se
a saída de um programa fosse guardada em disco e o segundo programa lesse este arquivo
gerado), desta forma, no 3º exemplo, o comando who listou as pessoas "logadas"na mesma má-
quina que você (não se esqueça jamais: o Linux é multiusuário) e o grep foi usado para 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
pessoas desconhecem é 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, o
fgrep é melhor, por ser mais rápido;
• O egrep ("e"de extended, extendido) é muito poderoso no uso de expressões regulares. Por
ser o mais lento da família, só deve ser usado quando for necessária a elaboração de uma
expressã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 e
50% 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 comandos
grep 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.
49
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ão
vamos 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
http://www.redhat.com/docs/manuals/linux/RHL-7.2-Manual/custom-guide/rescuemode.html 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. Por
esta 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 doc
drwxr-xr-x 11 root root 4096 Jul 13 18:58 freeciv
drwxr-xr-x 3 root root 4096 Oct 17 2000 gimp
drwxr-xr-x 3 root root 4096 Aug 8 2000 gnome
drwxr-xr-x 2 root root 4096 Aug 8 2000 idl
drwxrwxr-x 14 root root 4096 Jul 13 18:58 locale
drwxrwxr-x 12 root root 4096 Jan 14 2000 lyx
drwxrwxr-x 3 root root 4096 Jan 17 2000 pixmaps
drwxr-xr-x 3 root root 4096 Jul 2 20:30 scribus
drwxrwxr-x 3 root root 4096 Jan 17 2000 sounds
drwxr-xr-x 3 root root 4096 Dec 18 2000 xine
No exemplo que acabamos de ver, o circunflexo (^) serviu para limitar a pesquisa à primeira
posição da saída do ls longo. Os apóstrofos foram colocados para o Shell não "ver"o circunflexo
(^).
50
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
Vamos ver mais um. Sabemos que as quatro primeiras posições possíveis de um ls -l de um
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ão
as linhas listadas serão as que começam por um traço (-), seguido de qualquer coisa (o ponto
quando usado como uma expressão regular significa qualquer coisa), novamente seguido de
qualquer 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 de
mú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 de
programas para organizar os seus CDs de música, com pequenas adaptações, você pode fazer
o mesmo com os CDs de software que vêm com a Linux Magazine e outros que você compra ou
queima, disponibilizando este banco de software, desta forma ganhando muitos pontos com seu
chefe. Para todos que trabalham com você (o Linux é multiusuário, e como tal deve ser explo-
rado).
- Espera! De onde eu vou receber os dados dos CDs?
- Inicialmente, vou lhe mostrar como o seu programa pode receber parâmetros de quem o estiver
executando e em breve, ensinarei a ler os dados pela tela ou de um arquivo.
6.6 Passando parâmetros
O layout do arquivo músicas será o seguinte:
51
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
nome do álbum^intérprete1~nome da música1:..:intérprete~nome da música
isto é, o nome do álbum será separado por um circunflexo (^) do resto do registro, que é formado
por 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érprete
será separado por um til (~) do nome da música.
Eu quero escrever um programa que chamado musinc, incluirá registros no meu arquivo musicas.
Eu passarei o conteúdo de cada álbum como parâmetro na chamada do programa fazendo assim:
$ musinc "álbum^interprete~musica:interprete~musica:..."
Desta forma o programa musinc estará recebendo os dados de cada álbum como se fosse uma
variá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 seus
nomes são formados por um e somente um algarismo), isto é $1, $2, $3, ..., $9. Vamos, antes de
tudo, fazer um teste:
Exemplos
$ cat teste
#!/bin/bash
# Programa para testar passagem de parametros
echo "1o. parm -> $1"
echo "2o. parm -> $2"
echo "3o. parm -> $3"
Vamos executá-lo:
$ teste passando parametros para testar
bash: teste: cannot execute
Ops! Esqueci-me de torná-lo executável. Vou fazê-lo de forma a permitir que todos possam
executá-lo e em seguida vou testá-lo:
$ chmod 755 teste
$ teste passando parametros para testar
1o. parm -> passando
2o. parm -> parametros
3o. parm -> para
Repare que a palavra testar, que seria o quarto 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 de
outra forma:
$ teste "passando parametros"para testar
1o. parm -> passando parametros
52
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
2o. parm -> para
3o. 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ê-lo
assim:
$ cat teste
#!/bin/bash
# Programa para testar passagem de parametros (2a. Versao)
echo O programa $0 recebeu $# parametros
echo "1o. parm -> $1"
echo "2o. parm -> $2"
echo "3o. parm -> $3"
echo Todos de uma só ¨tacada¨: $*
Repare que antes das aspas eu usei uma barra invertida para escondê-las da interpretação do
Shell (se não usasse as contrabarras as aspas não apareceriam). Vamos executá-lo:
$ teste passando parametros para testar
O programa teste recebeu 4 parametros
1o. parm -> passando
2o. parm -> parametros
3o. parm -> para
Todos de uma só "tacada": passando parâmetros para testar
Conforme eu disse, os parâmetros recebem números de 1 a 9, mas isso não significa que não
posso usar mais de 9 parâmetros significa somente que só posso endereçar 9. Vamos testar isso:
Exemplo:
$ cat teste
53
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 $# parametros
echo "11o. parm -> $11"
shift
echo "2o. parm -> $1"
shift 2
echo "4o. Parm -> $1"
Vamos executá-lo:
$ teste passando parametros para testar
O programa teste recebeu 4 parametros que são:
11o. parm -> passando1
2o. parm -> parametros
4o. 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 que
aconteceu? O Shell interpretou como sendo $1 seguido do algarismo 1 e listou passando1;
2. O comando shift cuja sintaxe é shift n, podendo o n assumir qualquer valor numérico (porém
seu default é 1 como no exemplo dado), despreza os n primeiros parâmetros, tornando o
parâmetro de ordem n+1 o primeiro, ou seja, o $1.
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 programa é
muito simples (como tudo em Shell) e vou listá-lo para você ver: Exemplos
$ cat musinc
#!/bin/bash
# Cadastra CDs (versao 1)
#
echo $1 » musicas
O script é 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 supor
que 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 musicas
album 3^Artista5~Musica5:Artista6~Musica5
album 1^Artista1~Musica1:Artista2~Musica2
54
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. Poderia ter ficado melhor. Os álbuns estão
fora de ordem, dificultando a pesquisa. Vamos alterar nosso script e depois testá-lo novamente:
$ cat musinc
#!/bin/bash
# Cadastra CDs (versao 2)
#
echo $1 » musicas
sort musicas -o musicas
Vamos cadastrar mais um:
$ musinc "album 4^Artista7~Musica7:Artista8~Musica8"
Agora, vamos ver o que aconteceu com o arquivo musicas:
$ cat musicas
album 1^Artista1~Musica1:Artista2~Musica2
album 2^Artista3~Musica3:Artista4~Musica4"
album 3^Artista5~Musica5:Artista6~Musica5
album 4^Artista7~Musica7:Artista8~Musica8
Simplesmente inseri uma linha que classifica o arquivo musicas dando a saída nele mesmo (para
isso serve a opção -o), após cada álbum ser anexado.
Agora está quase funcional. Mas atenção, esta não é a versão final. O programa ficará muito
melhor e mais amigável, em uma nova versão que desenvolveremos após aprendermos a adqui-
rir os dados da tela e formatar a entrada.
Exemplos
Listar com o comando cat não serve, vamos, então, fazer um programa chamado 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 dois
parâmetros. Vamos fazer assim:
$ muslist "álbum 2"
grep: can’t open 2
55
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
musicas: album 1^Artista1~Musica1:Artista2~Musica2
musicas: album 2^Artista3~Musica3:Artista4~Musica4"
musicas: album 3^Artista5~Musica5:Artista6~Musica5
musicas: album 4^Artista7~Musica7:Artista8~Musica8
Onde está o erro? Eu tive o cuidado de colocar o parâmetro passado entre aspas 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 de
musicas, listou a todos.
Atenção: Sempre que a cadeia de caracteres a ser passada para o comando grep pos-
suir brancos ou TAB, mesmo que dentro de variáveis, coloque-a sempre entre aspas para
evitar que as palavras após o primeiro espaço em branco ou TAB sejam interpretadas como
nomes de arquivos.
Por outro lado, é melhor ignorarmos maiúsculas e minúsculas na pesquisa. Resolveríamos os
dois 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 e
minú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
da forma que estamos fazendo, podemos pesquisar por álbum, por música, por intérprete ou até
56
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
por um pedaço de qualquer um destes. Quando conhecermos os comandos condicionais, mon-
taremos uma nova versão de muslist que permitirá especificar por qual campo pesquisar.
Você me diz:
- Poxa, mas é um saco ter que colocar o argumento de pesquisa entre aspas na hora de passar
o 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 2
album2^Artista3~Musica3:Artista4~Musica4
Desta forma, o $*, que significa todos os parâmetros, será substituído pela cadeia album 2 (de
acordo 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 40
graus empenou o seu CD e agora você precisa de uma ferramenta para removê-lo do banco de
dados? Não tem problema, vamos desenvolver um script chamado musexc, para excluir estes
CDs.
Antes de desenvolver o "bacalho", quero te apresentar a uma opção bastante útil da família de
comandos 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"musicas
album1^Artista1~Musica1:Artista2~Musica2
album3^Artista5~Musica5:Artista6~Musica6
album4^Artista7~Musica7:Artista8~Musica8
Conforme eu expliquei antes, o grep do exemplo listou todos os registros de músicas exceto o
referente a album 2, porque atendia ao argumento do comando. Estamos, então, prontos para
desenvolver o script para remover aquele CD empenado da sua "CDteca". Ele tem a seguinte
cara:
$ cat musexc
#!/bin/bash
57
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á havia
citado 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 de
trabalho 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 de
ferramentas que ainda temos. Mas é bom, enquanto eu tomo mais um chope, você vai para casa
praticar 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 preciso
molhar a palavra porque estou de goela seca!
- Garçom! Mais um sem colarinho!
58
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 à
cadeia 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 e tem 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 inicial
PosFim = Posição final
$ cat numeros
1234567890
0987654321
1234554321
9876556789
$ cut -c1-5 numeros
12345
59
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
09876
12345
98765
$ cut -c-6 numeros
123456
098765
123455
987655
$ cut -c4- numeros
4567890
7654321
4554321
6556789
$ cut -c1,3,5,7,9 numeros
13579
08642
13542
97568
$ cut -c -3,5,8- numeros
1235890
0986321
1235321
9875789
Como dá para ver, no duro mesmo existem quatro sintaxes distintas: na primeira (-c 1-5), eu
especifiquei uma faixa, na segunda (-c -6), especifiquei tudo até uma 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 é
útil para arquivos com campos de tamanho fixo, mas atualmente o que mais existe são arquivos
com campos de tamanho variáveis, onde cada campo termina com um delimitador. Vamos dar
uma olhada no arquivo musicas que começamos a preparar no nosso papo na última vez que
viemos aqui no botequim.
$ cat musicas
album1^Artista1~Musica1:Artista2~Musica2
album2^Artista3~Musica3:Artista4~Musica4
album3^Artista5~Musica5:Artista6~Musica6
album4^Artista7~Musica7:Artista8~Musica8
Então, recapitulando, o seu layout é o seguinte:
60
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 é formado
por 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 do
inté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: musicas
Artista2~Musica2
Artista3~Musica4
Artista6~Musica6
Artista8~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~
Artista2
Artista4
Artista6
Artista8
Para entender isso, vamos pegar a primeira linha de músicas:
$ head -1 musica
album 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 (:) é album
1^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
61
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 ca-
racteres. 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 de
cadeia1 pelo seu correspondente na cadeia2 ou troca múltiplas ocorrências dos caracteres de
cadeia1 por somente um caractere, 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 simples:
$ echo bobo | tr o a
baba
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 (sim
ou não), e guardo sua resposta na variável $Resp. Ora o conteúdo de $Resp pode estar com
letra maiúscula ou minúscula, e desta forma eu teria que fazer diversos testes para saber se a
resposta 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ção
que pode ser usada são as escape sequences (prefiro escrever no bom e velho português, mas
62
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 significado
você 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 compli-
car a minha vida e em um exercício prático valendo nota que passei para ser feito no computador,
me entregou o script com todos os comandos separados por ponto-e-vírgula (lembra que eu disse
que o ponto-e-vírgula servia para separar diversos comandos em uma mesma linha?).
Vou dar um exemplo simplificado de uma "tripa"assim:
$ cat confuso
echo 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:
$ confuso
leia Programação Shell Linux do Julio Cezar Neves
/home/jneves/LM
confuso livro musexc musicas musinc muslist numeros
Mas nota de prova é coisa séria, então, para entender o que o aluno havia feito, o chamei e
em sua frente executei o seguinte comando:
$ tr ";" "n" < confuso
echo leia Programação Shell Linux do Julio Cezar Neves
pwd
cd ~
ls -l
rm -f lixo 2>/dev/null
Ele ficou muito desapontado, porque em 2 ou 3 segundos eu desfiz a gozação que ele perdera
horas para fazer.
Mas preste atenção! Se eu estivesse em uma máquina com Unix, eu teria feito:
63
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 # Hoje
Sun Sep 19 14:59:54 2004
$ date # Há duas semanas
Sun Sep 5 10:12:33 2004
Para pegar a hora eu deveria fazer:
$ date |cut -f 4 -d ' '
14:59:54
Mas duas semanas antes ocorreria o seguinte:
$ date |cut -f 4 -d ' '
5
Mas observe porque:
$ date # Há duas semanas
Sun Sep 5 10:12:33 2004
Como você pode notar, existem 2 caracteres em branco antes do 5 (dia), o que estraga tudo
porque o terceiro 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 re-
sultantes do 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á ajudando. Veja este arquivo que foi baixado de uma máquina
com aquele sistema operacional que pega vírus:
$ cat -ve ArqDoDOS.txt
Este arquivo^M$
foi gerado pelo^M$
DOS/Rwin e foi^M$
baixado por um^M$
64
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 como
um cifrão.
• Dica 2 - Isto ocorre porque no formato DOS (ou rwin), o fim dos registros é formado por um
Carriage-Return (r) e um line-feed (f). No Linux, porém, o final do registro tem somente o
line-feed.
Vamos 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.txt
Este arquivo^M$
foi gerado pelo^M$
DOS/Rwin e foi^M$
baixado por um^M$
ftp mal feito.^M$
A opção -d do tr remove o caractere especificado de todo o arquivo. Desta forma eu removi os
caracteres indesejados salvando em um arquivo de trabalho e posteriormente renomeei-o para a
sua 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 sin-
taxe:
if cmd
then
cmd1
cmdn
else
cmd3
cmd4
cmdm
fi
65
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 do
bloco 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 1
if grep ^$1 /etc/passwd
then
echo Usuario ’$1’ já existe
else
if useradd $1
then
echo Usuário ’$1’ incluído em /etc/passwd
else
echo "Problemas no cadastramento. Você é root?"
fi
fi
Repare que o if está testando direto o comando grep e esta é a sua finalidade. Caso o if$1
seja bem sucedido, ou seja, o usuário (cujo nome está em foi encontrado em /etc/passwd, os co-
mandos 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 comando
useradd foi executado a contento, criando o registro do usuário em /etc/passwd, ou não quando
dará a mensagem de erro.
Vejamos sua execução, primeiramente passando um usuário já cadastrado:
$ incusu jneves
jneves:x:54002:1001:Julio Neves:/home/jneves:/bin/bash
Usuario ’jneves’ ja existe
Como já vimos diversas vezes, mas é sempre bom insistir no tema para que você já fique preca-
vido, no exemplo dado surgiu uma linha indesejada, ela é a saída do comando grep. Para evitar
que isso aconteça, devemos desviar a saída desta instrução para /dev/null, ficando assim:
$ cat incusu
#!/bin/bash
# Versão 2
if grep ^$1 /etc/passwd > /dev/null
then
echo Usuario ’$1’ já existe
else
if useradd $1
66
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
then
echo Usuário ’$1’ incluído em /etc/passwd
else
echo "Problemas no cadastramento. Você é root?"
fi
fi
Agora, vamos testá-lo como usuário normal (não root):
$ incusu ZeNinguem
./incusu[6]: useradd: not found
Problemas 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 3
if grep ^$1 /etc/passwd > /dev/null
then
echo Usuario ’$1’ já existe
else
if useradd $1 2> /dev/null
then
echo Usuário ’$1’ incluído em /etc/passwd
else
echo "Problemas no cadastramento. Você é root?"
fi
fi
Depois destas alterações e de fazer um su - (me tornar root) vejamos o seu comportamento:
$ incusu botelho
Usuário ’botelho’ incluido em /etc/passwd
E novamente:
$ incusu botelho
Usuário ’botelho’ já existe
Lembra que eu falei que ao longo dos nossos papos e chopes os nossos programas iriam se
aprimorando? Então, vejamos agora como poderíamos melhorar o nosso programa para incluir
músicas:
$ cat musinc
#!/bin/bash
# Cadastra CDs (versao 3)
67
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
#
if grep "^$1$"musicas > /dev/null
then
echo Este álbum já está cadastrado
else
echo $1 » musicas
sort musicas -o musicas?
fi
Como você viu, é uma pequena 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 (^) e
terminava ($) igual ao parâmetro passado ($1). O uso do circunflexo (^) no início da cadeia e
cifrão ($) no fim, são para testar se o parâmetro passado (o álbum e seus dados) são exatamente
iguais a algum registro anteriormente cadastrado e não somente igual a um pedaço de algum dos
registros.
Vamos executá-lo passando um álbum já cadastrado:
$ musinc "album 4^Artista7~Musica7:Artista8~Musica8"
Este álbum já está cadastrado
E agora um não cadastrado:
$ musinc "album 5^Artista9~Musica9:Artista10~Musica10"
$ cat musicas
album1^Artista1~Musica1:Artista2~Musica2
album2^Artista3~Musica3:Artista4~Musica4
album3^Artista5~Musica5:Artista6~Musica6
album4^Artista7~Musica7:Artista8~Musica8
album5^Artista9~Musica9:Artista10~Musica10
- Como você viu, o programa melhorou um pouquinho, mas ainda não está pronto. À medida
que eu for te ensinando a programar em shell, nossa CDteca irá ficando cada vez melhor.
- Entendi tudo que você me explicou, mas ainda 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 comando
test. 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 de
diversas outras sintaxes do if.
- Falou! Acho bom mesmo porque eu também já tô ficando zonzo e assim tenho tempo para
praticar esse monte de coisas que você me falou hoje.
- Para fixar o que você aprendeu, tente fazer um scriptizinho para informar se um determinado
usuário, que será passado como parâmetro esta logado (arghh!) ou não.
- Aê Chico, mais dois chopes por favor...
68
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 fazer
um scriptizinho para informar se um determinado usuário, que será passado como parâmetro
está logado (arghh!) ou não. Eu fiz o seguinte:
$ cat logado
#!/bin/bash
# Pesquisa se uma pessoa está logada ou não
if who | grep $1 then echo $1 está logado
else
echo $1 não se encontra no pedaço
fi
- 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 jneves
jneves 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 para
evitar 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/null
then
echo $1 está logado
69
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
else
echo $1 não se encontra no pedaço
fi
Agora, vamos aos testes:
$ logado jneves
jneves está logado
$ logado chico
chico não se encontra no pedaço
Atenção: Ah, agora sim! Lembre-se desta pegadinha, a maior parte dos comandos tem
uma saída padrão e uma saída de erros (o grep é uma das poucas exceções, já que não
dá mensagem de erro quando não acha uma cadeia) e é necessário estarmos atentos para
redirecioná-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 falando
sobre o if, você me perguntou como se testa condições. Vejamos, então, o comando test.
8.2 O comando Test
Todos estão acostumados a usar o if testando condições e estas são sempre, maior, menor,
maior ou igual, menor ou igual, igual e diferente. Em Shell para testar condições usamos o
comando test, só que ele é muito mais poderoso que o que estamos habituados. Primeiramente,
vou te mostrar as principais opções (existem muitas outras) para testarmos arquivos em disco:
.
Veja as principais opções para teste de cadeias de caracteres:
70
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 seja,
as 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 muita coisa, e como eu te disse no início, o nosso if é muito mais po-
deroso que o dos outros. Vamos ver em uns exemplos como isso tudo funciona, primeiramente
testaremos a existência de um diretório:
Exemplos:
if test -d lmb
then
cd lmb
else
mkdir lmb
cd 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, mas que-
ria que você o entendesse assim, para, então, poder usar o ponto-de-espantação (!) como um
negador do test. Veja só:
if test ! -d lmb
then
mkdir lmb
fi
cd lmb
71
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 negativa
deve-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=1
cad2=01
if test $cad1 = $cad2
then
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=1
cad2=01
if test $cad1 -eq $cad2
then
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 ao
número 01 é realmente diferente da cadeia
Exemplos:
Para mostrar o uso dos conectores -o (OU) e -a (E), veja um exemplo feito direto no prompt
(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 grande chance
de estar incorreto):
72
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
> fi
Pode 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 de
incentivo.
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
> fi
Cuidado
Obviamente a operação redundou em erro, isto foi porque a opção -a tem precedência sobre
a -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 entre
si por diversos OU lógicos seja verdadeira, basta que uma delas seja, a expressão final resultou
como VERDADEIRO e o then foi executado de forma errada. Para que isso volte a funcionar,
façamos o seguinte:
73
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
> fi
Pode 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 resultado
final 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á muito
recursos, e isso poderia ser feito de forma muito mais simples com um único comando grep, da
seguinte maneira:
$ grep Artista1 musicas |grep Artista2
Da mesma forma para escolhermos CDs que tenham a 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 mais
aconselhá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 ajudar,:
$ grep Artista[12] musicas
No egrep acima, foi usada uma expressão regular, onde a barra vertical (|) trabalha como um
OU lógico e os parênteses são usados para limitar a amplitude deste OU. Já no grep da linha
seguinte, a palavra Artista deve ser seguida por um dos valores da lista formada pelos colchetes
([ ]), isto é, 1 ou 2.
- Tá legal, eu aceito o argumento, o if do Shell é muito mais poderoso que os outros caretas,
mas cá pra 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
74
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 lmb
then
mkdr lmb
fi
cd lmb
e utilizando a nova sintaxe, vamos fazê-lo assim:
if [ ! -d lmb ]
then
mkdir lmb
fi
cd lmb
Ou seja, o comando test pode ser substituído por um par de colchetes ([ ]), separados por espa-
ços em branco dos argumentos, o que aumentará enormemente a legibilidade, pois o comando
if irá ficar com a sintaxe semelhante à das outras linguagens e por isso este será o modo que o
comando test será usado daqui para a frente.
8.4 Encolheram o comando condicional
Repare a tabela (tabela verdade) a seguir:
.
Ou seja, quando o conector é E e a primeira condição é verdadeira, o resultado final pode ser
VERDADEIRO ou FALSO, dependendo da segunda condição, já no conector OU, caso a primeira
condição seja verdadeira, o resultado sempre será VERDADEIRO e se a primeira for falsa, o re-
sultado dependerá da segunda condição.
Os desenvolvedores do interpretador estão sempre tentando otimizar 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, a segunda será executada somente
caso a primeira seja falsa.
Aproveitando disso, foi criada uma forma abreviada de fazer testes. foi batizado o conector E
de && e o OU de || e para ver como isso funciona, vamos usá-los como teste no nosso velho
exemplo de trocarmos de diretório, que em sua última versão estava assim:
if [ ! -d lmb ]
75
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
then
mkdir lmb
fi
cd lmb
Isso também poderia ser escrito da seguinte maneira:
[ ! -d lmb ] && mkdir lmb
cd dir
Ou ainda retirando a negação (!):
[ -d lmb ] || mkdir lmb
cd dir
No primeiro caso, se o primeiro comando (o test que está representado pelos colchetes) for bem
sucedido, isto é, não existir o diretório lmb, o mkdir será efetuado porque a primeira condição era
verdadeira 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 cd
para dentro dele. Para executarmos mais de um comando desta forma, é necessário fazermos
um grupamento de comandos, e isso se consegue com o uso de chaves ( ). Veja como seria o
correto:
cd lmb ||
{
mkdir lmb
cd lmb
}
Ainda não está bom, 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 lmb
cd lmb
}
Como você viu o comando if nos permitiu fazer um cd seguro de diversas maneiras. É sempre
bom lembrarmos que o seguro a que me referi é no tocante ao fato de que ao final da execu-
ção você sempre estará dentro de lmb, desde que você tenha permissão para entrar em lmb,
permissão para criar um diretório em ../lmb, haja espaço em disco, ...
76
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
8.5 E tome de test
Tem, ainda, mais uma forma de test que te permite usar padrões para comparação. Estes
padrões atendem às normas de Geração de Nome de Arquivos (File Name Generation, que são
ligeiramente parecidas com as Expressões Regulares, mas não podem ser confundidas com es-
tas). A diferença de sintaxe deste para o test que acabamos de ver é que esse trabalha com dois
pares de colchete da seguinte forma:
[[ expressão ]]
Onde expressão é uma das que constam na tabela a seguir:
.
$ echo $H
13
$ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora inválida
Hora 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 caractere
var 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 do
comando test. No início deste papo, antes do último chope, afirmamos que o comando if do inter-
pretador Shell é mais poderoso que o seu similar em outras linguagens. Agora que conhecemos
77
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
todo 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á executar
uma das opções: inclusão, exclusão, alteração ou fim. Veja como ficaria este fragmento de script:
if [ $opc -eq 1 ]
then
inclusao
elif [ $opc -eq 2 ]
then
exclusao
elif [ $opc -eq 3 ]
then
alteracao
elif [ $opc -eq 4 ]
then
exit
else
echo Digite uma opção entre 1 e 4
fi
Neste exemplo, você viu o uso do elif com um else if, esta á a sintaxe válida e aceita, mas
poderíamos fazer melhor, e isto seria com o comando case, que tem a sintaxe a seguir:
case $var in
padrao1) cmd1
cmd2
cmdn ;;
padrao2) cmd1
cmd2
cmdn ;;
padraon) cmd1
cmd2
cmdn ;;
esac
Onde a variável $var é comparada aos padrões padrao1, ..., padraon e caso um deles atenda, o
bloco 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:
78
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 o
case e não o if ... elif ... else ... fi.
case $opc in
1) 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 asterisco
atende a qualquer coisa, então, ele servirá para qualquer coisa que não esteja no intervalo de 1
a 4. Outro ponto a ser notado é 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:
$ date
Tue Nov 9 19:37:30 BRST 2004
$ date +%H
19
O comando date informa a data completa do sistema, mas ele tem diversas opções para seu
mascaramento. Neste comando, a formatação começa com um sinal de mais (+) e os caracteres
de 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 hora
Hora=$(date +%H)
case $Hora in
0? | 1[01]) echo Bom Dia
;;
1[2-7] ) echo Boa Tarde
79
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
;;
esac
exit
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 ou
até mesmo para falar mal dos políticos.
- É fácil, meu e-mail é julio.neves@gmail.com, 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 que
receberá como parâmetro o nome de um arquivo e que quando executado salvará este arquivo
com o nome original seguido de um til (~) e colocará este arquivo dentro do vi (o melhor editor
que se tem notícia) para ser editado. Isso é para ter sempre a última cópia boa deste arquivo caso
o cara faça alterações indevidas. Obviamente, você fará as críticas necessárias, como verificar
se 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 pra entender?
- Hum, hum...
- Chico! Traz mais um sem colarinho que o cara aqui já está dando para entender!
80
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 instruçôes
precisam ser repetidas por várias vezes usando conjuntos diferentes de dados. Mais comumente,
uma seçâo de código que se repete é chamada de laço porque após a execuçâo da última instru-
çâo o programa se bifurca e retorna à primeira instrução ou encerra a execução. As instruçôes de
loop ou laço que veremos são o for, o while e o until que veremos daqui em diante. Começaremos
pelo 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 do comando faz parte do código fonte do Shell, ou seja, em bom programa é um built-in), é
muito mais 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 ... valn
faça
cmd1
cmd2
cmdn
feito
Onde a variável var assume cada um dos valores da lista val1 val2 ... valn e para cada um
desses valores executa o bloco de comandos formado por cmd1, cmd2 e cmdn.
9.2.1 Primeira sintaxe do comando for
for var in val1 val2 ... valn
do
cmd1
cmd2
81
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
cmdn
done
Vamos direto para os exemplos, para entender direito o funcionamento deste comando. Vamos
escrever um script para listar todos os arquivos do nosso 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ório
e o comando echo jogou-os para a tela separados por espaços em branco. Visto isso vamos ver
como resolver o problema a que nos propuzemos:
$ cat testefor1
#!/bin/bash
# 1o. Prog didático para entender o for
for Arq in *
do
echo -n $Arq: # A opcao -n eh para nao saltar linha
done
Então, vamos executá-lo:
$ testefor1
ArqDoDOS.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 uma
lista 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 exemplo
simples (por enquanto):
$ cat testefor2
#!/bin/bash
# 2o. Prog didático para entender o for
for Palavra in Papo de Botequim
do
echo $Palavra
done
E executando vem:
82
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
$ testefor2
Papo
de
Botequim
Como você viu, este exemplo é tão simples como o anterior, mas serve para mostrar o com-
portamento básico do for.
Veja só a força do for: ainda estamos na primeira sintaxe do comando e já estou mostrando
novas formas de usá-lo. Lá atrás eu havia falado que o for usava listas separadas por espaços
em 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
te mostrarei como se comporta uma variável do sistema chamada de $IFS. Repare seu conteúdo:
$ echo "$IFS"| od -h
0000000 0920 0a0a
0000004
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 repre-
senta o <TAB> como um ^I. Usei os dois-pontos (:) para mostrar o início e o fim do echo. E
desta forma, mais uma vez pudemos notar que os três caracteres estão presentes naquela
variável.
Agora veja você, IFS significa Inter Field Separator ou, traduzindo, separador entre campos. Uma
vez entendido isso, eu posso afirmar (porque vou provar) que o comando for não usa listas sepa-
radas por espaços em branco, mas sim pelo conteúdo da variável $IFS, cujo valor padrão (default)
83
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
são esses caracteres que acabamos de ver. Para comprovarmos isso, vamos mostrar um script
que 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 musicas
album 1^Artista1~Musica1:Artista2~Musica2
album 2^Artista3~Musica3:Artista4~Musica4
album 3^Artista5~Musica5:Artista6~Musica6
album 4^Artista7~Musica7:Artista1~Musica3
album 5^Artista9~Musica9:Artista10~Musica10
Em cima deste "leiaute"foi desenvolvido o script a seguir:
$ cat listartista
#!/bin/bash
# Dado um artista, mostra as suas musicas
if [ $# -ne 1 ]
then
echo Voce deveria ter passado um parametro
exit 1
fi
IFS="
:"
for ArtMus in $(cut -f2 -d^ musicas)
do
echo "$ArtMus"| grep $1 && echo $ArtMus | cut -f2 -d~
done
O script, como sempre, começa testando se os parâmetros foram passados corretamente, em
seguida o IFS foi setado para <ENTER> e dois-pontos (:) (como demonstram as aspas em linha
diferentes), porque é ele que separa os blocos Artistan~Musicam. Desta forma, a 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 cut
listará somente o nome da música. Vamos executá-lo:
$ listartista Artista1
Artista1 Musica1
Musica1
Artista1 Musica3
Musica3
Artista10 Musica10
Musica10
Aconteceram duas coisas indesejáveis: os blocos também foram listados e a Musica10 idem.
Além do mais, o nosso arquivo de músicas está muito simples, na vida real, tanto a música
84
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
quanto o artista têm mais de um nome. Suponha que o artista fosse uma dupla sertaneja cha-
mada Perereca & Peteleca. Nesta caso o $1 seria Perereca e o resto seria ignorado na pesquisa.
Para que isso não ocorresse, eu devia passar o nome do artista entre aspas (") ou alterar $1
por $@ (que significa todos os parâmetros passados), que é a melhor solução, mas neste caso
eu teria que modificar a crítica dos parâmetros e o grep. A nova crítica não seria se eu passei
um parâmetro, mas pelo menos um parâmetro e quanto ao grep, veja só o que resultaria após a
substituiçã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 aspas
também foram inseridas para que o nome do artista fosse visto como uma só cadeia monolítica.
Ainda falta consertar 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 grep
para /dev/null para que os blocos não sejam mais listados. Veja, então, a nova (e definitiva) cara
do 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 ]
then
echo Voce deveria ter passado pelo menos um parametro
exit 1
fi
IFS="
:"
for ArtMus in $(cut -f2 -d^ musicas)
do
echo "$ArtMus"| grep -i "^$@~" > /dev/null && echo $ArtMus | cut -f2 -d
done
Que executando vem:
$ listartista Artista1
Musica1
Musica3
85
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
9.2.2 Segunda sintaxe do comando for
for var
do
cmd1
cmd2
cmdn
done
- Sem o in como ele saberá que valor assumir?
- Esta construção a primeira vista parece exquisita, mas é bastante simples. Neste caso, 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 ]
then
echo Uso: $0 musica1 [musica2] ... [musican]
exit 1
fi
IFS="
:"
for Musica
do
echo $Musica
Str=$(grep -i "$Musica"musicas) ||
{
echo "Não encontrada"
continue
}
for ArtMus in $(echo "$Str"| cut -f2 -d^)
do
echo "$ArtMus"| grep -i "$Musica"| cut -f1 -d
done
done
Da mesma forma que os outros, começamos o exercício com uma crítica sobre os parâmetros
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. Em
seguida, o outro for pega cada bloco Artista Musica nos registros que estão em $Str e lista cada
artista que execute aquela música.
86
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
Como sempre vamos executá-lo para ver se funciona mesmo:
$ listamusica musica3 Musica4 "Eguinha Pocotó"
musica3
Artista3
Artista1
Musica4
Artista4
Eguinha Pocotó
Não encontrada
A listagem ficou feia 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 esta
listagem novamente usando todas estas ferramentas.
A esta altura dos acontecimentos você deve estar se perguntando: "E aquele for tradicional das
outras linguagens em que ele sai contando a partir de um número, com um determinado incre-
mento até alcançar uma condição?"
E eu te respondo: "Eu não te disse que o nosso for é mais porreta que os outros?"Para fazer
isso existem duas formas:
1 - Com a primeira sintaxe que vimos, como nos exemplos a seguir direto no prompt:
$ for i in $(seq 9) > do > echo -n "$i "
> done
1 2 3 4 5 6 7 8 9
Neste, a variável i assumiu os inteiros de 1 a 9 gerados pelo comando seq e a opção -necho
foi usada para não saltar linha a cada número listado (sinto-me ecologicamente correto por não
gastar 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 "
> done
4 5 6 7 8 9
Ou ainda na forma mais completa do seq:
$ for i in $(seq 0 3 9)
> do
> echo -n "$i "
> done
0 3 6 9
87
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
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
cmd1
cmd2
cmdn
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ção
cond;
incr - Significa o incremento que a variável var sofrerá em cada passada do loop.
Como sempre vamos aos exemplos que tudo fica mais fácil:
$ for ((i=1; i<=9; i++))
> do
> echo -n "$i "
> done
1 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 o
echo) será executado enquanto i menor ou igual (<)= a 9 e o incremento de i1 a cada passada
do 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 o
uso 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 o
bloco de comandos.
$ for ((; i<=9;))
> do
> let i++
> echo -n "$i "
> done
1 2 3 4 5 6 7 8 9
Repare que o incremento saiu do corpo do for e passou para o bloco de comandos, repare
também que quando usei o let, não foi necessário sequer inicializar a variável $i. Veja só os
88
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
comandos a seguir dados diretamente no prompt para mostrar o que acabo de falar:
$ echo $j
$ let j++
$ echo $j
1
Ou seja, a variável $j sequer existia e no primeiro let assumiu o valor 0 (zero) para, após o
incremento, ter o valor 1.
Veja só como as coisas ficam simples:
$ for arq in *
> do
> let i++
> echo "$i -> $Arq"
> done
1 -> ArqDoDOS.txt1
2 -> confuso
3 -> incusu
4 -> listamusica
5 -> listartista
6 -> logado
7 -> musexc
8 -> musicas
9 -> musinc
10 -> muslist
11 -> testefor1
12 -> testefor2
- Pois é amigo, tenho certeza que você já tomou um xarope do comando for. Por hoje chega,
na 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 arquivo
texto, 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ão
vale usar o wc -w.
89
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
o loop de for a seguir:
for ((; i<=9;))
do
let i++
echo -n "$i "
done
Uma vez que chegamos neste ponto, creio ser bastante interessante citar que o Shell trabalha
com o conceito de "Expansão Aritmética"(Arithmetic Expansion) que é acionado por uma cons-
trução da forma
$((expressão))
ou
let expressão
No último for citado usei a expansão das duas formas, mas não poderíamos seguir adiante sem
saber que a expressão pode ser de uma das listadas a seguir:
90
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? Enganou-se 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 lingua-
gens e nelas, o que normalmente ocorre é que um bloco de comandos é executado, enquanto
(enquanto em ingles é while) uma determinada condição for verdadeira. Pois bem, isto é o que
ocorre nas linguagens caretas! Em programação Shell, o bloco de comandos é executado en-
quanto um comando for verdadeiro. E é claro, se quiser testar uma condição use o comando
while junto com o comando test, exatamente como você aprendeu a fazer no if, lembra?
Então, a sintaxe do comando fica assim:
while comando
do
cmd1
cmd2
...
cmdn
done
e desta forma o bloco de comandos formado pelas instruções cmd1, cmd2,... e cmdncomando
for 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 sem
poder 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 xefe
do
sleep 30
done
echo 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-
91
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 o
chefe estiver logado (Argh!). Assim que ele se desconectar do servidor, o fluxo do script sairá do
loop e que será verdadeiro enquanto e dará a tão ansiada mensagem de liberdade.
Quando o executei adivinha o que aconteceu?
$ logaute.sh
xefe pts/0 Jan 4 08:46 (10.2.4.144)
xefe pts/0 Jan 4 08:47 (10.2.4.144)
...
xefe pts/0 Jan 4 08:52 (10.2.4.144)
Isto é a cada 30 segundos seria enviado para a tela a 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. Para
evitar 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 sorriso
while who | grep xefe > /dev/null
do
sleep 30
done
echo 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 programa
que será executado em background e que me informe do seu término. Mas, para você entender
este exemplo, primeiro tenho de mostar uma nova variável do sistema. Veja estes comandos
diretos no prompt:
$ sleep 10&
[1] 16317
$ echo $!
16317
[1]+ Done sleep 10
$ echo $!
16317
Isto é, criei um processo em background para dormir por 10 segundos, somente para mostrar
que a variável $! guarda o PID (Process IDentification) do último processo em background, mas
repare 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 monitorar qualquer processo em background. Veja como:
$ cat monbg.sh
#!/bin/bash
92
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
# Executa e monitora um
# processo em background
$1 & # Coloca em background
while ps | grep -q $!
do sleep 5
done
echo Fim do Processo $1
Este script é bastante similar ao anterior, mas tem uns macetes a mais, veja só: ele tem que
ser executado em background para não prender o prompt, mas o $!background após o monbg.sh
propriamente dito. Repare também a opção -q (quiet) do grep, ela serve para tranformá-lo num
comando mineiro, isto é, para o grepwhile ps | grep $! > /dev/null, como nos exemplos que vimos
até agora. será o do programa passado como parâmetro já que ele foi colocado em "trabalhar 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 (Process IDentifi-
cation) 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 resolver
este 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 operador
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 programa
que inicialmente lerá o nome do álbum e em seguida fara um loop de leitura, pegando a música
e o artista. Este loop termina quando for informada uma música vazia, isto é, ao ser solicitada
a 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 que
o álbum seja todo do mesmo artista) até que ele deseje alterá-lo. Vamos ver como ficou:
$ cat musinc
#!/bin/bash
# Cadastra CDs (versao 4)
#
clear
read -p "Título do Álbum: "Tit
[ "$Tit"] || exit 1 # Fim da execução se título vazio
93
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
if grep "^$Tit^" musicas > /dev/null
then
echo Este álbum já está cadastrado
exit 1
fi
Reg="$Tit^"
Cont=1
oArt=
while true
do
echo Dados da trilha $Cont:
read -p "Música: "Mus
[ "$Mus"] || break # Sai se vazio
read -p "Artista: $oArt // "Art
[ "$Art"] && oArt="$Art"# Se vazio Art anterior
Reg="$Reg$oArt $Mus:"# Montando registro
Cont=$((Cont + 1))
# A linha anterior tb poderia ser ((Cont++))
done
echo "$Reg»> musicas
sort musicas -o musicas$ cat musinc
#!/bin/bash
# Cadastra CDs (versao 4)
#
clear
read -p "Título do Álbum: "Tit
[ "$Tit"] || exit 1 # Fim da execução se título vazio
if grep "^$Tit^" musicas > /dev/null
then
echo Este álbum já está cadastrado
exit 1
fi
Reg="$Tit^" Cont=1
oArt=
while true
do
echo Dados da trilha $Cont:
read -p "Música: "Mus
[ "$Mus"] || break # Sai se vazio
read -p "Artista: $oArt // "Art
[ "$Art"] && oArt="$Art"# Se vazio Art anterior
Reg="$Reg$oArt $Mus:"# Montando registro
Cont=$((Cont + 1))
# A linha anterior tb poderia ser ((Cont++))
done
echo "$Reg»> musicas
sort musicas -o musicas
94
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á a
execuçã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 () para
protegê-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 $oArt
que só terá o seu conteúdo alterado, quando algum dado 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 uma
operaçã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 forma oti-
mizada 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. É o seguinte: ambos
testam comandos; ambos possuem a mesma sintaxe e ambos atuam em loop, porém enquanto o
while executa o bloco de instruções do loop enquanto um comando for bem sucedido, o until exe-
cuta o bloco do loop até que o comando seja bem sucedido. Parece pouca coisa mas a diferença
é fundamental.
A sintaxe do comando é praticamente a mesma do while. Veja:
until comando
do
cmd1
cmd2
...
cmdn
done
E desta forma o bloco de comandos formado pelas instruções cmd1, cmd2,... e cmdncomando é
executado até que a execução da instrução seja bem sucedida.
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 para
neutralizá-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.
Um dia deu um problema da rede, ele me pediu para dar uma olhada no micro dele e me dei-
95
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
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 julio
do
sleep 30
done
echo $(date "+ Em %d/%m às %H:%Mh") > relapso.log
Olha que safado! O cara estava montando um log com os horários que eu chegava, e ainda
por cima chamou o arquivo que me monitorava de relapso.log! O que será que ele quis dizer com
isso?
Neste script, o pipeline who | grep julio, será bem sucedido somente quando juliowho, isto é,
quando eu me "logar"no servidor. Até que isso aconteça, o comando sleep, que forma o bloco de
instruções do until, colocará 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 comando
Em 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 programa
termina. Vejamos como melhorá-lo:
$ cat musinc
#!/bin/bash
# Cadastra CDs (versao 5)
#
Para=
until [ "$Para"]
do
clear
read -p "Título do Álbum: "Tit
if [ ! "$Tit"] # Se titulo vazio...
then
Para=1 # Liguei flag de saída
else
if grep "^$Tit^" musicas > /dev/null
then
echo Este álbum já está cadastrado
exit 1
fi
Reg="$Tit^"
96
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
Cont=1
oArt=
while [ "$Tit"]
do
echo Dados da trilha $Cont:
read -p "Música: "Mus
[ "$Mus"] || break # Sai se vazio
read -p "Artista: $oArt // "Art
[ "$Art"] && oArt="$Art"# Se vazio Art anterior
Reg="$Reg$oArt $Mus:"# Montando registro
Cont=$((Cont + 1))
# A linha anterior tb poderia ser ((Cont++))
done
echo "$Reg»> musicas
sort musicas -o musicas
fi
done
Nesta versão, um loop maior foi adicionado antes da leitura do título, que só terminará quando a
variável $Para deixar de ser vazia. Caso o título do álbum não seja informado, a variável $Para
receberá valor (no caso coloquei 1, mas poderia ter colocado qualquer coisa. O importante é que
nã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
da frente. Em algumas oportunidades, temos que colocar um comando que aborte de forma
controlada este loop. De maneira inversa, algumas vezes desejamos que o fluxo de execução
do programa volte antes de chegar ao done. Para isto, temos respectivamente, os comandos
break (que já vimos rapidamente nos exemplos do comado while) e continue, que funcionam da
seguinte forma:
O que eu não havia dito anteriormente é que nas suas sintaxes genéricas eles aparecem da
seguinte forma:
break [qtd loop]
e
continue [qtd loop]
Onde qtd loop representa a quantidade dos loops mais internos sobre os quais os comandos
irão atuar. Seu valor default é 1.
97
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
.
Duvido que você nunca tenha deletado um arquivo e logo após se arrependeu porque não devia
tê-lo removido. Pois é, na décima vez que fiz isso, criei um script para simular uma lixeira, isto
é, quando mando remover um (ou vários) arquivo(s), o programa "finge"que removeu-o, mas 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 remover
then
echo "Erro -> Uso: erreeme arq [arq] ... [arq]"
echo "O uso de metacaracteres é permitido. Ex. erreeme arq*"
exit 1
fi
98
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...
then
mkdir $MeuDir # Vou cria-lo
fi
if [ ! -w $MeuDir ] # Se não posso gravar no diretório...
then
echo Impossivel salvar arquivos em $MeuDir. Mude permissao...
exit 2
fi
Erro=0 # Variavel para indicar o cod. de retorno do prg
for Arq # For sem o "in"recebe os parametros passados
do
if [ ! -f $Arq ] # Se este arquivo não existir...
then
echo $Arq nao existe.
Erro=3
continue # Volta para o comando for
fi
DirOrig=’dirname $Arq’ # Cmd. dirname informa nome do dir de $Arq
if [ ! -w $DirOrig ] # Verifica permissão de gravacaoo no diretório
then
echo Sem permissao de remover no diretorio de $Arq
Erro=4
continue # Volta para o comando for
fi
if [ "$DirOrig"= "$MeuDir"] # Se estou "esvaziando a lixeira"...
then
echo $Arq ficara sem copia de seguranca
rm -i $Arq # Pergunta antes de remover
[ -f $Arq ] || echo $Arq removido # Será que o usuario removeu?
continue
fi
cd $DirOrig # Guardo no fim do arquivo o seu diretorio
pwd » $Arq # original para usa-lo em um script de undelete
mv $Arq $MeuDir # Salvo e removido
echo $Arq removido
99
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
done
exit $Erro # Passo eventual numero do erro para o codigo de retorno
Como você pode ver, a maior parte do script é formada por pequenas críticas aos parâmetros
informados, mas como o script pode ter recebido diversos arquivos para remover, a cada arquivo
que não se encaixa dentro do especificado, há um continue, para que a sequência volte para o
loop do for de forma a receber outros arquivos. Quando você está no Windows (com perdão da
má palavra) e tenta remover aquele monte de lixo com nomes esquisitos como HD04TG.TMP, se
der erro em um deles, os outros não são removidos, não é? Então, o continue foi usado para
evitar que um impropério desses ocorra, isto é, mesmo que dê erro na remoção de um arquivo, o
programa continuará removendo os outros que foram passados.
- Eu acho que a esta altura você deve estar curioso para ver o programa que restaura o arquivo
removido, não é? Pois então aí vai um desafio: faça-o em casa e me traga para discutirmos no
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 de
criticar 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 para
julio.neves@gmail.com. Agora chega de papo que eu já estou de goela seca de tanto falar. Me
acompanha 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!
100
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
para apagar dados da tela, saber a quantidade de linhas e colunas para poder posicionar corre-
tamente um campo, apagar um campo cuja crítica detectou como errado. Enfim, quase toda a
formatação da 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ó:
101
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
.
$ tput it
8
Neste exemplo, eu recebi o tamanho inicial da <TAB> ( Initial T ab), mas me diga: para que
eu quero saber isso? Se você quiser saber tudo sobre o comando tput (e olha que é coisa que
não acaba mais), veja em:
http://www.cs.utah.edu/dept/old/texinfo/tput/tput.html#SEC4.
Vamos fazer um programa bem fácil para mostrar alguns atributos deste comando. É o famoso e
famigerado Alô Mundo só que esta frase será escrita no centro da tela e em vídeo reverso e após
isso, o cursor voltará para a posição em que estava antes de escrever esta tão criativa frase. Veja:
$ cat alo.sh
#!/bin/bash
# Script bobo para testar
# o comando tput (versao 1)
Colunas=’tput cols’ # Salvando quantidade colunas
Linhas=’tput lines’ # Salvando quantidade linhas
Linha=$((Linhas / 2)) # Qual eh a linha do meio da tela?
Coluna=$(((Colunas - 9) / 2)) # Centrando a mensagem na tela
tput sc # Salvando posicao do cursor
tput cup $Linha $Coluna # Posicionando para escrever
tput rev # Video reverso
echo Alô Mundo
tput sgr0 # Restaura video ao normal
tput rc # Restaura cursor aa posição original
Como o programa já está todo comentado, acho que a única explicação necessária seria para
a 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 $#var
4
$ var="Papo de Botequim"
$ echo $#var
16
Ahhh, melhorou! Então, agora sabemos que a construção $#variavel devolve a quantidade de
caracteres de variável. Assim sendo, vamos otimizar o nosso programa para que ele escreva
em vídeo reverso, no centro da tela a cadeia passada como parâmetro e depois o cursor volte à
102
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
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 colunas
Linhas=’tput lines’ # Salvando quantidade linhas
Linha=$((Linhas / 2)) # Qual eh a linha do meio da tela?
Coluna=$(((Colunas - $#1) / 2)) #Centrando a mensagem na tela
tput sc # Salvando posicao do cursor
tput cup $Linha $Coluna # Posicionando para escrever
tput rev # Video reverso
echo $1
tput sgr0 # Restaura video ao normal
tput rc # Restaura cursor aa posição original
Este script é igual ao anterior, só que trocamos o valor fixo da versão anterior (9), por $#1, onde
este 1 é o $1 ou seja, esta construção devolve o tamanho do primeiro parâmetro passado para
o programa. Se o parâmetro que eu quiser passar tiver espaços em branco, teria que colocá-lo
todo entre aspas, senão o $1$1 por $*, que como sabemos é o conjunto de todos os parâmetros.
Então, aquela linha ficaria assim: seria somente o primeiro pedaço. Para evitar este aborreci-
mento, é 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 quando executar, passar a frase
que você deseja centrar como parâmetro.
11.2 E agora podemos ler os dados na tela
A partir de agora vamos aprender tudo sobre leitura, num pub londrino tomando scotch e não
em um boteco desses tomando chope. Mas vamos em frente.
Da última vez que nos encontramos aqui eu já dei uma palinha sobre o comando read. Para
começarmos a sua análise mais detalhada. Veja só isso:
$ read var1 var2 var3
Papo de Botequim
$ echo $var1
Papo
$ echo $var2
de
$ echo $var3
103
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
Botequim
$ read var1 var2
Papo de Botequim
$ echo $var1
Papo
$ echo $var2
de Botequim
Como você viu, o read recebe uma lista separada por espaços em branco e coloca cada item
desta lista em uma variável. Se a quantidade de variáveis for menor que a quantidade de ítens, 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 ainda
acredita nisso? Vamos testar direto no prompt:
$ oIFS="$IFS"
$ IFS=:
$ read var1 var2 var3
Papo de Botequim
$ echo $var1
Papo de Botequim
$ echo $var2
$ echo $var3
$ read var1 var2 var3
Papo:de:Botequim
$ echo $var1
Papo
$ echo $var2
de
$ echo $var3
Botequim
$ IFS="$oIFS"
Viu, estava furado! O read lê uma lista, assim como o for, separada pelos caracteres da va-
riável $IFS. Então, veja como isso pode facilitar a sua vida:
$ grep julio /etc/passwd
julio:x:500:544:Julio C. Neves - 7070:/home/julio:/bin/bash
$ oIFS="$IFS"# Salvando IFS
$ IFS=:
$ grep julio /etc/passwd | read lname lixo uid gid coment home shell
$ echo -e "$lnamen$uidn$gidn$comentn$homen$shell"
julio
500
104
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
544
Julio 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 campos
de uma só vez. A opção -e do echo foi usada para que o n new line fosse entendido como um
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.
Para ler um campo "Matrícula":
$ echo -n "Matricula: "; read Mat # -n nao salta linha
Matricula: 12345
$ echo $Mat
12345
Ou simplificando com a opção -p:
$ read -p "Matricula: "Mat
Matricula: 12345
$ echo $Mat
12345
Para ler uma determinada quantidade de caracteres:
$ read -n5 -p"CEP: "Num ; read -n3 -p- Compl
CEP: 12345-678$
$ echo $Num
12345
$ echo $Compl
678
105
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
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 algarismo
teclado, é 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 duas
coisas:
1. O comando após o par de barras verticais (||) (o ou lógico, lembra-se?) será executado caso
a 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> for
teclado.
Para ler um dado sem ser exibido na tela:
$ read -sp "Senha: "
Senha: $ echo $REPLY
segredo
Aproveitei um erro para mostrar um macete. Quando escrevi a primeira linha, esqueci de colocar
o nome da variável que iria receber a senha, e só notei quando ia listar o seu valor. Felizmente
a variável $REPLY do Bash, possui a última cadeia lida e me aproveitei disso para não perder a
viagem. 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 ir
para a tela. Como no exemplo anterior, a falta do new-line fez com que o prompt de comando ($)
permanecesse na mesma linha.
Bem, agora que sabemos ler na 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
bloco de instruções enquanto este comando for bem sucedido. Quando você está lendo um ar-
quivo que 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:
106
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
1 - Redirecionando a entrada do arquivo para o bloco do while assim:
while read Linha
do
echo $Linha
done < arquivo
2 - Redirecionando a saída de um cat para o while, da seguinte maneira:
cat arquivo |
while read Linha
do
echo $Linha
done
Cada um dos processos tem suas vantagens e desvantagens:
Vantagens do primeiro processo:
• É mais 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 vezes
prejudica a vizualização do código.
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, pesado
e 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/ while
while read Linha
do
Ultimo="$Linha"
echo -$Ultimo-"
107
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
done
echo "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 e
outro depois de cada, e no final exibe o conteúdo da variável $Ultimo. Repare no entanto que o
conteúdo desta variável permanece como (vazio).
- Será que a variável não foi atualizada?
- Foi, e isso pode ser comprovado porque a linha echo -$Ultimo-"lista corretamente as linhas.
- Então, porque isso aconteceu?
- Por que como eu disse, o bloco de instruções redirecionado pelo pipe (|) é executado em um
subshell e lá as variáveis são atualizadas. Quando este subshell termina, as atualizações das
variáveis vão para os píncaros do inferno junto com ele. Repare que vou fazer uma pequena
mudança nele, passando o arquivo por redirecionamento de entrada (<) e as coisas passarão a
funcionar na mais perfeita ordem:
$ cat redirread.sh
#!/bin/bash
# redirread.sh
# Exemplo de read passando arquivo por pipe.
Ultimo="(vazio)"
while read Linha
do
Ultimo="$Linha"
echo -$Ultimo-"
done < $0 # Passando o arq. do script ($0) p/ while
echo "Acabou, Último=:$Ultimo:"
108
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
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 importante
macete que vou mostrar utilizando um exemplo prático. Suponha que você queira listar na tela um
arquivo e a cada dez registros esta listagem pararia para que o operador pudesse ler o conteúdo
da tela e ela só voltasse a rolar (scroll) após o operador digitar qualquer tecla. Para não gastar
muito papel (da Linux Magazine), 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
# Prg de teste para escrever
# 10 linhas e parar para ler
# Versão 1
while read Num
do
let ContLin++ # Contando...
echo -n "$Num "# -n para nao saltar linha
((ContLin % 10)) > /dev/null || read
done < numeros
Na tentativa de fazer um programa 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 da divisã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 aconteceu o seguinte problema:
$ 10porpag.sh
1 2 3 4 5 6 7 8 9 10 12 13 14 15 16 17 18 19 20 21 23 24 25 26 27 28 29 30
Repare que faltou o número 11 e a listagem não parou no read. O que houve foi que toda a
entrada do loop estava redirecionada do arquivo numeros e desta forma, a leitura foi feita em
109
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
cima deste arquivo, desta forma perdendo o 11 (e também o 22).
Vamos mostrar então como deveria ficar para funcionar:
$ cat 10porpag.sh
#!/bin/bash
# Prg de teste para escrever
# 10 linhas e parar para ler
# Versão 2
while read Num
do
let ContLin++ # Contando...
echo -n "$Num "# -n para nao saltar linha
((ContLin % 10)) > /dev/null || read < /dev/tty
done < numeros
Observe que agora a entrada do read foi redirecionada por /dev/tty, que nada mais é senão
o terminal corrente, explicitando desta forma que aquela leitura seria feita do teclado e não de
números. É bom realçar que isto não acontece somente quando usamos o redirecionamento de
entrada, se houvéssemos usado o redirecionamento via pipe (|), o mesmo teria ocorrido.
Veja agora a sua execução:
$ 10porpag.sh
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
Isto está quase bom mas falta um pouco para ficar excelente. Vamos melhorar um pouco o
exemplo para que você o reproduza e teste (mas antes de testar aumente o número de registros
de numeros ou reduza o tamanho da tela, para que haja quebra).
$ cat 10porpag.sh
#!/bin/bash
# Prg de teste para escrever
# 10 linhas e parar para ler
# Versão 3
clear
while read Num
do
((ContLin++)) # Contando...
echo "$Num"
((ContLin % (’tput lines’ - 3))) ||
read -n1 -p"Tecle Algo « /dev/tty # para ler qq caractere
clear # limpa a tela apos leitura
110
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
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 linhas,
listará 22 registros e parará para leitura. No comando read-n1 para ler somente um caractere
sem 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 deixar
um 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 julio.neves@gmail.com. Vou aproveitar também para mandar o meu jabá: diga para os
amigos que quem estiver afim de fazer um curso porreta de programação em Shell (de 40 horas)
que mande um e-mail para julio.neves@tecnohall.com.br para informar-se. Valeu!
111
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=$#Msg
local Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha
tput cup $LinhaMesg $Col
echo "$Msg"
tput cup $LinhaMesg $((Col + TamMsg + 1))
read -n1 SN
[ ! $SN ] && SN=$2 # Se vazia coloca default em SN
echo $SN | tr A-Z a-z # A saída de SN será em minúscula
tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
return # Sai da função
}
Como podemos ver, uma função é definida quando fazemos nome_da_função () e todo o seu
corpo 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 à passagem de parâmetros para programas, também valem
para funções, mas é muito importante realçar que os parâmetros passados para um programa
nã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
112
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
foram criadas como local. A finalidade disso é simplesmente para economizar memória, já que
ao sair da rotina, elas serão devidamente detonadas da partição e caso não tivesse usado este
artifício, permaneceriam residentes.
A linha de código que cria local Msg, concatena ao texto recebido ($1) um abre parênteses, a
resposta default ($2) em caixa alta, uma barra, a outra resposta ($3) em caixa baixa e finaliza
fechando 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 que
no 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=$#Msg
local Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha
tput cup $LinhaMesg $Col
echo "$Msg"
read -n1
tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
return # Sai da função
}
Esta é uma outra forma de definir uma função: não a chamamos como no exemplo anterior
usando 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 a
variável $* que como já sabemos é o conjunto de todos os parâmetros passados, para que o
programador não precise usar aspas envolvendo a mensagem que deseja passar para a função.
Para terminar com isso, vamos ver as alterações que o programa necessita quando usamos o
conceito de funções:
$ cat musinc6
#!/bin/bash
# Cadastra CDs (versao 6)
#
# Área de variáveis globais
LinhaMesg?=$((’tput lines’ - 3)) # Linha que msgs serão dadas para operador
113
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
TotCols?=$(tput cols) # Qtd colunas da tela para enquadrar msgs
# Área de funções
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
# abaixo colocaria em Msg o valor "Aceita? (S/n)"
local Msg="$1 (’echo $2 | tr a-z A-Z’/’echo $3 | tr A-Z a-z’)"
local TamMsg?=$#Msg
local Col=$(((TotCols? - TamMsg?) / 2)) # Centra msg na linha
tput cup $LinhaMesg $Col
echo "$Msg"
tput cup $LinhaMesg $((Col + TamMsg? + 1))
read -n1 SN
[ ! $SN ] && SN=$2 # Se vazia coloca default em SN
echo $SN | tr A-Z a-z # A saída de SN será em minúscula
tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
return # 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 linha
tput cup $LinhaMesg $Col
echo "$Msg"
read -n1
tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
return # Sai da função
}
# O corpo do programa propriamente dito começa aqui
clear
echo "
Inclusao de Músicas
==== == ===
114
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
Título do Álbum:
| Este campo foi
Faixa
< criado somente para
|orientar o preenchimento
Nome da Música:
Intérprete:"# Tela montada com um único echo
while true
do
tput cup 5 38; tput el # Posiciona e limpa linha
read Album
[ ! "$Album"] && # Operador deu
{
Pergunta "Deseja Terminar"s n
[ $SN = "n"] && continue # Agora só testo a caixa baixa
clear; exit # Fim da execução
}
grep -iq "^$Album^" musicas 2> /dev/null &&
{
MandaMsg? Este álbum já está cadastrado
continue # Volta para ler outro álbum
}
Reg="$Album^" # $Reg receberá os dados de gravação
oArtista= # Guardará artista anterior
while true
do
((Faixa++))
tput cup 7 38
echo $Faixa
tput cup 9 38 # Posiciona para ler musica
read Musica
[ "$Musica"] || # Se o operador tiver dado ...
{
Pergunta "Fim de Álbum?"s n
[ "$SN"= n ] && continue # Agora só testo a caixa baixa
break # Sai do loop para gravar dados
}
115
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 é default
read Artista
[ "$Artista"] && oArtista="$Artista"
Reg="$Reg$oArtista $Musica:"# Montando registro
tput cup 9 38; tput el # Apaga Musica da tela
tput cup 11 38; tput el # Apaga Artista da tela
done
echo "$Reg»> musicas # Grava registro no fim do arquivo
sort musicas -o musicas # Classifica o arquivo
done
Repare que a estruturação do _script_está conforme o gráfico a seguir:
Variáveis Globais
Funçõ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 dito
porque no ponto em que o programador mencionou seu nome, o interpretador Shell já o havia
localizado e registrado que era uma função.
Algo interessante no uso de funções é fazê-las o mais genérico possível de forma que elas sirvam
para outras aplicações, sem necessidade de serem reescritas. Essas duas que acabamos de ver
têm uso generalizado, pois é difícil um script que tenha uma entrada de dados pelo teclado que
não use uma rotina do tipo da MandaMsg ou não interage com o operador por algo semelhante à
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 de
programaçã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, volte a prestar atenção! Bem, já que você está mesmo sem vontade de
pensar e prefere ler a resposta, te darei uma dica: acho que você sabe que o .bash_profile é um
dos programas que são automaticamente "executados"quando você se loga. Agora que te dei
esta dica olhe novamente para a saída do ls e me diga o que há de diferente nela.
116
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 nenhum
direito de execução. Isso se dá porque 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 sob
o 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 lhe é passado como parâmetro.
Melhor um exemplo que 453 palavras. Veja este scriptizinho a seguir:
$ cat script_bobo
cd ..
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_bobo
jneves juliana paula silvie
$ pwd
/home/jneves
Se eu mandei ele subir um diretório, porque não subiu? Subiu sim! O subshellscript tanto su-
biu que 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:
$ source script_bobo
jneves juliana paula silvie
$ pwd
/home
$ cd -
/home/jneves
$ . script_bobo
jneves juliana paula silvie
$ pwd
/home
Ah! Agora sim! Sendo passado como parâmetro do comando source ou .script foi executado
no Shell corrente deixando neste, todo o ambiente criado. Agora damos um rewind para o início
da explicação sobre este comando. Lá falamos do .bash_profile, e a esta altura você já deve
saber que a sua incumbência é, logo após o login, deixar o ambiente de trabalho preparado para
o usuário, e agora entendemos que é por isso mesmo que ele é executado usando este artifício.
117
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 lhe
digo que sim, mas isso nos traz um monte de vantagens e uma das mais usadas é tratar funções
como rotinas externas. Veja uma outra forma de fazer o nosso programa para incluir CDs no
arquivo musicas:
$ cat musinc7
#!/bin/bash
# Cadastra CDs (versao 6)
#
# Área de variáveis globais
LinhaMesg?=$((’tput lines’ - 3)) # Linha que msgs serão dadas para operador
TotCols?=$(tput cols) # Qtd colunas da tela para enquadrar msgs
# O corpo do programa propriamente dito começa aqui
clear
echo "
Inclusao de Músicas
==== == ===
Título do Álbum:
| Este campo foi
Faixa
< criado somente para
|orientar o preenchimento
Nome da Música:
Intérprete:"# Tela montada com um único echo
while true
do
tput cup 5 38; tput el # Posiciona e limpa linha
read Album
[ ! "$Album"] && # Operador deu
{
source pergunta.func "Deseja Terminar"s n
[ $SN = "n"] && continue # Agora só testo a caixa baixa
clear; exit # Fim da execução
118
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
}
grep -iq "^$Album^" musicas 2> /dev/null &&
{
. mandamsg.func Este álbum já está cadastrado
continue # Volta para ler outro álbum
}
Reg="$Album^" # $Reg receberá os dados de gravação
oArtista= # Guardará artista anterior
while true
do
((Faixa++))
tput cup 7 38
echo $Faixa
tput cup 9 38 # Posiciona para ler musica
read Musica
[ "$Musica"] || # Se o operador tiver dado ...
{
. pergunta.func "Fim de Álbum?"s n
[ "$SN"= n ] && continue # Agora só testo a caixa baixa
break # Sai do loop para gravar dados
}
tput cup 11 38 # Posiciona para ler Artista
[ "$oArtista"]&& echo -n "($oArtista) "# Artista anterior é default
read Artista
[ "$Artista"] && oArtista="$Artista"
Reg="$Reg$oArtista $Musica:"# Montando registro
tput cup 9 38; tput el # Apaga Musica da tela
tput cup 11 38; tput el # Apaga Artista da tela
done
echo "$Reg»> musicas # Grava registro no fim do arquivo
sort musicas -o musicas # Classifica o arquivo
done
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 por
qualquer 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 dando
maior 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
# $2 - Valor a ser aceito com resposta default
119
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
# $3 - O outro valor aceito
# Supondo que $1=Aceita?, $2=s e $3=n, a linha
# abaixo colocaria em Msg o valor "Aceita? (S/n)"
Msg="$1 (’echo $2 | tr a-z A-Z’/’echo $3 | tr A-Z a-z’)"
TamMsg=$#Msg
Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha
tput cup $LinhaMesg $Col
echo "$Msg"
tput cup $LinhaMesg $((Col + zTamMsg + 1))
read -n1 SN
[ ! $SN ] && SN=$2 # Se vazia coloca default em SN
echo $SN | tr A-Z a-z # A saída de SN será em minúscula
tput 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=$#Msg
Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha
tput cup $LinhaMesg $Col
echo "$Msg"
read -n1
tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
Em ambos os arquivos, fiz somente duas 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 que só
pode ser usada no corpo de funções e, portanto, estas variáveis permanecem no ambiente
do 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ão
idênticos e significam rotina executada sem erros;
3. O comando que estamos acostumados a usar para gerar código de retorno é o exit, mas
a saída de uma rotina externa não pode ser feita desta forma, porque por estar sendo
executada no mesmo Shell que o script chamador, o exit simplesmente encerraria este
Shell, terminando a execução de todo o script;
4. De onde surgiu a variável LinhaMesg? Ela veio do musinc7, porque ela havia sido declarada
antes da chamada das rotinas (nunca esquecendo que o Shell que está interpretando o
script e estas rotinas é o mesmo);
5. Se você decidir usar rotinas externas, não se avexe, abunde os comentários (principalmente
120
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 novidades para melhorar os scriptslistartista no qual
você passava o nome de um artista como parâmetro e ele devolvia as suas músicas? Ele era
assim:
$ cat listartista
#!/bin/bash
# Dado um artista, mostra as suas musicas
# versao 2
if [ $# -eq 0 ]
then
echo Voce deveria ter passado pelo menos um parametro
exit 1
fi
IFS="
:"
for ArtMus in $(cut -f2 -d^ musicas)
do
echo "$ArtMus"| grep -i "^$*~" > /dev/null && echo $ArtMus verb=|= cut -f2 -d
done
- Claro que me lembro!...
- Então para firmar os conceitos que te passei, faça ele com a tela formatada, em loop, de forma
que ele só termine quando receber um <ENTER> puro no nome do artista. Ahhh! Quando a
listagem atingir a antepenúltima linha da tela, o programa deverá dar uma parada para que o
operador possa lê-las, isto é, suponha que a tela tenha 25 linhas. A cada 22 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 que
acabamos de desenvolver.
- Chico, manda mais dois, o meu é com pouca pressão...
121
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á
sei que 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 uma
instrução muito interessante - é o printf - sua sintaxe é a seguinte:
printf formato [argumento...]
Onde:
formato - é uma cadeia de caracteres que contém 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 caractere
% 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:
122
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 de
detalhes e, portanto, chato para explicar e, pior ainda para ler ou estudar, vamos passar direto
aos 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 "%cn1 caracter"
1 Saltou linha mas ainda não listou a cadeia inteira
$ printf "%c caracteren"1
1 caractere Esta é a forma correta o %c recebeu o 1
$ a=2
$ printf "%c caracteresn"$a
2 caracteres O %c recebeu o valor da variável $a
$ printf "%10c caracteresn"$a 2 caracteres
$ printf "%10cn"$a caracteres
2
c
Repare que nos dois últimos exemplos, em virtude do %c, só foi listado um caractere de cada
cadeia. 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 "%dn"32
32
$ printf "%10dn"32
32 Preenche com brancos à esquerda e não com zeros
$ printf "%04dn"32
0032 04 após % significa 4 dígitos com zeros à esquerda
$ printf "%en"$(echo "scale=2 ; 100/6" | bc)
1.666000e+01 O default do %e é 6 decimais
$ printf "%.2en"‘echo "scale=2 ; 100/6" | bc‘
1.67e+01 O .2 especificou duas decimais
$ printf "%fn"32.3
32.300000 O default do %f é 6 decimais
$ printf "%.2fn"32.3
123
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
32.30 O .2 especificou duas decimais $ printf "%.3fn"‘echo "scale=2 ; 100/6" |
bc‘
33.330 O bc devolveu 2 decimais. o printf colocou 0 à direita
$ printf "%on"10
12 Converteu o 10 para octal
$ printf "%03on"10octal, né?
$ printf "%sn"Peteleca
Peteleca
$ printf "%15sn"Peteleca
Peteleca Peteleca com 15 caracteres enchidos com brancos
$ printf "%-15sNevesn"Peteleca
Peteleca Neves O menos (-) encheu à direita com brancos
$ printf "%.3sn"Peteleca
Pet 3 trunca as 3 primeiras
$ printf "%10.3san"Peteleca
Peta Pet com 10 caracteres concatenado com a (após o s)
$ printf "EXEMPLO %xn"45232
EXEMPLO b0b0 Transformou para hexa mas os zeros não combinam
$ printf "EXEMPLO %Xn"45232
EXEMPLO B0B0 Assim disfarçou melhor (repare o X maiúsculo)
$ printf "%X %XL%Xn"49354 192 10
C0CA 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 como
resultado 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 que
o 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 lhe ajudar na elaboração de scripts. Então, as principais são:
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
124
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 do
locale).
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 o
próximo prompt primário (definido em $PS1). Se esta variável estiver sem valor ou com um
valor menor ou igual a zero, a verificação de novas correspondências não serão efetuada.
PATH Caminhos que serão pesquisados para tentar localizar um arquivo especificado. Como
cada script é um arquivo, caso use o diretório corrente (.) na sua variável $PATH, você não
necessitará de usar o ./scrp para que scrp seja executado. Basta fazer scrp. Este é o modo
que procedo aqui no Botequim.
PIPESTATUS É uma variável do tipo vetor (array) que contém uma lista valores de código de
retorno do último pipeline executado, isto é, um array que abriga cada um dos $? de cada
instruçã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 se
está repetindo muito uma determinada instrução.
PS1 É o prompt principal. No "Papo de Botequim"usamos os seus defaults: $ para usuário
comum 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 uma
googlada)
PS2 Também chamado prompt de continuação, é aquele sinal de maior (>) que aparece após
um <ENTER> sem o comando ter sido encerrado.
PWD Possui o caminho completo ($PATH) do diretório corrente. Tem o mesmo efeito do comando
pwd.
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.
125
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ário 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 timeout
do comando read. No prompt, este valor é interpretado como o tempo de espera por uma
ação antes de expirar a seção. Supondo que a variável contenha 30, o Shell dará logout
apó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
dos seus antecessores (., .. e ~), o cd foi executado para /usr/local/bin
• LANG
$ date
Thu Apr 14 11:54:13 BRT 2005
$ LANG=pt_BR date
Qui Abr 14 11:55:14 BRT 2005
Com a especificação da variável LANG=pt_BR (português do Brasil), a data passou a ser in-
formada 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
$ who
jneves pts/0 Apr 11 16:26 (10.2.4.144)
jneves pts/1 Apr 12 12:04 (10.2.4.144)
$ who | grep ^botelho
$ echo $PIPESTATUS[*]
0 1
Neste exemplo, mostramos que o usuário botelho não estava "logado", em seguida executa-
mos um pipeline que procurava por ele. Usa-se a notação [*] em um array para listar todos os
seus elementos, e desta forma vimos que a primeira instrução (who) foi bem sucedida (código de
retorno 0) 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))
126
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 da
divisão de qualquer número por 101 varia entre 0 e 100.
• REPLY
$ who
jneves pts/0 Apr 11 16:26 (10.2.4.144)
jneves pts/1 Apr 12 12:04 (10.2.4.144)
$ who | grep ^botelho
$ echo $PIPESTATUS[*]
0 1
$ read -p "Digite S ou N: "
Digite S ou N: N
$ echo $REPLY
N
Eu sou do tempo que memória era um bem precioso que custava muito caro. Então, para pe-
gar um S ou um N, não costumo a alocar um espaço especial e assim sendo, pego o que foi
digitado 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 ajudam muito,
facilitam a visualização, manutenção e depuração do código, mas não são tão eficientes quanto
os intrínsecos (built-ins). Quando o nosso problema for performance, devemos dar preferência ao
uso dos intrínsecos e a partir de agora te mostrarei algumas técnicas para o seu programa.
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, o
tr, o sed e outras de forma mais ágil.
127
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 for para a variá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 $#cadeia
4
• Para extrair de uma cadeia da posição um até o final, fazemos:
$ cadeia=abcdef
$ echo $cadeia:1
bcdef
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, a origem da contagem é zero e não um.
128
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 do
Shell (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 somente
Botequim:.
$ 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 como
no 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 texto
da seguinte forma:
echo Uso: $0##*/ texto da mensagem de erro
Neste exemplo, seria suprimido à esquerda tudo até a última barra (/) do caminho (path), desta
forma sobrando somente o nome do programa.
• O uso do percentual (%) é como se olhássemos o jogo-da-velha (#) no espelho, isto é, são
simétricos. Então vejamos um exemplo para provar isso:
129
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
$ echo $cadeia
Papo 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 sempre combinarão com a
maior possibilidade, veja o exemplo a seguir onde a intenção era trocar Papo de Botequim por
Conversa de Botequim:
$ echo $cadeia
Papo de Botequim
$ echo $cadeia/*o/Conversa
Conversatequim
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/Conversa
Conversa de Botequim
$ echo $cadeia/????/Conversa
Conversa de Botequim
• Trocando todas as ocorrências de uma subcadeia por outra. Quando fazemos:
$ echo $cadeia//o/a
Papa de Batequim
Trocamos todos as letras o por a. Outro exemplo mais útil é para contarmos a quantidade de
arquivos existentes no diretório corrente. Observe a linha a seguir:
$ ls | wc -l
30
Viu? O wc produz um monte de espaços em branco no início. Para tirá-los, podemos fazer:
130
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, montei
esta 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 $Passaro
quero quero
$ echo "Como diz o sulista - "$Passaro/#quero/não
Como diz o sulista - não quero
Para trocar no final, fazemos:
$ echo "Como diz o nordestino - "$Passaro/%quero/não
Como diz o nordestino - quero não
- Agora já chega, o papo hoje foi muito chato porque foi muita decoreba, mas o principal é você ter
entendido o que te falei e, quando precisar, consulte estes guardanapos em que rabisquei estas
dicas e depois guarde-os para consultas futuras. Mas voltando à vaca fria: tá na hora de tomar
outro e ver o jogo do mengão. Na próxima vou te dar moleza e só vou cobrar o seguinte: pegue
a rotina pergunta.func, (a que na qual falamos no início do nosso bate papo de hoje) e otimize-a
para 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.
131
Capítulo 14
Parte X
14.1 O comando eval
- Vou te mostrar 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 de
execuçã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 parecia bastante lógico...
- O seu raciocínio realmente foi lógico, o problema é que você esqueceu de uma das primeiras
coisas que te falei aqui no Boteco e vou repetir. O Shell usa a seguinte ordem para resolver uma
linha de comandos:
• Resolve os redirecionamentos;
• Substitui as 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
132
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 prompt
do terminal. Quando você põe o eval na frente, no entanto, o que ocorre é que o Shell trata cmd
como 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 $$var2
3
Na primeira passada a contrabarra () seria retirada e $var2 seria resolvido produzindo var1,
para a segunda passada teria sobrado echo $var1, que produziria o resultado esperado.
Agora vou colocar um comando dentro de var2:
$ var2=ls
Vou executar:
$ $var2
10porpag1.sh alo2.sh listamusica logaute.sh
10porpag2.sh confuso listartista mandamsg.func
10porpag3.sh contpal.sh listartista3 monbg.sh
alo1.sh incusu logado
Agora vamos colocar em var2 o seguinte: ls $var1; e em var1 vamos colocar l*, vejamos:
$ var2=’ls $var1’
$ var1=’l*’
$ $var2
ls: $var1: No such file or directory
$ eval $var2
listamusica listartista listartista3 logado logaute.sh
Novamente, no tempo de substituição das variáveis, $var1 ainda não havia se apresentado ao
133
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 duas
passadas 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 operador
escolhesse 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=1
printf "%11st%snn"Opção Programa
CASE=’case $opt in’
for arq in *.sh
do
printf "t%03dt%sn"$i $arq
CASE="$CASE
"$(printf "%03d)t %s;;"$i $arq)
i=$((i+1))
done
CASE="$CASE
*) . erro;;
esac"
read -n3 -p "Informe a opção desejada: "opt
echo
eval "$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 seguida come-
cei a montar dinamicamente a variável $CASE, na qual ao final será feito um eval para execução
do programa escolhido. Repare no entanto que dentro do loop do for existem dois printf: o pri-
meiro serve para formatar a tela e o segundo para montar o case (se antes do comando read
você colocar uma linha echo "$CASE", verá que o comando case montado dentro da variável
está todo indentado. Frescura, né?. Na saída do for, foi adicionada uma linha à variável $CASE,
para no caso de se fazer uma opção inválida, ser executada uma função externa para dar men-
sagens de erro.
Vamos executá-lo para ver a saída gerada:
$ fazmenu.sh
Opcao Programa
001 10porpag1.sh
002 10porpag2.sh
003 10porpag3.sh
134
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
004 alo1.sh
005 alo2.sh
006 contpal.sh
007 fazmenu.sh
008 logaute.sh
009 monbg.sh
010 readpipe.sh
011 redirread.sh
Informe a opção desejada:
Neste programa seria interessante darmos uma opção de término, e para isso seria necessário a
inclusão de uma linha após o loop de montagem da tela e alterarmos a linha na qual fazemos a
atribuiçã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=1
printf "%11st%snn" Opção Programa
CASE=’case $opt in’
for arq in *.sh
do
printf "t%03dt%sn" $i $arq
i=$((i+1))
done
printf "t%dt%snn"999 "Fim do programa"# Linha incluida
CASE="$CASE
999) exit;; # Linha alterada
*) ./erro;;
esac"
read -n3 -p "Informe a opção desejada: "opt
echo
eval "$CASE"
14.2 Sinais de Processos
Existe no Linux uma coisa chamada sinal (signal). Existem diversos sinais que podem ser
mandados para (ou gerados por) processos em execução. Vamos, de agora em diante, dar uma
olhadinha nos sinais mandados para os processos e mais à frente vamos dar uma passada rápida
pelos sinais gerados por processos.
135
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 comando
kill, algumas seqüências de teclas também podem gerar sig. A tabela a seguir mostra os sinais
mais importantes para monitorarmos:
.
Além destes sinais, existe o famigerado -9 ou SIGKILL que, para o processo que o está rece-
bendo, equivale a colocar o dedo no botão de desligar do computador o que seria altamente
indesejável já que muitos programas necessitam "limpar o meio de campo"ao seu término. Se o
seu final ocorrer 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 de
arquivos 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 anormais de
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 ... SN
ou
trap ’cmd1; cmd2; cmdn’ S1 S2 ... SN
Onde os comandos cmd1, cmd2, cmdn serão executados caso o programa receba os sinais
136
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
S1 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 podem ser também uma função interna, uma externa
ou outro script.
Para entender o uso de aspas (") e apóstrofos (') vamos recorrer a um exemplo que trata
um fragmento de um script que faz um ftp para uma máquina remota ($RemoComp), na qual o
usuário é $Fulano, sua senha é $Segredo e vai transmitir o arquivo contido em $Arq. Suponha
ainda que estas quatro variáveis foram recebidas em uma rotina anterior de leitura e que este
script é muito usado por diversas pessoas da instalação. Vejamos este trecho de código:
ftp -ivn $RemoComp « FimFTP » /tmp/$ $ 2» /tmp/$ $
user $Fulano $Segredo
binary
get $Arq
FimFTP
Repare que, tanto as saídas dos diálogos do ftp, como os erros encontrados, estão sendo re-
direcionados 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úmero do
processo (PID), que é único, e com este tipo de construção evita-se que dois ou mais usuários
disputem a posse e os direitos sobre o arquivo.
Caso este ftp seja interrompido por um kill ou um <CTRL+C>, certamente deixará lixo no disco.
É exatamente esta a forma como mais se usa o comando trap. Como isto é trecho de um script,
devemos, logo no seu 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 programa
encerrar (no exit dentro do comando trap) ou um fim normal (sinal 0), o arquivo /tmp/$ $ seria
removido.
Caso na linha de comandos do trap não houvesse a instrução exit, ao final da execução desta
linha o fluxo do programa retornaria ao ponto em que estava quando recebeu o sinal que originou
a execução deste trap.
Este trap poderia ser subdividido, ficando da seguinte forma:
trap "rm -f /tmp/$ $"0
trap "exit"1 2 3 15
Assim, ao receber um dos sinais o programa terminaria e ao terminar, geraria um sinal 0 que
removeria o arquivo. Caso seu fim seja normal, o sinal 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 é interpretado
137
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
(e é por isso que é usual colocá-lo no início do programa) e novamente quando um dos sinais
listados é recebido. Então, no último exemplo, o valor de $ $ será substituído no momento que o
comando trap foi lido da primeira vez, já que as aspas (") não protegem o cifrão ($) da interpre-
taçã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, o
Shell 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 script1:
Neste 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 interpretada
neste momento e o PID do script2 seria diferente do encontrado em /tmp/$ $ (não esqueça
que $ $ é a variável que contém o PID do processo ativo).
• 2º Caso: O ftp acima encontra-se em script2:
Neste caso, o argumento do comando trap deveria estar entre apóstrofos ('), pois caso a
interrupção se desse durante a execução de script1, o arquivo não teria sido criado, caso
ocorresse durante a execução de script2, o valor de $ $ seria o PID deste processo, que
coincidiria com o de /tmp/$ $.
O comando trap, quando executado sem argumentos, lista os sinais que estão sendo monitora-
dos no ambiente, bem como a linha de comando que será executada quando tais sinais forem
recebidos.
Se a linha de comandos do trap for nula (vazia), isto significa que os sinais especificados 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, quando
nã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 o
seguinte, 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ém
tomar 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.
138
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 for
gerado um sinal de interrupção, este não terá efeito nem sobre o Shell principal nem sobre os
Subshell 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 se
encontre 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 -echo
read Senha
stty 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
o programa e, caso ele agisse desta forma, o que quer que ele escrevesse, não apareceria na
tela do seu terminal. Para evitar que isso aconteça, o melhor a fazer é:
echo -n "Senha: "
trap "stty echo
exit"2 3
stty -echo
read Senha
stty echo
trap 2 3
Para terminar este assunto, abra uma console gráfica e escreva no prompt de comando o se-
guinte:
$ trap "echo Mudou o tamanho da janela"28
Em seguida, pegue o mouse e arraste-o de forma a variar o tamanho da janela corrente. Sur-
preso? É o Shell orientado a eventos
Agora escreva assim:
$ trap "echo já era"17
Em seguida faça:
$ sleep 3 &
139
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. Ao
fim deste tempo, você receberá a mensagem já era, porque o sinal 17 é emitido a cada vez que
um subshell termina a sua execução.
Para devolver estes sinais aos seus defaults, faça:
$ trap 17 28
Ou
$ trap ? 17 28
Acabamos de ver mais dois sinais que não são tão importantes como os que vimos anterior-
mente, mas vou registrá-los na tabela a seguir:
.
Muito legal este comando, né? Se você descobrir algum caso bacana de uso de sinais, por favor
me informe por e-mail porque é muito rara a literatura sobre o assunto.
14.5 O comando getopts
O comando getopts recupera as opções e seus argumentos de uma lista de parâmetros de
acordo com a sintaxe POSIX.2, isto é, letras (ou números) após um sinal de menos (-) seguidas
ou não de 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 da
letra, 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
140
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 comando
getopts 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ão
definida 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. Quando
o getopts obtém uma opção, ele armazena o índice do próximo parâmetro a ser processado na
variável OPTIND.
Quando uma opção tem um argumento associado (indicado pelo : na cadeiadeopcoes), getopts
armazena 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 de
có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_LETRA
do
echo "getopts fez a variavel OPT_LETRA igual a ’$OPT_LETRA’"
echo "OPTARG eh ’$OPTARG’"
done
used_up=’expr $OPTIND - 1’
echo "Dispensando os primeiros $OPTIND-1 = $used_up argumentos"
shift $used_up
141
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 arq2
getopts fez a variavel OPT_LETRA igual a ’h’
OPTARG eh ”
getopts fez a variavel OPT_LETRA igual a ’P’
OPTARG eh ’impressora’
Dispensando os primeiros $OPTIND-1 = 2 argumentos
O que sobrou da linha de comandos foi ’arq1 arq2’
Desta forma, sem ter muito trabalho, separei todas as 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) separado
da opção (-P), o resultado seria exatamente o mesmo, exceto pelo $OPTIND, já que neste caso
ele identifica um conjunto de três opções/argumentos e no anterior somente dois. Veja só:
$ getoptst.sh -h -P impressora arq1 arq2
getopts fez a variavel OPT_LETRA igual a ’h’
OPTARG eh ”
getopts fez a variavel OPT_LETRA igual a ’P’
OPTARG eh ’impressora’
Dispensando os primeiros $OPTIND-1 = 3 argumentos
O que sobrou da linha de comandos foi ’arq1 arq2’
Repare, no exemplo a seguir, que se passarmos uma opção inválida, a variável $OPT_LETRA
receberá 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 – f
getopts fez a variavel OPT_LETRA igual a ’?’
OPTARG eh ”
getopts fez a variavel OPT_LETRA igual a ’P’
OPTARG eh ’impressora’
Dispensando os primeiros $OPTIND-1 = 2 argumentos
O que sobrou da linha de comandos foi ’arq1 arq2’
- Me diz uma coisa: você 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 foi
didático, mas imagine um programa que aceitasse muitas opções e seus parâmetros poderiam
ou não estar colados às opções, suas opções também poderiam ou não estar coladas, ia ser um
case 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 tanta
informação nova na minha cabeça. Vamos tomar a saideira ou você ainda quer explicar alguma
particularidade do Shell?
- Nem um nem outro, eu também já cansei, mas hoje não vou tomar a saideira porque estou
142
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
indo dar aula na UniRIO, que é a primeira universidade federal que está preparando no uso de
Software Livre, seus alunos do curso de graduação em informática.
Mas antes vou te deixar um problema: 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 a minha conta.
143
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ônimo
de First In First Out que se refere à propriedade em que a ordem dos bytes entrando no pipe é a
mesma 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 pipe1
prw-r-r– 1 julio dipao 0 Jan 22 23:11 pipe1|
O p na coluna mais à esquerda indica que fifo1 é um named pipe. O resto dos bitspipe funci-
onam como um arquivo normal. Nos sistemas mais modernos uma barra vertical (|) colocado
ao fim do nome do arquivo, é outra dica, e nos sistemas LINUX, onde a 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, normalmente
situado no diretório /etc.
Nos sistemas mais modernos, a mesma tarefa é feita pelo mkfifo. O programa mkfifo recebe
um ou mais nomes como argumento e cria pipes com estes nomes. Por exemplo, para criar um
named pipe com o nome pipe1, faça:
$ mkfifo pipe1
Como sempre, a melhor forma de mostrar como algo funciona é dando exemplos. Suponha
que nós tenhamos criado o named pipe mostrado anteriormente. Vamos agora trabalhar com
duas seções ou duas consoles virtuais ou uma de cada. Em uma delas faça:
$ ls -l > pipe1
e em outra faça:
$ cat < pipe1
144
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
A saída do comando executado na primeira console foi exibida na segunda. Note que a 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ão,
o sistema operacional suspendeu o primeiro processo até que o segundo "abrisse"o pipe. Para
que um processo que usa pipe não fique em modo de wait, é necessário que em uma ponta do
pipe 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 um
determinado named pipe e ele só sairá, se outro processo "falar"ou "ouvir"aquele pipe.
Você já viu que o uso desta ferramenta é ótimo para sincronizar processos e para fazer bloqueio
em arquivos de forma a evitar perda/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
blocos de 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 BigFile3
do
if sort $Arq
then
Manda=va
145
CDTC Centro de Difusão de Tecnologia e Conhecimento Brasil/DF
else
Manda=pare
break
fi
done
echo $Manda > pipe1
[ $Manda = pare ] &&
{
echo Erro durante a classificação dos arquivos
exit 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 cadeia
pare é enviada pelo pipe1 e programa1 é descontinuado com um fim anormal.
Enquanto, o Programa1 executava o seu primeiro bloco (as classificações) o Programa2 exe-
cutava o seu BLOCO1, processando as suas rotinas de abertura e menu paralelamente ao Pro-
grama1, 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 OK
exit 1
fi
Após a execução de seu primeiro bloco, o Programa2 passará a "ouvir"o pipe1, ficando parado
até que as classificações do Programa1 terminem, testando a seguir a mensagem passada pelo
pipe1 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
quantos hits recebe uma determinada URL e a rotina de contagem está da seguinte maneira:
Hits="$(cat page.hits 2> /dev/null)"|| Hits=0
echo $((Hits=Hits++)) > page.hits
146
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 antes
da sua gravação, isto é, basta que o segundo acesso seja feito após o primeiro ter executado a
primeira 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 incrementar
o contador. Note que ele vai ser usado por qualquer página no nosso site que precise de um
contador.
$ cat contahits.sh
#!/bin/bash
PIPE="/tmp/pipe_contador"# arquivo named pipe
# dir onde serao colocados os arquivos contadores de cada pagina
DIR="/var/www/contador"
[ -p "$PIPE"] || mkfifo "$PIPE"
while :
do
for URL in $(cat < $PIPE)
do
FILE="$DIR/$(echo $URL | sed ’s,.*/„’)"
# OBS1: no sed acima, como precisava procurar
# uma barra,usamos vírgula como separador.
# OBS2: quando rodar como daemon comente a proxima linha
echo "arquivo = $FILE"
n="$(cat $FILE 2> /dev/null)"|| n=0
echo $((n=n+1)) > "$FILE"
done
done
Como só este script altera os arquivos, não existe problema de concorrência.
Este script será um daemon, isto é, rodará em background. Quando uma página sofrer um
acesso, ela escreverá a sua URL no arquivo de pipe. Para testar, execute este comando:
echo "teste_pagina.html» /tmp/pipe_contador
Para evitar erros, em cada página que quisermos adicionar o contador acrescentamos a seguinte
linha:
<!–#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.
147
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, Thobias
Salazar Trevisan que escreveu o script e colocou-o em sua excelente URL. Aconselho a todos
que querem aprender Shell a dar uma olhada nela (Dê uma olhada e inclua-a nos favoritos).
Ahhh! Você pensa que o assunto sobre named pipes está esgotado? Enganou-se. Vou mos-
trar um uso diferente a partir de agora.
15.4 Substituição de processos
Acabei várias 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 ou
um pipeline de comandos entre parênteses e um < ou um > grudado na frente do parêntese da
esquerda. Por exemplo, teclando-se o comando: $ cat <(ls -l)
Resultará no comando ls -l executado em um subshell como é normal (por estar entre 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 dispositivo
ló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 o
comando cmp, fazendo:
$ cmp <(cat dir/*) <(cat dir.bkp/*) || echo backup furado
ou, melhor ainda:
$ cmp <(cat dir/*) <(cat dir.bkp/*) >/dev/null || echo backup furado
Da forma acima, a 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 de
ambos os diretórios, pois qualquer modificação que um arquivo sofra é mostrada na data/hora de
alteração e/ou no tamanho do arquivo. Veja como ficaria:
$ cmp <(ls -l dir) <(ls -l dir.bkp) >/dev/null || echo backup furado
148
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 de
uma linha de saída, que serve como guia para outros. Eu quero gerar uma listagem dos meus
arquivos, numerando-os e ao final dar o total de arquivos do diretório corrente:
while read arq
do
((i++)) # assim nao eh necessario inicializar i
echo "$i: $arq"
done < <(ls)
echo "No diretorio corrente (’pwd’) existem $i arquivos"
Eu sei que existem outras formas de executar a mesma tarefa. Usando o comando while, a
forma mais comum de resolver esse problema seria:
ls | while read arq
do
((i++)) # assim nao eh necessario inicializar i
echo "$i: $arq"
done
echo "No diretorio corrente (’pwd’) existem $i arquivos"
Quando executasse o script, pareceria estar tudo certo, porém no comando echodone, você
verá que o valor de $i foi perdido. Isso deve-se ao fato desta variável estar sendo incrementada
em um subshell criado pelo pipe (|) e que terminou no comando done, levando com ele todas as
variáveis criadas no seu interior e as alterações feitas em todas as variáveis, inclusive as criadas
externamente.
Somente para te mostrar que uma variável criada fora do subshell e alterada em seu interior
perde as alterações feitas ao seu final, execute o script a seguir:
#!/bin/bash
LIST= # Criada no shell principal
ls | while read FILE # Inicio do subshell
do
LIST="$FILE $LIST"# Alterada dentro do subshell
done # Fim do subshell
echo :$LIST:
Ao final da execução você verá que aperecerão apenas dois-pontos (::). Mas no início deste
exemplo eu disse que era meramente didático porque existem formas melhores de fazer a mesma
tarefa. Veja só estas duas:
$ ls | ln
ou então, usando a própria substituição de processos:
$ cat -n <(ls)
149
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 este
comando 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 mal
dos políticos é só mandar um e-mail para julio.neves@gmail.com. Vou aproveitar também para
mandar o meu jabá: diga para os amigos que quem estiver afim de fazer um curso porreta de
programação em Shell que mande um e-mail para julio.neves@uniriotec.br para informar-se.
Valeu!
150

Shell script

  • 1.
  • 2.
    Sumário I Sobre essaApostila 4 II Informações Básicas 6 III GNU Free Documentation License 11 IV Shell Script 20 1 O que é Shell Script 21 2 Plano de ensino 22 2.1 Objetivo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.2 Público Alvo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.3 Pré-requisitos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.4 Descrição . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.5 Metodologia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.6 Programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.7 Avaliação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.8 Bibliografia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3 Introdução 25 4 Expressões Regulares 26 4.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.2 Comando grep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.3 Os Metacaracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 4.4 Metacaracteres quantificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 4.5 Metacaracteres tipo âncora . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 4.5.1 Circunflexo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 4.5.2 Cifrão - o fim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 4.5.3 Borda - a limítrofe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 4.6 Outros metacaracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 4.7 Explicando-os melhor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 4.7.1 Escape . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 4.7.2 Ou . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 4.7.3 Grupo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 1
  • 3.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 4.7.4 Retrovisor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 5 Parte I 35 5.1 Diálogo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5.2 O ambiente Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5.3 O ambiente Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 5.4 Uma rápida passagem nos principais sabores de Shell . . . . . . . . . . . . . . . . . 36 5.5 Explicando o funcionamento do Shell . . . . . . . . . . . . . . . . . . . . . . . . . . 37 5.6 Caracteres para remoção do significado . . . . . . . . . . . . . . . . . . . . . . . . . 39 5.7 Caracteres de redirecionamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 5.7.1 Caracteres de redirecionamento . . . . . . . . . . . . . . . . . . . . . . . . . 40 5.7.2 Redirecionamento da Saída Padrão . . . . . . . . . . . . . . . . . . . . . . . 40 5.7.3 Redirecionamento da Saída de erro Padrão . . . . . . . . . . . . . . . . . . . 41 5.7.4 Redirecionamento da Entrada Padrão . . . . . . . . . . . . . . . . . . . . . . 42 5.8 Redirecionamento de Comandos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 5.9 Caracteres de Ambiente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 6 Parte II 48 6.1 Diálogo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 6.2 Eu fico com o grep, você com a gripe . . . . . . . . . . . . . . . . . . . . . . . . . . 48 6.3 A família grep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 6.4 Exemplos da família grep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 6.5 Vamos montar uma cdteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 6.6 Passando parâmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 6.7 Macetes paramétricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 7 Parte III 59 7.1 Trabalhando com cadeias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 7.2 O comando cut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 7.2.1 O comando cut a opção -c . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 7.2.2 O comando cut a opção -f . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 7.3 O comando tr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 7.3.1 Trocando caracteres com tr . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 7.3.2 Removendo caracteres com tr . . . . . . . . . . . . . . . . . . . . . . . . . . 63 7.3.3 Xpremendo com tr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 7.3.4 O Comando if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 8 Parte IV 69 8.1 Diálogo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 8.2 O comando Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 8.3 Continuação do comando test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 8.4 Encolheram o comando condicional . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 8.5 E tome de test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 8.6 Acaso casa com case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 2
  • 4.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 9 Parte V 81 9.1 Comandos de Loop (ou laço) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 9.2 O Comando for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 9.2.1 Primeira sintaxe do comando for . . . . . . . . . . . . . . . . . . . . . . . . . 81 9.2.2 Segunda sintaxe do comando for . . . . . . . . . . . . . . . . . . . . . . . . . 86 9.2.3 Terceira sintaxe do comando for . . . . . . . . . . . . . . . . . . . . . . . . . 88 10 Parte VI 90 10.1 Um pouco mais de for e matemática . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 10.2 O Comando while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 10.3 O Comando Until . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 10.4 Atalhos no loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 11 Parte VII 101 11.1 O comando tput . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 11.2 E agora podemos ler os dados na tela . . . . . . . . . . . . . . . . . . . . . . . . . . 103 11.3 Vamos ler arquivos? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106 12 Parte VIII 112 12.1 Funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 12.2 O comando source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 13 Parte IX 122 13.1 Envenenando a escrita . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 13.2 Principais Variáveis do Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 13.3 Expansão de parâmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 14 Parte X 132 14.1 O comando eval . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 14.2 Sinais de Processos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 14.3 Sinais assassinos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 14.4 O trap não atrapalha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 14.5 O comando getopts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 15 Parte XI 144 15.1 Named Pipes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 15.2 Sincronização de processos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 15.3 Bloqueio de arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146 15.4 Substituição de processos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 3
  • 5.
  • 6.
    CDTC Centro deDifusã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 (http://www.cdtc.org.br.) O formato original deste material bem como sua atualização está disponível dentro da licença GNU Free Documentation License, cujo teor integral encontra-se aqui reproduzido na seção de mesmo nome, tendo inclusive uma versão traduzida (não oficial). A revisão e alteração vem sendo realizada pelo CDTC (suporte@cdtc.org.br) desde outubro de 2006. Críticas e sugestões construtivas serão bem-vindas a qualquer hora. Autores A autoria deste é de responsabilidade de Waldemar Silva Júnior (waldemar@cdtc.org.br). O texto original faz parte do projeto Centro de Difusão de Tecnologia e Conhecimento que vêm sendo realizado pelo ITI (Instituto Nacional de Tecnologia da Informação) em conjunto com outros parceiros institucionais, e com as universidades federais brasileiras que tem produzido e utilizado Software Livre apoiando inclusive a comunidade Free Software junto a outras entidades no país. Informações adicionais podem ser obtidas através do email ouvidoria@cdtc.org.br, ou da home page da entidade, através 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 responsabilizam direta ou indiretamente por qualquer prejuízo oriundo da utilização do material aqui contido. Licença Copyright ©2006, Instituto Nacional de Tecnologia da Informação (cdtc@iti.gov.br) . Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the 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 Documentation License. 5
  • 7.
  • 8.
    CDTC Centro deDifusã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 do desenvolvimento 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 e de código fonte aberto, identificando e mobilizando grupos de formadores de opinião dentre os servidores públicos e agentes políticos da União Federal, estimulando e incentivando o mercado nacional a adotar novos modelos de negócio da tecnologia da informação e de novos negócios de comunicação com base em software não-proprietário e de código fonte aberto, oferecendo treinamento 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 como incentivadores e defensores dos 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 de produtos de software não proprietários e do 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ê comece seu 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 o roteiro acima. Licença Copyright ©2006, Instituto Nacional de Tecnologia da Informação (cdtc@iti.gov.br). 7
  • 9.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF É dada permissão para copiar, distribuir e/ou modificar este documento sob os termos da Licença de Documentação Livre GNU, Versão 1.1 ou qualquer versão posterior públicada pela Free Software Foundation; com o Capitulo Invariante SOBRE ESSA APOSTILA. 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 seus colegas de turma respeitando-os e se fazendo ser respeitado pelos mesmos; • 5. Organização pessoal: planejar e organizar tudo é fundamental para facilitar a sua revisão e a sua recuperação de materiais; • 6. Vontade para realizar as atividades no tempo correto: anotar todas as suas obrigações e realizá-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, aprendizagens e 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ão controla 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ções que sejam pertinentes ao curso (avisos, notícias). As mensagens postadas nele são enviadas a 8
  • 10.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF todos participantes. Assim, se o monitor ou algum outro participante tiver uma informação que interesse ao grupo, favor postá-la aqui. Porém, se o que você deseja é resolver alguma dúvida ou discutir algum tópico específico do curso. É recomendado que você faça uso do Fórum de dúvidas gerais que lhe dá recursos mais efetivos para esta prática. . O fórum de dúvidas gerais tem por objetivo disponibilizar um meio fácil, rápido e interativo para solucionar suas dúvidas e trocar experiências. As mensagens postadas nele são enviadas a todos participantes do curso. Assim, fica muito mais fácil obter respostas, já que todos podem ajudar. Se você receber uma mensagem com algum tópico que saiba responder, não se preocupe com a formalizaçã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 podem ser 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, por pessoas 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 das ferramentas 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 respectivos valores e atitudes, incentiva mas é honesto, imparcial, amável, positivo, respeitador, aceita as idéias dos estudantes, é paciente, pessoal, tolerante, apreciativo, compreensivo e pronto a ajudar. 9
  • 11.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 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 tutor ou instrutor: • fornece explicações claras acerca do que ele espera e do estilo de classificação que irá utilizar; • 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 um reparo de um estudante: ’os comentários deixam-nos com uma sensação de crítica, de ameaç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
  • 12.
    Parte III GNU FreeDocumentation License 11
  • 13.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF (Traduzido pelo João S. O. Bueno através do CIPSGA em 2001) Esta é uma tradução não oficial da Licença de Documentação Livre GNU em Português Brasi- leiro. Ela não é publicada pela Free Software Foundation, e não se aplica legalmente a distribuição de textos que usem a GFDL - apenas o texto original em Inglês da GNU FDL faz isso. Entretanto, nós esperamos que esta tradução ajude falantes de português a entenderem melhor a GFDL. This is an unofficial translation of the GNU General Documentation License into Brazilian Por- tuguese. It was not published by the Free Software Foundation, and does not legally state the distribution terms for software that uses the GFDL–only the original English text of the GFDL does that. However, we hope that this translation will help Portuguese speakers understand the GFDL better. Licença de Documentação Livre GNU Versão 1.1, Março de 2000 Copyright (C) 2000 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA É permitido a qualquer um copiar e distribuir cópias exatas deste documento de licença, mas não é permitido alterá-lo. INTRODUÇÃO O propósito desta Licença é deixar um manual, livro-texto ou outro documento escrito "livre"no sentido de liberdade: assegurar a qualquer um a efetiva liberdade de copiá-lo ou redistribui-lo, com ou sem modificações, comercialmente ou não. Secundariamente, esta Licença mantém para o autor e editor uma forma de ter crédito por seu trabalho, sem ser considerado responsável pelas modificações feitas por terceiros. Esta Licença é um tipo de "copyleft"("direitos revertidos"), o que significa que derivações do documento precisam ser livres no mesmo sentido. Ela complementa a GNU Licença Pública Ge- ral (GNU GPL), que é um copyleft para software livre. Nós fizemos esta Licença para que seja usada em manuais de software livre, por que software livre precisa de documentação livre: um programa livre deve ser acompanhado de manuais que provenham as mesmas liberdades que o software possui. Mas esta Licença não está restrita a manuais de software; ela pode ser usada para qualquer trabalho em texto, independentemente do assunto ou se ele é publicado como um livro impresso. Nós recomendamos esta Licença prin- cipalmente para trabalhos cujo propósito seja de introdução ou referência. APLICABILIDADE E DEFINIÇÕES Esta Licença se aplica a qualquer manual ou outro texto que contenha uma nota colocada pelo detentor dos direitos autorais dizendo que ele pode ser distribuído sob os termos desta Licença. O "Documento"abaixo se refere a qualquer manual ou texto. Qualquer pessoa do público é um 12
  • 14.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF licenciado e é referida como "você". Uma "Versão Modificada"do Documento se refere a qualquer trabalho contendo o documento ou uma parte dele, quer copiada exatamente, quer com modificações e/ou traduzida em outra língua. Uma "Seção Secundária"é um apêndice ou uma seção inicial do Documento que trata ex- clusivamente da relação dos editores ou dos autores do Documento com o assunto geral do Documento (ou assuntos relacionados) e não contém nada que poderia ser incluído diretamente nesse assunto geral (Por exemplo, se o Documento é em parte um livro texto de matemática, a Seção Secundária pode não explicar nada de matemática). Essa relação poderia ser uma questão de ligação histórica com o assunto, ou matérias relaci- onadas, ou de posições legais, comerciais, filosóficas, éticas ou políticas relacionadas ao mesmo. As "Seções Invariantes"são certas Seções Secundárias cujos títulos são designados, como sendo de Seções Invariantes, na nota que diz que o Documento é publicado sob esta Licença. Os "Textos de Capa"são certos trechos curtos de texto que são listados, como Textos de Capa Frontal ou Textos da Quarta Capa, na nota que diz que o texto é publicado sob esta Licença. Uma cópia "Transparente"do Documento significa uma cópia que pode ser lida automatica- mente, representada num formato cuja especificação esteja disponível ao público geral, cujos conteúdos possam ser vistos e editados diretamente e sem mecanismos especiais com editores de texto genéricos ou (para imagens compostas de pixels) programas de pintura genéricos ou (para desenhos) por algum editor de desenhos grandemente difundido, e que seja passível de servir como entrada a formatadores de texto ou para tradução automática para uma variedade de formatos que sirvam de entrada para formatadores de texto. Uma cópia feita em um formato de arquivo outrossim Transparente cuja constituição tenha sido projetada para atrapalhar ou de- sencorajar modificações subsequentes pelos leitores não é Transparente. Uma cópia que não é "Transparente"é chamada de "Opaca". Exemplos de formatos que podem ser usados para cópias Transparentes incluem ASCII sim- ples sem marcações, formato de entrada do Texinfo, formato de entrada do LaTex, SGML ou XML usando uma DTD disponibilizada publicamente, e HTML simples, compatível com os padrões, e projetado para ser modificado por pessoas. Formatos opacos incluem PostScript, PDF, formatos proprietários que podem ser lidos e editados apenas com processadores de texto proprietários, SGML ou XML para os quais a DTD e/ou ferramentas de processamento e edição não estejam disponíveis para o público, e HTML gerado automaticamente por alguns editores de texto com finalidade apenas de saída. A "Página do Título"significa, para um livro impresso, a página do título propriamente dita, mais quaisquer páginas subsequentes quantas forem necessárias para conter, de forma legível, o material que esta Licença requer que apareça na página do título. Para trabalhos que não tenham uma página do título, "Página do Título"significa o texto próximo da aparição mais proe- minente do título do trabalho, precedendo o início do corpo do texto. 13
  • 15.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF FAZENDO CÓPIAS EXATAS Você pode copiar e distribuir o Documento em qualquer meio, de forma comercial ou não comercial, desde que esta Licença, as notas de copyright, e a nota de licença dizendo que esta Licença se aplica ao documento estejam reproduzidas em todas as cópias, e que você não acres- cente nenhuma outra condição, quaisquer que sejam, às desta Licença. Você não pode usar medidas técnicas para obstruir ou controlar a leitura ou confecção de cópias subsequentes das cópias que você fizer ou distribuir. Entretanto, você pode aceitar com- pensação em troca de cópias. Se você distribuir uma quantidade grande o suficiente de cópias, você também precisa respeitar as condições da seção 3. Você também pode emprestar cópias, sob as mesmas condições colocadas acima, e também pode exibir cópias publicamente. FAZENDO CÓPIAS EM QUANTIDADE Se você publicar cópias do Documento em número maior que 100, e a nota de licença do Documento obrigar Textos de Capa, você precisará incluir as cópias em capas que tragam, clara e legivelmente, todos esses Textos de Capa: Textos de Capa da Frente na capa da frente, e Textos da Quarta Capa na capa de trás. Ambas as capas também precisam identificar clara e legivelmente você como o editor dessas cópias. A capa da frente precisa apresentar o título com- pleto com todas as palavras do título igualmente proeminentes e visíveis. Você pode adicionar outros materiais às capas. Fazer cópias com modificações limitadas às capas, tanto quanto estas preservem o título do documento e satisfaçam a essas condições, pode ser tratado como cópia exata em outros aspectos. Se os textos requeridos em qualquer das capas for muito volumoso para caber de forma legível, você deve colocar os primeiros (tantos quantos couberem de forma razoável) na capa verdadeira, e continuar os outros nas páginas adjacentes. Se você publicar ou distribuir cópias Opacas do Documento em número maior que 100, você precisa ou incluir uma cópia Transparente que possa ser lida automaticamente com cada cópia Opaca, ou informar, em ou com, cada cópia Opaca a localização de uma cópia Transparente completa do Documento acessível publicamente em uma rede de computadores, à qual o público usuário de redes tenha acesso a download gratuito e anônimo utilizando padrões públicos de protocolos de rede. Se você utilizar o segundo método, você precisará tomar cuidados razoavel- mente prudentes, quando iniciar a distribuição de cópias Opacas em quantidade, para assegurar que esta cópia Transparente vai permanecer acessível desta forma na localização especificada por pelo menos um ano depois da última vez em que você distribuir uma cópia Opaca (direta- mente ou através de seus agentes ou distribuidores) daquela edição para o público. É pedido, mas não é obrigatório, que você contate os autores do Documento bem antes de redistribuir qualquer grande número de cópias, para lhes dar uma oportunidade de prover você com uma versão atualizada do Documento. 14
  • 16.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF MODIFICAÇÕES Você pode copiar e distribuir uma Versão Modificada do Documento sob as condições das se- ções 2 e 3 acima, desde que você publique a Versão Modificada estritamente sob esta Licença, com a Versão Modificada tomando o papel do Documento, de forma a licenciar a distribuição e modificação da Versão Modificada para quem quer que possua uma cópia da mesma. Além disso, você precisa fazer o seguinte na versão modificada: A. Usar na Página de Título (e nas capas, se houver alguma) um título distinto daquele do Do- cumento, e daqueles de versões anteriores (que deveriam, se houvesse algum, estarem listados na seção "Histórico do Documento"). Você pode usar o mesmo título de uma versão anterior se o editor original daquela versão lhe der permissão; B. Listar na Página de Título, como autores, uma ou mais das pessoas ou entidades responsá- veis pela autoria das modificações na Versão Modificada, conjuntamente com pelo menos cinco dos autores principais do Documento (todos os seus autores principais, se ele tiver menos que cinco); C. Colocar na Página de Título o nome do editor da Versão Modificada, como o editor; D. Preservar todas as notas de copyright do Documento; E. Adicionar uma nota de copyright apropriada para suas próprias modificações adjacente às outras notas de copyright; F. Incluir, imediatamente depois das notas de copyright, uma nota de licença dando ao público o direito de usar a Versão Modificada sob os termos desta Licença, na forma mostrada no tópico abaixo; G. Preservar nessa nota de licença as listas completas das Seções Invariantes e os Textos de Capa requeridos dados na nota de licença do Documento; H. Incluir uma cópia inalterada desta Licença; I. Preservar a seção entitulada "Histórico", e seu título, e adicionar à mesma um item dizendo pelo menos o título, ano, novos autores e editor da Versão Modificada como dados na Página de Título. Se não houver uma sessão denominada "Histórico"no Documento, criar uma dizendo o título, ano, autores, e editor do Documento como dados em sua Página de Título, então adicionar um item descrevendo a Versão Modificada, tal como descrito na sentença anterior; J. Preservar o endereço de rede, se algum, dado no Documento para acesso público a uma cópia Transparente do Documento, e da mesma forma, as localizações de rede dadas no Docu- mento para as versões anteriores em que ele foi baseado. Elas podem ser colocadas na seção "Histórico". Você pode omitir uma localização na rede para um trabalho que tenha sido publicado pelo menos quatro anos antes do Documento, ou se o editor original da versão a que ela se refira der sua permissão; K. Em qualquer seção entitulada "Agradecimentos"ou "Dedicatórias", preservar o título da 15
  • 17.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF seção e preservar a seção em toda substância e fim de cada um dos agradecimentos de contri- buidores e/ou dedicatórias dados; L. Preservar todas as Seções Invariantes do Documento, inalteradas em seus textos ou em seus títulos. Números de seção ou equivalentes não são considerados parte dos títulos da seção; M. Apagar qualquer seção entitulada "Endossos". Tal sessão não pode ser incluída na Versão Modificada; N. Não reentitular qualquer seção existente com o título "Endossos"ou com qualquer outro título dado a uma Seção Invariante. Se a Versão Modificada incluir novas seções iniciais ou apêndices que se qualifiquem como Seções Secundárias e não contenham nenhum material copiado do Documento, você pode optar por designar alguma ou todas aquelas seções como invariantes. Para fazer isso, adicione seus títulos à lista de Seções Invariantes na nota de licença da Versão Modificada. Esses títulos preci- sam ser diferentes de qualquer outro título de seção. Você pode adicionar uma seção entitulada "Endossos", desde que ela não contenha qual- quer coisa além de endossos da sua Versão Modificada por várias pessoas ou entidades - por exemplo, declarações de revisores ou de que o texto foi aprovado por uma organização como a definição oficial de um padrão. Você pode adicionar uma passagem de até cinco palavras como um Texto de Capa da Frente , e uma passagem de até 25 palavras como um Texto de Quarta Capa, ao final da lista de Textos de Capa na Versão Modificada. Somente uma passagem de Texto da Capa da Frente e uma de Texto da Quarta Capa podem ser adicionados por (ou por acordos feitos por) qualquer entidade. Se o Documento já incluir um texto de capa para a mesma capa, adicionado previamente por você ou por acordo feito com alguma entidade para a qual você esteja agindo, você não pode adicionar um outro; mas você pode trocar o antigo, com permissão explícita do editor anterior que adicionou a passagem antiga. O(s) autor(es) e editor(es) do Documento não dão permissão por esta Licença para que seus nomes sejam usados para publicidade ou para assegurar ou implicar endossamento de qualquer Versão Modificada. COMBINANDO DOCUMENTOS Você pode combinar o Documento com outros documentos publicados sob esta Licença, sob os termos definidos na seção 4 acima para versões modificadas, desde que você inclua na com- binação todas as Seções Invariantes de todos os documentos originais, sem modificações, e liste todas elas como Seções Invariantes de seu trabalho combinado em sua nota de licença. O trabalho combinado precisa conter apenas uma cópia desta Licença, e Seções Invariantes Idênticas com multiplas ocorrências podem ser substituídas por apenas uma cópia. Se houver múltiplas Seções Invariantes com o mesmo nome mas com conteúdos distintos, faça o título de 16
  • 18.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF cada seção único adicionando ao final do mesmo, em parênteses, o nome do autor ou editor origianl daquela seção, se for conhecido, ou um número que seja único. Faça o mesmo ajuste nos títulos de seção na lista de Seções Invariantes nota de licença do trabalho combinado. Na combinação, você precisa combinar quaisquer seções entituladas "Histórico"dos diver- sos documentos originais, formando uma seção entitulada "Histórico"; da mesma forma combine quaisquer seções entituladas "Agradecimentos", ou "Dedicatórias". Você precisa apagar todas as seções entituladas como "Endosso". COLETÂNEAS DE DOCUMENTOS Você pode fazer uma coletânea consitindo do Documento e outros documentos publicados sob esta Licença, e substituir as cópias individuais desta Licença nos vários documentos com uma única cópia incluida na coletânea, desde que você siga as regras desta Licença para cópia exata de cada um dos Documentos em todos os outros aspectos. Você pode extrair um único documento de tal coletânea, e distribuí-lo individualmente sob esta Licença, desde que você insira uma cópia desta Licença no documento extraído, e siga esta Licença em todos os outros aspectos relacionados à cópia exata daquele documento. AGREGAÇÃO COM TRABALHOS INDEPENDENTES Uma compilação do Documento ou derivados dele com outros trabalhos ou documentos se- parados e independentes, em um volume ou mídia de distribuição, não conta como uma Ver- são Modificada do Documento, desde que nenhum copyright de compilação seja reclamado pela compilação. Tal compilação é chamada um "agregado", e esta Licença não se aplica aos outros trabalhos auto-contidos compilados junto com o Documento, só por conta de terem sido assim compilados, e eles não são trabalhos derivados do Documento. Se o requerido para o Texto de Capa na seção 3 for aplicável a essas cópias do Documento, então, se o Documento constituir menos de um quarto de todo o agregado, os Textos de Capa do Documento podem ser colocados em capas adjacentes ao Documento dentro do agregado. Senão eles precisarão aparecer nas capas de todo o agregado. TRADUÇÃO Tradução é considerada como um tipo de modificação, então você pode distribuir traduções do Documento sob os termos da seção 4. A substituição de Seções Invariantes por traduções requer uma permissão especial dos detentores do copyright das mesmas, mas você pode incluir traduções de algumas ou de todas as Seções Invariantes em adição às versões orignais dessas Seções Invariantes. Você pode incluir uma tradução desta Licença desde que você também in- clua a versão original em Inglês desta Licença. No caso de discordância entre a tradução e a 17
  • 19.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF versão original em Inglês desta Licença, a versão original em Inglês prevalecerá. TÉRMINO Você não pode copiar, modificar, sublicenciar, ou distribuir o Documento exceto como expres- samente especificado sob esta Licença. Qualquer outra tentativa de copiar, modificar, sublicen- ciar, ou distribuir o Documento é nula, e resultará automaticamente no término de seus direitos sob esta Licença. Entretanto, terceiros que tenham recebido cópias, ou direitos de você sob esta Licença não terão suas licenças terminadas, tanto quanto esses terceiros permaneçam em total acordo com esta Licença. REVISÕES FUTURAS DESTA LICENÇA A Free Software Foundation pode publicar novas versões revisadas da Licença de Documen- tação Livre GNU de tempos em tempos. Tais novas versões serão similares em espirito à versão presente, mas podem diferir em detalhes ao abordarem novos porblemas e preocupações. Veja http://www.gnu.org/copyleft/. A cada versão da Licença é dado um número de versão distinto. Se o Documento especificar que uma versão particular desta Licença "ou qualquer versão posterior"se aplica ao mesmo, você tem a opção de seguir os termos e condições daquela versão específica, ou de qualquer versão posterior que tenha sido publicada (não como rascunho) pela Free Software Foundation. Se o Documento não especificar um número de Versão desta Licença, você pode escolher qualquer versão já publicada (não como rascunho) pela Free Software Foundation. ADENDO: Como usar esta Licença para seus documentos Para usar esta Licença num documento que você escreveu, inclua uma cópia desta Licença no documento e ponha as seguintes notas de copyright e licenças logo após a página de título: Copyright (c) ANO SEU NOME. É dada permissão para copiar, distribuir e/ou modificar este documento sob os termos da Licença de Documentação Livre GNU, Versão 1.1 ou qualquer versão posterior publicada pela Free Soft- ware Foundation; com as Seções Invariantes sendo LISTE SEUS TÍTULOS, com os Textos da Capa da Frente sendo LISTE, e com os Textos da Quarta-Capa sendo LISTE. Uma cópia da li- cença está inclusa na seção entitulada "Licença de Documentação Livre GNU". Se você não tiver nenhuma Seção Invariante, escreva "sem Seções Invariantes"ao invés de dizer quais são invariantes. Se você não tiver Textos de Capa da Frente, escreva "sem Textos de Capa da Frente"ao invés de "com os Textos de Capa da Frente sendo LISTE"; o mesmo para os Textos da Quarta Capa. Se o seu documento contiver exemplos não triviais de código de programas, nós recomenda- mos a publicação desses exemplos em paralelo sob a sua escolha de licença de software livre, 18
  • 20.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF tal como a GNU General Public License, para permitir o seu uso em software livre. 19
  • 21.
  • 22.
    Capítulo 1 O queé Shell Script Shell é o nome que se dá à linha de comando em modo texto dos sistemas operacionais Linux e UNIX. E, portanto, os shell scripts são um meio de se juntar uma porção de comandos shell em um só 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 de sistemas Unix e Linux é mais poderosa. 21
  • 23.
    Capítulo 2 Plano deensino 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 em geral. 2.3 Pré-requisitos Os usuários deverão ser, necessariamente, funcionários públicos e ter conhecimentos básicos para 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 Moodle como ferramenta de aprendizagem. O curso tem duração de uma semana e possui um conjunto de atividades (lições, fóruns, glossários, questionários e outros) que deverão ser executadas de acordo com as instruções fornecidas. O material didático está disponível on-line de acordo com as datas pré-estabelecidas em cada tópico. 2.5 Metodologia O curso está dividido da seguinte maneira: Duração Descrição do Módulo 1 semana Expressões Regulares 2 semana Ambiente Shell e Comandos de manipulação de caracteres Todo o material está no formato de lições, e estará disponível ao longo do curso. Ao final de cada semana do curso será disponibilizada a prova referente ao módulo estudado anteriormente 22
  • 24.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF que também conterá perguntas sobre os textos indicados. Utilize o material de cada semana e os exemplos disponibilizados para se preparar para prova. As lições contêm o conteúdo principal. Elas poderão ser acessadas quantas vezes forem neces- sárias, desde que estejam 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 seja menor 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ções quanto a da avaliação serão consideradas para a nota final. Todos os módulos ficarão visíveis para 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á ser enviada ao 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. 2.7 Avaliação Toda a avaliação será feita on-line. Aspectos a serem considerados na avaliação: • Expressões Regulares; 23
  • 25.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF • 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 e obtenção do certificado o participante deverá obter nota final maior ou igual a 6.0 de acordo com 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 24
  • 26.
    Capítulo 3 Introdução Sejam bemvindos ao curso de Bash Básico! O curso tem duração de apenas duas semanas e consiste na leitura de alguns livros e lições e, ao término delas, realizar algumas avaliações. Todas essas atividades valem pontuação. Para um 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 que aprender brincando. É interessante que já esteja instalado o Linux e que deixe o terminal sempre aberto. Assim facilita acompanhar o raciocínio da leitura. Qualquer dúvida quanto ao comando pode ser resolvida digitando no Shell: $ man nomedocomando ou ainda discutindo concosco no fórum. Assim aprendemos juntos a funcionalidade de cada um. Bom trabalho! 25
  • 27.
    Capítulo 4 Expressões Regulares 4.1Introduçã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 a todas 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]ato pode 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 ser praticado. 4.2 Comando grep Para não precisar listar todo o conteúdo de um arquivo por completo para apenas saber os dados do usuário "root", pode-se usar o grep para pesquisar e retornar somente a linha dele. O 26
  • 28.
    CDTC Centro deDifusã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 sistema UNIX/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/passwd root:x:0:0:root:/root:/bin/bash 4.3 Os Metacaracteres Cada metacaracteres é uma ferramenta que tem uma função específica. Servem para dar mais poder às pesquisas, informando padrões e posições impossíveis de se especificar usando somente 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 um individualmente, 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 importa com quem seja. Pode ser um número, uma letra, um TAB, um , o que vier ele traça, pois o ponto casa qualquer coisa. Exemplos: "n.o"casaria: não, nao, ... 27
  • 29.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF ".eclado"casaria: teclado, Teclado, ... "12.45"casaria: 12:45, 12 45, 12.45, ... A lista Bem mais exigente que o ponto, a lista não casa com qualquer um. Ela sabe exatamente o que quer, e nada diferente daquilo, a lista casa com quem ela conhece. Toda "lista"(os colchetes e seu 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 negada A lista negada é exatamente igual à lista, podendo ter caracteres literais, intervalos e classes POSIX. 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 os componentes listados. Observe que a diferença em sua notação é que o primeiro caractere da lista é um circunflexo, ele indica que esta é uma lista negada. Então, se [0-9] são números, [^0-9] é qualquer coisa fora nú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ício de uma linha. Mas tem de ser alguma coisa. Só porque ela é uma lista negada isso não significa que 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írgula ou 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 Metacaracteres quantificadores Os quantificadores servem para indicar o número de repetições permitidas para a entidade imediatamente anterior. Essa entidade pode ser um caractere ou metacaractere. Em outras pa- lavras, eles dizem a quantidade de repetições que o átomo anterior pode ter, quantas vezes ele pode aparecer. São eles: opcional ? asterisco * mais + 28
  • 30.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF chaves Opcional É útil para procurar palavras no singular e plural e pode ser tornar opcionais caracteres e me- tacaracteres. Exemplos: Expressão Casa com Ondas? Onda Ondas Senadora? 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 menos uma 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ão precisas 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 que isso. 0,1 zero ou 1 (igual ao opcional) 0, zero ou mais (igual ao asterisco) 1, um ou mais (igual ao mais) 3 exatamente 29
  • 31.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 4.5 Metacaracteres tipo âncora São aqueles que não casam caracteres ou definem quantidades, ao invés disso eles marcam uma 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 da lista (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 no final 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"seguida de um fim de linha. Esse cifrão é o mesmo caractere que é utilizado para identificar as variáveis do shell, como $PWD e $HOME. Para evitar possíveis problemas com a expansão de variáveis, é preciso "proteger"a expressão regular passada ao grep. A proteção é feita colocando-se a ER entre ’aspas simples’ fazendo: $ grep ’bash$’ /etc/passwd Veja outros exemplos: 30
  • 32.
    CDTC Centro deDifusã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 útil para casar palavras exatas, e não partes de palavras. Palavra aqui é um conceito que engloba apenas 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! • diab - - - dia, melodia, bom-dia! • bdiab — dia, bom-dia! 4.6 Outros metacaracteres Vamos ver outros metacaracteres, que têm funções específicas e não relacionadas entre si, portanto não podem ser agrupados em outra classe fora a tradicional "outros". Mas atenção, isso não quer dizer que eles são inferiores, pelo contrário, o poder das ERs é multiplicado com seu uso 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* 31
  • 33.
    CDTC Centro deDifusã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-se 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 os parê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, podemos 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ê: "ou boa-tarde, ou boa-noite", ou seja "ou isso ou aquilo". Lembre que a lista também é uma espécie de 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 tem o dom de juntar vários tipos de sujeitos em um mesmo local. Dentro de um grupo podemos ter um ou mais caracteres, metacarateres e inclusive outros grupos. Como em uma expressão matemática, os parênteses definem um grupo e seu conteúdo pode ser visto como um bloco na expressã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ência limitada 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 que casamos várias repetições da palavra, como ai, aiai, aiaiai, ... E assim podemos agrupar tudo o 32
  • 34.
    CDTC Centro deDifusã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 um supermercado 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 hiper temos um trecho per comum aos dois, então podíamos "alternativizar"apenas as diferenças su e hi: (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 de 1 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": http://www.redhat.com/docs/manuals/linux/RHL-7.2-Manual/custom-guide/rescuemode.html ([A- 33
  • 35.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Za-z]+)-1 Viu o poder dessa ER? Ela casa palavras repetidas separadas por um traço, como o próprio quero-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 palavras repetidas 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 pre- cisamos usar as bordas para completar nossa ER: b([A-Za-z]+) 1b http://www.redhat.com/docs/manuals/linux/RHL-7.2-Manual/custom-guide/rescuemode.html Como já dito, podemos usar no máximo nove retrovisores. Vamos ver uns exemplos com mais de um 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 vezes nem sabe. O brinde é o trecho de texto casado pela ER que está no grupo, que fica guardado em um cantinho especial 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ém como 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 a ER. 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. 34
  • 36.
    Capítulo 5 Parte I 5.1Diálogo Diálogo entre 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 operacional que você tem que dar dez boots por dia e não tem domínio nenhum sobre o que está acontecendo no seu computador. Mas deixa isso prá lá, vou te explicar o que é Shell e os componentes de sua família e ao final da explanação você dirá: "Meu Deus do Shell! Porque eu não optei pelo Linux antes?". 5.2 O ambiente Linux Para você entender o que é e como funciona o Shell, primeiro será mostrado como funciona o ambiente em camadas do Linux. Dê uma olhada no gráfico abaixo: 35
  • 37.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF . Neste gráfico dá para ver que a camada de hardware é a mais profunda e é formada pelos com- ponentes físicos do seu computador. Envolvendo esta, vem a camada do kernel que é o cerne do Linux, seu núcleo, é quem coloca o hardware para funcionar fazendo seu gerenciamento e con- trole. Os programas e comandos que envolvem o kernel, dele se utilizam para realizar as tarefas aplicativas para que foram desenvolvidos. Fechando tudo isso vem o Shell que leva este nome porque em inglês, Shell significa concha, carapaça, isto é, fica entre o usuário e o sistema opera- cional, de forma que tudo que interage com o sistema operacional, tem que passar pelo seu crivo. http://www.redhat.com/docs/manuals/linux/RHL-7.2-Manual/custom-guide/rescuemode.html 5.3 O ambiente Shell Bom já que para chegar ao núcleo do Linux, no seu kernel que é o que interessa a todo apli- cativo, é necessária a filtragem do Shell, vamos entender como ele funciona de forma a tirar o máximo proveito das inúmeras facilidades que ele nos oferece. O Linux por definição é um sistema multiusuário - não podemos nunca esquecer disto - e para per- mitir o acesso de determinados usuários e barrar a entrada de outros, existe um arquivo chamado /etc/passwd que além de fornecer dados para esta função de "leão-de-chácara"do Linux, tam- bé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 receberá ao se "logar"(ARGH!!!). Lembra que foi falado de Shell, família, irmão? Pois é, vamos começar a entender isto: o Shell, 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 sistema operacional Unix foram aparecendo. Atualmente existem diversos sabores de Shell, dentre estes é destacado o sh (Bourne Shell), o ksh (Korn Shell), bash (Bourne Again Shell) e o csh (C Shell). 5.4 Uma rápida passagem 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 de Standard Shell por ter sido durante vários anos o único e até hoje é o mais utilizado até porque ele foi portado para todos os ambientes Unix e distros Linux. Korn Shell (ksh) Desenvolvido por David Korn, também da Bell Labs, é um superset do sh, isto é, possui todas as facilidades do sh e a elas agregou muitas outras. A compatibilidade total com o sh vem trazendo muitos 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 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. C 36
  • 38.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Shell (csh) Desenvolvido por Bill Joy da Berkley University é o Shell mais utilizado em ambientes *BSD e Xenix. A estruturação de seus comandos é bem similar à da linguagem C. Seu grande pecado foi ignorar a compatibilidade com o sh, partindo por um caminho próprio. Além destes Shells existem outros, mas, neste curso, será abordado somente sobre os três pri- meiros tratando-os genericamente por Shell e assinalando as especificidades de cada um que porventura hajam. Atenção: Quando foi dito que o último campo do /etc/passwd informa ao sistema qual é o Shell que o usuário 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á imediatamente um 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 resolverá vá- rias coisas de forma a não onerar o kernel com tarefas repetitivas, aliviando-o para tratar assuntos mais nobres. Como cada usuário possui o seu próprio Shell interpondo-se entre ele e o Linux, é o Shell quem interpreta os comandos que são teclados e examina as suas sintaxes, passando-os esmiuçados para execução. O Shell é um interpretador (ou será intérprete) que traz consigo uma poderosa linguagem com comandos de alto nível, que permite construção de loops (laços), de tomadas de decisão e de armazenamento de valores em variáveis, como será mostrado. Será explicado 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 Comandos Neste exame, o Shell identifica os caracteres especiais (reservados) que têm significado para interpretação da linha, logo após verifica se a linha passada é um comando ou uma atribuição. Comando Quando 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 esta tarefa. Atribuição Se o Shell encontra dois campos separados por um sinal de igual ()= sem espaços em branco 37
  • 39.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF entre eles, identifica esta seqüência como uma atribuição. Exemplos: $ ls linux linux 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 = 1000 bash: 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: = e 1000. Resolução de Redirecionamentos Após identificar os componentes da linha que você teclou, o Shell parte para a resolução de redirecionamentos. O Shell tem incorporado ao seu elenco de vantagens o que chamamos de redirecionamento, que pode ser de entrada (stdin), de saída (stdout) ou dos erros (stderr), con- forme será explicado a seguir. Substituição de Variáveis Neste 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 ele será 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 kernel Completadas as tarefas anteriores, o Shell monta a linha de comandos, já com todas as subs- tituições feitas, chama o kernel para executá-la em um novo Shell (Shell filho), ganhando um número de processo (PID ou Process Identification) e permanece inativo durante a execução do programa. Uma vez encerrado este processo (juntamente com o Shell filho), recebe novamente o controle e, exibindo um prompt, mostra que está pronto para executar outros comandos. 38
  • 40.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 5.6 Caracteres para remoção do significado É isso mesmo, quando não se deseja que o Shell interprete um caractere especial, deve-se "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 existe o arquivo linux*. Contrabarra ou Barra Invertida () Idêntico aos apóstrofos exceto que a barra invertida inibe a interpretação somente do caractere que a segue. Suponha que você, acidentalmente, tenha criado um arquivo chamado * (asterisco) - que alguns sabores de Unix permitem - e deseja removê-lo. Se você fizesse: $ rm * Estaria cometendo um grande erro, pois o rm removeria todos os arquivos do diretório corrente. 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 ($), uma crase (`), ou uma barra invertida (), estes caracteres serão interpretados pelo Shell. Não precisa se preocupar, não foi dado 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 39
  • 41.
    CDTC Centro deDifusã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 é chamada Entrada Padrão ou stdin e seu default é o teclado do terminal. Analogamente, a saída do comando é chamada Saída Padrão ou stdout e seu default é a tela do terminal. Para a tela també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 (st- dout). Caso a entrada não seja definida, ele espera os dados da stdin. Como não foi especificada a entrada, ele está esperando-a pelo teclado (Entrada Padrão) e como também não foi citada a saída, o que será teclado irá para a tela (Saída Padrão) fazendo desta forma, um programa 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) seguido do nome do arquivo para o qual se deseja mandar a saída. Vamos transformar o pro- grama gago em um editor de textos. $ cat > Arq O cat continua sem ter a entrada especificada, portanto está aguardando que os dados sejam teclados, porém a sua saída está sendo desviada para o arquivo Arq. Assim sendo, tudo que esta sendo teclado esta indo para dentro de Arq, de forma que fizemos o editor de textos mais curto e ruim do planeta. Se fizermos, novamente: $ cat > Arq Os dados contidos em Arq serão perdidos, já que antes do redirecionamento o ShellArq estava vazio. Para colocar mais informações no final do arquivo, deveriamos ter feito: criará um $ cat » Arq Atenção: Como já haviamos lhe dito, o Shell resolve a linha e depois manda o comando para a execução. Assim, se você redirecionar a saída de um arquivo 40
  • 42.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF para ele próprio, primeiramente o Shell "esvazia"este arquivo e depois manda o comando para execução, desta forma você acabou de perder o conteúdo do seu 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, os erros 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 dados ao final de um arquivo, e o segundo redireciona a Saída de Erro Padrão (stderr) para um 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$$. Para nã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 isso não aconteça, deve-se fazer: $ rm /tmp/seraqueexiste$ $ 2> /dev/null (dois cifrões juntos) Sobre o exemplo visto, há duas dicas: Dica 1: O $ $ (dois cifrões juntos) contêm o PID, isto é, o número do seu processo. Como o Linux é multiusuário, é bom anexar sempre o $ $ (dois cifrões juntos) ao nome dos arquivos que 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 o usasse (criando-o então) seria o seu dono e todos os outros ganhariam um erro quando tentassem gravar algo nele. Para que você teste a Saída de Erro Padrão direto no prompt do seu Shell, vamos dar mais um exemplo. Faça: $ ls naoexiste bash: naoexiste no such file or directory $ ls naoexiste 2> arquivodeerros $ hspace*1cmtextbf$ cat arquivodeerros bash: naoexiste no such file or directory Neste exemplo, vimos que quando fizemos um ls em naoexiste, ganhamos uma mensagem de erro. Após, redirecionarmos a Saída de Erro Padrão para arquivodeerros e executarmos o 41
  • 43.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF mesmo comando, recebemos somente o prompt na tela. Quando listamos o conteúdo do arquivo para o qual foi redirecionada a Saída de Erro Padrão, vimos que a mensagem de erro tinha sido armazenada nele. Faça este teste. Dica 2: - Quem é esse tal de /dev/null? - Em Unix existe um arquivo fantasma. Chama-se /dev/null. Tudo que é mandado para este arquivo some, assemelha-se a um Buraco Negro. No caso do exemplo, como não nos interessava guardar a possível mensagem de erro oriunda do comando rm, redirecionamos- a para este arquivo. É interessante notar que estes caracteres de redirecionamento são cumulativos, isto é, se no exemplo 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 para que serve isso? - você irá perguntar. -Com esse exemplo você entenderá: Suponha que você queira mandar um email para o seu chefe. Ao invés de sair redigindo o email direto no prompt da tela de forma a tornar impossível a correção de uma frase anterior onde, sem querer, escreveu um "nós vai", você edita um arquivo com o conteúdo da mensagem e após umas quinze verificações sem constatar nenhum erro, decide enviá-lo e para tal, faz: $ mail chefe < arquivocommailparaochefe O seu chefe, então, receberá o conteúdo do arquivocommailparaochefe. Um outro tipo de redirecionamento que o Shell te permite é o chamado here document. Ele é representado por « (menor menor) e serve para indicar ao Shell que o escopo de um comando começa na linha seguinte e termina quando encontra uma linha cujo conteúdo seja unicamente o label que segue o sinal «. Veja o fragmento de script a seguir, com uma rotina de ftp: $ ftp -ivn hostremoto « fimftp user $Usuário $Senha binary get arquivoremoto fimftp Aqui, temos muitos detalhes interessantes: 1. As opções que foram usadas para o ftp (-ivn) servem para ele ir listando tudo que está acon- tecendo (-v de verbose), para não perguntar se você tem certeza de que deseja transmitir 42
  • 44.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF cada arquivo (-i de interactive), e finalmente a opção -n serve para dizer ao ftp para ele não solicitar o usuário e sua senha, pois esses serão informados pela instrução específica (user). 2. Quando foi usado o « fimftp, estava dizendo o seguinte para o intérprete: "Shell, não faça 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 resolverá antes do redirecionamento. Mas a grande vantagem desse tipo de construção é que ela permite que comandos também sejam interpretados dentro do escopo do here document, o que também contraria o que foi dito. Depois será explicado como isso funciona. Agora não dá, pois está faltando ferramenta. 1. O comando user é do repertório de instruções do ftp e serve para passar o usuário e a senha que haviam sido lidos em uma rotina anterior a esse fragmento de código e colocados, respectivamente, 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 para saber se está em ASCII, EBCDIC, ... 3. O get arquivoremoto diz ao ftp para pegar esse arquivo em hostremoto e trazê-lo para o nosso host local. Se fosse para mandar o arquivo, 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 atento quanto a isso, por que este tipo de erro cria problemas no programador, até que seja de- tectado. Lembre-se: um label que se preze tem que ter uma linha inteira só para ele. 5.8 Redirecionamento de Comandos Os redirecionamentos que falamos até aqui sempre se referiam a arquivos, isto é mandavam para arquivo, recebiam de arquivo, simulavam arquivo local e etc. O que veremos a partir de agora redireciona a saída de um comando para a entrada de outro, é muito utilizado e ajuda bas- tante. Seu nome é pipe (que em inglês significa tubo, já que ele encana a saída de um comando para a entrada de outro) e sua representação é uma barra vertical (|). http://www.redhat.com/docs/manuals/linux/RHL-7.2-Manual/custom-guide/rescuemode.html $ ls | wc -l 21 O comando ls passou a lista de arquivos para o comando wc, que quando está com a opção -l conta a quantidade de linhas que recebeu. Desta forma, podemos afirmar categoricamente que no diretório existiam 21 arquivos. $ cat /etc/passwd |sort | lp 43
  • 45.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Esta linha de comandos manda a listagem do arquivo /etc/passwd para a entrada do comando sort. Este a classifica e manda-a para o lp que é o gerenciador do spool de impressão. 5.9 Caracteres de Ambiente Normalmente, ao priorizar uma expressão você a coloca entre parênteses, por causa da arit- mética é normal pensarmos desta forma. Mas em Shell o que prioriza mesmo são as crases (`) e não os parênteses. Vamos 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 posso fazer: $ who | wc -l 8 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 quero é que ele esteja no meio de uma frase. Para mandar frases para a tela, uso o comando echo, ficando assim: $ echo "Existem who | wc -l usuários conectados" Existem who | wc -l usuários conectados Não funcionou, e não foi por causa das aspas que eu coloquei, mas sim por que eu teria que ter executado o who | wc -l antes do echo. Para resolver este problema, tenho que priorizar esta segunda parte do comando com o uso de crases, fazendo assim: $ echo "Existem ’who | wc -l’ 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 conectados Existem 8 usuários conectados Como foi dito antes, as aspas protegem tudo que está dentro dos seus limites, da interpreta- ção do Shell. Como para o Shell basta um espaço em branco como separador, os vários espaços serão trocado por um único após a retirada das aspas. Quando estiver no Shell, você deve sempre dar um comando em cada linha. Para agrupar co- mandos em uma mesma linha, terá que separá-los por ponto-e-vírgula(;). Então: http://www.redhat.com/docs/manuals/linux/RHL-7.2-Manual/custom-guide/rescuemode.html $pwd ; cd /etc; pwd; cd -; pwd /home/meudir 44
  • 46.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF /etc/ /home/meudir Neste exemplo, listamos o nome do diretório corrente com o comando pwd, mudamos para o diretório /etc, novamente listamos o nome do diretório e, finalmente, voltamos para o diretório onde estava anteriormente (cd -), listando seu nome. Repare que foi colocado o ponto-e-vírgula (;) de todas as formas possíveis para mostrar que não importa se existem espaços em branco antes ou após este caractere. Finalmente, vamos ver o caso dos parênteses. Veja só o caso a seguir, bem parecido com o exemplo anterior: $ (pwd ; cd /etc ; pwd;) /home/meudir /etc/ $ pwd /home/meudir - Quequeiiisso minha gente? Eu estava no /home/meudir, mudei para o /etc, constatei que estava neste diretório com o pwd seguinte, e quando o agrupamento de comandos terminou, eu vi que 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 é que ele 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ênteses foram executados, o novo Shell que estava no diretório /etcShell anterior cujo diretório corrente era /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: $ mail suporte « FIM http://www.redhat.com/docs/manuals/linux/RHL-7.2-Manual/custom-guide/rescuemode.html >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 document. Os comandos entre crases (‘) serão priorizados e, portanto, o Shell os executará an- tes da instrução mail. Quando o suporte receber o e-mail, verá que os comandos date e ls foram executados imediatamente antes do comando mail, recebendo então uma fotografia do ambiente no momento em que a correspondência foi enviada. 45
  • 47.
    CDTC Centro deDifusã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 conceito de prompt secundário, ou de continuação de comando que é enviado para a tela quando há uma quebra 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 vem sendo utilizada como forma de priorização de execução de comandos, tal qual as crases (‘). São as 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. Aqui dentro do Botequim, usarei ambas as formas, indistintamente. Vejamos novamente o exemplo dado para as crases sob esta nova ótica: $ echo Existem $(who | grep wc -l) usuários conectados Existem 8 usuários conectados Veja só este caso: $ Arqs=ls $ echo $Arqs ls Neste exemplo, eu fiz uma atribuição (=) e executei uma instrução. O que eu queria era que a variá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ção do ls. Para fazer o que desejamos é necessário que eu priorize a execução deste comando em detrimento 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 colocar dentro 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: 46
  • 48.
    CDTC Centro deDifusã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 May 24 19:43 arq2 -rw-r–r– 1 jneves jneves 1866 Jan 22 2003 arql - Pô, saiu tudo embolado! - Pois é cara, como eu já te disse, se você deixar o Shell "ver"os espaços em branco, sempre que houver diversos espaços juntos, eles serão trocados por apenas um. Para que a listagem saia 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, vou lhe explicar uma série de instruções típicas de programação Shell. Tchau! Ahh! Só mais uma coisinha 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 47
  • 49.
    Capítulo 6 Parte II 6.1Diálogo - Garçom! Traz um "chops"e dois "pastel". O meu amigo hoje não vai beber por que ele final- mente esta 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 vi nada prático nisso... - Calma rapaz, o que te falei até agora, serve como base ao que há de vir daqui pra frente. Vamos usar estas ferramentas que vimos para montar programas es- truturados, 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 em inglês com este nome... - É claro, grep é um acrônimo Global Regular Expres- sion Print, que usa expressõ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.). Por falar em expressões regulares (ou regexp), o Aurélio Marinho Jargas tem todas as dicas em sua página (inclusive tutorias) que abordam o tema. Se você está mesmo a fim de aprender a programar em Shell, Perl, Python, ... Acho bom você ler estes artigos para te ajudar no que está para vir. 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 "¨para caracteres como "*"para que o bash não o entenda como um curinga para nomes de arquivos. A definiçã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 do comando grep. Vejamos: Pesquisando em um arquivo: $ grep rafael /etc/passwd Pesquisando em vários arquivos: $ grep grep *.sh 48
  • 50.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Pesquisando na saída de comando: $ who | grep Pelegrino No 1º exemplo, o mais simples, procurei a palavra rafael em qualquer lugar do arquivo /etc/passwd. Se quisesse procurá-la como um login name, isto é, somente no início dos registros deste arquivo, eu deveria fazer: $ grep '^rafael’ /etc/passwd E para que serve este circunflexo e os apóstrofos? O circunflexo (^), se você tivesse lido os artigos anteriores sobre expressões regulares que te falei, saberia que servem para limitar a pesquisa ao início de cada linha, e os apóstrofos (’) servem para o Shell não interpretar este circunflexo, deixando-o passar incólume para o comando grep. O grep aceita como entrada, a saída de outro comando redirecionado por um pipe (isto é muito comum em Shell e é um tremendo acelerador de execução de comando já que atua como se a saída de um programa fosse guardada em disco e o segundo programa lesse este arquivo gerado), desta forma, no 3º exemplo, o comando who listou as pessoas "logadas"na mesma má- quina que você (não se esqueça jamais: o Linux é multiusuário) e o grep foi usado para 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 pessoas desconhecem é 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, o fgrep é melhor, por ser mais rápido; • O egrep ("e"de extended, extendido) é muito poderoso no uso de expressões regulares. Por ser o mais lento da família, só deve ser usado quando for necessária a elaboração de uma expressã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 e 50% 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 comandos grep 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. 49
  • 51.
    CDTC Centro deDifusã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ão vamos 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 http://www.redhat.com/docs/manuals/linux/RHL-7.2-Manual/custom-guide/rescuemode.html 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. Por esta 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 doc drwxr-xr-x 11 root root 4096 Jul 13 18:58 freeciv drwxr-xr-x 3 root root 4096 Oct 17 2000 gimp drwxr-xr-x 3 root root 4096 Aug 8 2000 gnome drwxr-xr-x 2 root root 4096 Aug 8 2000 idl drwxrwxr-x 14 root root 4096 Jul 13 18:58 locale drwxrwxr-x 12 root root 4096 Jan 14 2000 lyx drwxrwxr-x 3 root root 4096 Jan 17 2000 pixmaps drwxr-xr-x 3 root root 4096 Jul 2 20:30 scribus drwxrwxr-x 3 root root 4096 Jan 17 2000 sounds drwxr-xr-x 3 root root 4096 Dec 18 2000 xine No exemplo que acabamos de ver, o circunflexo (^) serviu para limitar a pesquisa à primeira posição da saída do ls longo. Os apóstrofos foram colocados para o Shell não "ver"o circunflexo (^). 50
  • 52.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Vamos ver mais um. Sabemos que as quatro primeiras posições possíveis de um ls -l de um 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ão as linhas listadas serão as que começam por um traço (-), seguido de qualquer coisa (o ponto quando usado como uma expressão regular significa qualquer coisa), novamente seguido de qualquer 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 de mú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 de programas para organizar os seus CDs de música, com pequenas adaptações, você pode fazer o mesmo com os CDs de software que vêm com a Linux Magazine e outros que você compra ou queima, disponibilizando este banco de software, desta forma ganhando muitos pontos com seu chefe. Para todos que trabalham com você (o Linux é multiusuário, e como tal deve ser explo- rado). - Espera! De onde eu vou receber os dados dos CDs? - Inicialmente, vou lhe mostrar como o seu programa pode receber parâmetros de quem o estiver executando e em breve, ensinarei a ler os dados pela tela ou de um arquivo. 6.6 Passando parâmetros O layout do arquivo músicas será o seguinte: 51
  • 53.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF nome do álbum^intérprete1~nome da música1:..:intérprete~nome da música isto é, o nome do álbum será separado por um circunflexo (^) do resto do registro, que é formado por 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érprete será separado por um til (~) do nome da música. Eu quero escrever um programa que chamado musinc, incluirá registros no meu arquivo musicas. Eu passarei o conteúdo de cada álbum como parâmetro na chamada do programa fazendo assim: $ musinc "álbum^interprete~musica:interprete~musica:..." Desta forma o programa musinc estará recebendo os dados de cada álbum como se fosse uma variá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 seus nomes são formados por um e somente um algarismo), isto é $1, $2, $3, ..., $9. Vamos, antes de tudo, fazer um teste: Exemplos $ cat teste #!/bin/bash # Programa para testar passagem de parametros echo "1o. parm -> $1" echo "2o. parm -> $2" echo "3o. parm -> $3" Vamos executá-lo: $ teste passando parametros para testar bash: teste: cannot execute Ops! Esqueci-me de torná-lo executável. Vou fazê-lo de forma a permitir que todos possam executá-lo e em seguida vou testá-lo: $ chmod 755 teste $ teste passando parametros para testar 1o. parm -> passando 2o. parm -> parametros 3o. parm -> para Repare que a palavra testar, que seria o quarto 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 de outra forma: $ teste "passando parametros"para testar 1o. parm -> passando parametros 52
  • 54.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 2o. parm -> para 3o. 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ê-lo assim: $ cat teste #!/bin/bash # Programa para testar passagem de parametros (2a. Versao) echo O programa $0 recebeu $# parametros echo "1o. parm -> $1" echo "2o. parm -> $2" echo "3o. parm -> $3" echo Todos de uma só ¨tacada¨: $* Repare que antes das aspas eu usei uma barra invertida para escondê-las da interpretação do Shell (se não usasse as contrabarras as aspas não apareceriam). Vamos executá-lo: $ teste passando parametros para testar O programa teste recebeu 4 parametros 1o. parm -> passando 2o. parm -> parametros 3o. parm -> para Todos de uma só "tacada": passando parâmetros para testar Conforme eu disse, os parâmetros recebem números de 1 a 9, mas isso não significa que não posso usar mais de 9 parâmetros significa somente que só posso endereçar 9. Vamos testar isso: Exemplo: $ cat teste 53
  • 55.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF #!/bin/bash # Programa para testar passagem de parametros (3a. Versao) echo O programa $0 recebeu $# parametros echo "11o. parm -> $11" shift echo "2o. parm -> $1" shift 2 echo "4o. Parm -> $1" Vamos executá-lo: $ teste passando parametros para testar O programa teste recebeu 4 parametros que são: 11o. parm -> passando1 2o. parm -> parametros 4o. 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 que aconteceu? O Shell interpretou como sendo $1 seguido do algarismo 1 e listou passando1; 2. O comando shift cuja sintaxe é shift n, podendo o n assumir qualquer valor numérico (porém seu default é 1 como no exemplo dado), despreza os n primeiros parâmetros, tornando o parâmetro de ordem n+1 o primeiro, ou seja, o $1. 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 programa é muito simples (como tudo em Shell) e vou listá-lo para você ver: Exemplos $ cat musinc #!/bin/bash # Cadastra CDs (versao 1) # echo $1 » musicas O script é 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 supor que 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 musicas album 3^Artista5~Musica5:Artista6~Musica5 album 1^Artista1~Musica1:Artista2~Musica2 54
  • 56.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF album 2^Artista3~Musica3:Artista4~Musica4" Não está funcional como achava que deveria ficar. Poderia ter ficado melhor. Os álbuns estão fora de ordem, dificultando a pesquisa. Vamos alterar nosso script e depois testá-lo novamente: $ cat musinc #!/bin/bash # Cadastra CDs (versao 2) # echo $1 » musicas sort musicas -o musicas Vamos cadastrar mais um: $ musinc "album 4^Artista7~Musica7:Artista8~Musica8" Agora, vamos ver o que aconteceu com o arquivo musicas: $ cat musicas album 1^Artista1~Musica1:Artista2~Musica2 album 2^Artista3~Musica3:Artista4~Musica4" album 3^Artista5~Musica5:Artista6~Musica5 album 4^Artista7~Musica7:Artista8~Musica8 Simplesmente inseri uma linha que classifica o arquivo musicas dando a saída nele mesmo (para isso serve a opção -o), após cada álbum ser anexado. Agora está quase funcional. Mas atenção, esta não é a versão final. O programa ficará muito melhor e mais amigável, em uma nova versão que desenvolveremos após aprendermos a adqui- rir os dados da tela e formatar a entrada. Exemplos Listar com o comando cat não serve, vamos, então, fazer um programa chamado 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 dois parâmetros. Vamos fazer assim: $ muslist "álbum 2" grep: can’t open 2 55
  • 57.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF musicas: album 1^Artista1~Musica1:Artista2~Musica2 musicas: album 2^Artista3~Musica3:Artista4~Musica4" musicas: album 3^Artista5~Musica5:Artista6~Musica5 musicas: album 4^Artista7~Musica7:Artista8~Musica8 Onde está o erro? Eu tive o cuidado de colocar o parâmetro passado entre aspas 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 de musicas, listou a todos. Atenção: Sempre que a cadeia de caracteres a ser passada para o comando grep pos- suir brancos ou TAB, mesmo que dentro de variáveis, coloque-a sempre entre aspas para evitar que as palavras após o primeiro espaço em branco ou TAB sejam interpretadas como nomes de arquivos. Por outro lado, é melhor ignorarmos maiúsculas e minúsculas na pesquisa. Resolveríamos os dois 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 e minú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 da forma que estamos fazendo, podemos pesquisar por álbum, por música, por intérprete ou até 56
  • 58.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF por um pedaço de qualquer um destes. Quando conhecermos os comandos condicionais, mon- taremos uma nova versão de muslist que permitirá especificar por qual campo pesquisar. Você me diz: - Poxa, mas é um saco ter que colocar o argumento de pesquisa entre aspas na hora de passar o 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 2 album2^Artista3~Musica3:Artista4~Musica4 Desta forma, o $*, que significa todos os parâmetros, será substituído pela cadeia album 2 (de acordo 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 40 graus empenou o seu CD e agora você precisa de uma ferramenta para removê-lo do banco de dados? Não tem problema, vamos desenvolver um script chamado musexc, para excluir estes CDs. Antes de desenvolver o "bacalho", quero te apresentar a uma opção bastante útil da família de comandos 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"musicas album1^Artista1~Musica1:Artista2~Musica2 album3^Artista5~Musica5:Artista6~Musica6 album4^Artista7~Musica7:Artista8~Musica8 Conforme eu expliquei antes, o grep do exemplo listou todos os registros de músicas exceto o referente a album 2, porque atendia ao argumento do comando. Estamos, então, prontos para desenvolver o script para remover aquele CD empenado da sua "CDteca". Ele tem a seguinte cara: $ cat musexc #!/bin/bash 57
  • 59.
    CDTC Centro deDifusã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á havia citado 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 de trabalho 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 de ferramentas que ainda temos. Mas é bom, enquanto eu tomo mais um chope, você vai para casa praticar 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 preciso molhar a palavra porque estou de goela seca! - Garçom! Mais um sem colarinho! 58
  • 60.
    Capítulo 7 Parte III 7.1Trabalhando com cadeias Pelo título acima não pense você que vou lhe ensinar a ser carcereiro! Estou me referindo à cadeia 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 e tem 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 inicial PosFim = Posição final $ cat numeros 1234567890 0987654321 1234554321 9876556789 $ cut -c1-5 numeros 12345 59
  • 61.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 09876 12345 98765 $ cut -c-6 numeros 123456 098765 123455 987655 $ cut -c4- numeros 4567890 7654321 4554321 6556789 $ cut -c1,3,5,7,9 numeros 13579 08642 13542 97568 $ cut -c -3,5,8- numeros 1235890 0986321 1235321 9875789 Como dá para ver, no duro mesmo existem quatro sintaxes distintas: na primeira (-c 1-5), eu especifiquei uma faixa, na segunda (-c -6), especifiquei tudo até uma 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 é útil para arquivos com campos de tamanho fixo, mas atualmente o que mais existe são arquivos com campos de tamanho variáveis, onde cada campo termina com um delimitador. Vamos dar uma olhada no arquivo musicas que começamos a preparar no nosso papo na última vez que viemos aqui no botequim. $ cat musicas album1^Artista1~Musica1:Artista2~Musica2 album2^Artista3~Musica3:Artista4~Musica4 album3^Artista5~Musica5:Artista6~Musica6 album4^Artista7~Musica7:Artista8~Musica8 Então, recapitulando, o seu layout é o seguinte: 60
  • 62.
    CDTC Centro deDifusã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 é formado por 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 do inté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: musicas Artista2~Musica2 Artista3~Musica4 Artista6~Musica6 Artista8~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~ Artista2 Artista4 Artista6 Artista8 Para entender isso, vamos pegar a primeira linha de músicas: $ head -1 musica album 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 (:) é album 1^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 61
  • 63.
    CDTC Centro deDifusã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 ca- racteres. 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 de cadeia1 pelo seu correspondente na cadeia2 ou troca múltiplas ocorrências dos caracteres de cadeia1 por somente um caractere, 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 simples: $ echo bobo | tr o a baba 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 (sim ou não), e guardo sua resposta na variável $Resp. Ora o conteúdo de $Resp pode estar com letra maiúscula ou minúscula, e desta forma eu teria que fazer diversos testes para saber se a resposta 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ção que pode ser usada são as escape sequences (prefiro escrever no bom e velho português, mas 62
  • 64.
    CDTC Centro deDifusã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 significado você 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 compli- car a minha vida e em um exercício prático valendo nota que passei para ser feito no computador, me entregou o script com todos os comandos separados por ponto-e-vírgula (lembra que eu disse que o ponto-e-vírgula servia para separar diversos comandos em uma mesma linha?). Vou dar um exemplo simplificado de uma "tripa"assim: $ cat confuso echo 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: $ confuso leia Programação Shell Linux do Julio Cezar Neves /home/jneves/LM confuso livro musexc musicas musinc muslist numeros Mas nota de prova é coisa séria, então, para entender o que o aluno havia feito, o chamei e em sua frente executei o seguinte comando: $ tr ";" "n" < confuso echo leia Programação Shell Linux do Julio Cezar Neves pwd cd ~ ls -l rm -f lixo 2>/dev/null Ele ficou muito desapontado, porque em 2 ou 3 segundos eu desfiz a gozação que ele perdera horas para fazer. Mas preste atenção! Se eu estivesse em uma máquina com Unix, eu teria feito: 63
  • 65.
    CDTC Centro deDifusã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 # Hoje Sun Sep 19 14:59:54 2004 $ date # Há duas semanas Sun Sep 5 10:12:33 2004 Para pegar a hora eu deveria fazer: $ date |cut -f 4 -d ' ' 14:59:54 Mas duas semanas antes ocorreria o seguinte: $ date |cut -f 4 -d ' ' 5 Mas observe porque: $ date # Há duas semanas Sun Sep 5 10:12:33 2004 Como você pode notar, existem 2 caracteres em branco antes do 5 (dia), o que estraga tudo porque o terceiro 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 re- sultantes do 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á ajudando. Veja este arquivo que foi baixado de uma máquina com aquele sistema operacional que pega vírus: $ cat -ve ArqDoDOS.txt Este arquivo^M$ foi gerado pelo^M$ DOS/Rwin e foi^M$ baixado por um^M$ 64
  • 66.
    CDTC Centro deDifusã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 como um cifrão. • Dica 2 - Isto ocorre porque no formato DOS (ou rwin), o fim dos registros é formado por um Carriage-Return (r) e um line-feed (f). No Linux, porém, o final do registro tem somente o line-feed. Vamos 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.txt Este arquivo^M$ foi gerado pelo^M$ DOS/Rwin e foi^M$ baixado por um^M$ ftp mal feito.^M$ A opção -d do tr remove o caractere especificado de todo o arquivo. Desta forma eu removi os caracteres indesejados salvando em um arquivo de trabalho e posteriormente renomeei-o para a sua 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 sin- taxe: if cmd then cmd1 cmdn else cmd3 cmd4 cmdm fi 65
  • 67.
    CDTC Centro deDifusã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 do bloco 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 1 if grep ^$1 /etc/passwd then echo Usuario ’$1’ já existe else if useradd $1 then echo Usuário ’$1’ incluído em /etc/passwd else echo "Problemas no cadastramento. Você é root?" fi fi Repare que o if está testando direto o comando grep e esta é a sua finalidade. Caso o if$1 seja bem sucedido, ou seja, o usuário (cujo nome está em foi encontrado em /etc/passwd, os co- mandos 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 comando useradd foi executado a contento, criando o registro do usuário em /etc/passwd, ou não quando dará a mensagem de erro. Vejamos sua execução, primeiramente passando um usuário já cadastrado: $ incusu jneves jneves:x:54002:1001:Julio Neves:/home/jneves:/bin/bash Usuario ’jneves’ ja existe Como já vimos diversas vezes, mas é sempre bom insistir no tema para que você já fique preca- vido, no exemplo dado surgiu uma linha indesejada, ela é a saída do comando grep. Para evitar que isso aconteça, devemos desviar a saída desta instrução para /dev/null, ficando assim: $ cat incusu #!/bin/bash # Versão 2 if grep ^$1 /etc/passwd > /dev/null then echo Usuario ’$1’ já existe else if useradd $1 66
  • 68.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF then echo Usuário ’$1’ incluído em /etc/passwd else echo "Problemas no cadastramento. Você é root?" fi fi Agora, vamos testá-lo como usuário normal (não root): $ incusu ZeNinguem ./incusu[6]: useradd: not found Problemas 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 3 if grep ^$1 /etc/passwd > /dev/null then echo Usuario ’$1’ já existe else if useradd $1 2> /dev/null then echo Usuário ’$1’ incluído em /etc/passwd else echo "Problemas no cadastramento. Você é root?" fi fi Depois destas alterações e de fazer um su - (me tornar root) vejamos o seu comportamento: $ incusu botelho Usuário ’botelho’ incluido em /etc/passwd E novamente: $ incusu botelho Usuário ’botelho’ já existe Lembra que eu falei que ao longo dos nossos papos e chopes os nossos programas iriam se aprimorando? Então, vejamos agora como poderíamos melhorar o nosso programa para incluir músicas: $ cat musinc #!/bin/bash # Cadastra CDs (versao 3) 67
  • 69.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF # if grep "^$1$"musicas > /dev/null then echo Este álbum já está cadastrado else echo $1 » musicas sort musicas -o musicas? fi Como você viu, é uma pequena 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 (^) e terminava ($) igual ao parâmetro passado ($1). O uso do circunflexo (^) no início da cadeia e cifrão ($) no fim, são para testar se o parâmetro passado (o álbum e seus dados) são exatamente iguais a algum registro anteriormente cadastrado e não somente igual a um pedaço de algum dos registros. Vamos executá-lo passando um álbum já cadastrado: $ musinc "album 4^Artista7~Musica7:Artista8~Musica8" Este álbum já está cadastrado E agora um não cadastrado: $ musinc "album 5^Artista9~Musica9:Artista10~Musica10" $ cat musicas album1^Artista1~Musica1:Artista2~Musica2 album2^Artista3~Musica3:Artista4~Musica4 album3^Artista5~Musica5:Artista6~Musica6 album4^Artista7~Musica7:Artista8~Musica8 album5^Artista9~Musica9:Artista10~Musica10 - Como você viu, o programa melhorou um pouquinho, mas ainda não está pronto. À medida que eu for te ensinando a programar em shell, nossa CDteca irá ficando cada vez melhor. - Entendi tudo que você me explicou, mas ainda 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 comando test. 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 de diversas outras sintaxes do if. - Falou! Acho bom mesmo porque eu também já tô ficando zonzo e assim tenho tempo para praticar esse monte de coisas que você me falou hoje. - Para fixar o que você aprendeu, tente fazer um scriptizinho para informar se um determinado usuário, que será passado como parâmetro esta logado (arghh!) ou não. - Aê Chico, mais dois chopes por favor... 68
  • 70.
    Capítulo 8 Parte IV 8.1Diá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 fazer um scriptizinho para informar se um determinado usuário, que será passado como parâmetro está logado (arghh!) ou não. Eu fiz o seguinte: $ cat logado #!/bin/bash # Pesquisa se uma pessoa está logada ou não if who | grep $1 then echo $1 está logado else echo $1 não se encontra no pedaço fi - 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 jneves jneves 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 para evitar 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/null then echo $1 está logado 69
  • 71.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF else echo $1 não se encontra no pedaço fi Agora, vamos aos testes: $ logado jneves jneves está logado $ logado chico chico não se encontra no pedaço Atenção: Ah, agora sim! Lembre-se desta pegadinha, a maior parte dos comandos tem uma saída padrão e uma saída de erros (o grep é uma das poucas exceções, já que não dá mensagem de erro quando não acha uma cadeia) e é necessário estarmos atentos para redirecioná-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 falando sobre o if, você me perguntou como se testa condições. Vejamos, então, o comando test. 8.2 O comando Test Todos estão acostumados a usar o if testando condições e estas são sempre, maior, menor, maior ou igual, menor ou igual, igual e diferente. Em Shell para testar condições usamos o comando test, só que ele é muito mais poderoso que o que estamos habituados. Primeiramente, vou te mostrar as principais opções (existem muitas outras) para testarmos arquivos em disco: . Veja as principais opções para teste de cadeias de caracteres: 70
  • 72.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF . E pensa que acabou? Engano seu! Agora é que vem o que você está mais acostumado, ou seja, as 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 muita coisa, e como eu te disse no início, o nosso if é muito mais po- deroso que o dos outros. Vamos ver em uns exemplos como isso tudo funciona, primeiramente testaremos a existência de um diretório: Exemplos: if test -d lmb then cd lmb else mkdir lmb cd 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, mas que- ria que você o entendesse assim, para, então, poder usar o ponto-de-espantação (!) como um negador do test. Veja só: if test ! -d lmb then mkdir lmb fi cd lmb 71
  • 73.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Desta forma, o diretório lmb seria criado somente se ele ainda não existisse, e esta negativa deve-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=1 cad2=01 if test $cad1 = $cad2 then 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=1 cad2=01 if test $cad1 -eq $cad2 then 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 ao número 01 é realmente diferente da cadeia Exemplos: Para mostrar o uso dos conectores -o (OU) e -a (E), veja um exemplo feito direto no prompt (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 grande chance de estar incorreto): 72
  • 74.
    CDTC Centro deDifusã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 > fi Pode 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 de incentivo. 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 > fi Cuidado Obviamente a operação redundou em erro, isto foi porque a opção -a tem precedência sobre a -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 entre si por diversos OU lógicos seja verdadeira, basta que uma delas seja, a expressão final resultou como VERDADEIRO e o then foi executado de forma errada. Para que isso volte a funcionar, façamos o seguinte: 73
  • 75.
    CDTC Centro deDifusã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 > fi Pode 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 resultado final 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á muito recursos, e isso poderia ser feito de forma muito mais simples com um único comando grep, da seguinte maneira: $ grep Artista1 musicas |grep Artista2 Da mesma forma para escolhermos CDs que tenham a 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 mais aconselhá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 ajudar,: $ grep Artista[12] musicas No egrep acima, foi usada uma expressão regular, onde a barra vertical (|) trabalha como um OU lógico e os parênteses são usados para limitar a amplitude deste OU. Já no grep da linha seguinte, a palavra Artista deve ser seguida por um dos valores da lista formada pelos colchetes ([ ]), isto é, 1 ou 2. - Tá legal, eu aceito o argumento, o if do Shell é muito mais poderoso que os outros caretas, mas cá pra 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 74
  • 76.
    CDTC Centro deDifusã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 lmb then mkdr lmb fi cd lmb e utilizando a nova sintaxe, vamos fazê-lo assim: if [ ! -d lmb ] then mkdir lmb fi cd lmb Ou seja, o comando test pode ser substituído por um par de colchetes ([ ]), separados por espa- ços em branco dos argumentos, o que aumentará enormemente a legibilidade, pois o comando if irá ficar com a sintaxe semelhante à das outras linguagens e por isso este será o modo que o comando test será usado daqui para a frente. 8.4 Encolheram o comando condicional Repare a tabela (tabela verdade) a seguir: . Ou seja, quando o conector é E e a primeira condição é verdadeira, o resultado final pode ser VERDADEIRO ou FALSO, dependendo da segunda condição, já no conector OU, caso a primeira condição seja verdadeira, o resultado sempre será VERDADEIRO e se a primeira for falsa, o re- sultado dependerá da segunda condição. Os desenvolvedores do interpretador estão sempre tentando otimizar 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, a segunda será executada somente caso a primeira seja falsa. Aproveitando disso, foi criada uma forma abreviada de fazer testes. foi batizado o conector E de && e o OU de || e para ver como isso funciona, vamos usá-los como teste no nosso velho exemplo de trocarmos de diretório, que em sua última versão estava assim: if [ ! -d lmb ] 75
  • 77.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF then mkdir lmb fi cd lmb Isso também poderia ser escrito da seguinte maneira: [ ! -d lmb ] && mkdir lmb cd dir Ou ainda retirando a negação (!): [ -d lmb ] || mkdir lmb cd dir No primeiro caso, se o primeiro comando (o test que está representado pelos colchetes) for bem sucedido, isto é, não existir o diretório lmb, o mkdir será efetuado porque a primeira condição era verdadeira 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 cd para dentro dele. Para executarmos mais de um comando desta forma, é necessário fazermos um grupamento de comandos, e isso se consegue com o uso de chaves ( ). Veja como seria o correto: cd lmb || { mkdir lmb cd lmb } Ainda não está bom, 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 lmb cd lmb } Como você viu o comando if nos permitiu fazer um cd seguro de diversas maneiras. É sempre bom lembrarmos que o seguro a que me referi é no tocante ao fato de que ao final da execu- ção você sempre estará dentro de lmb, desde que você tenha permissão para entrar em lmb, permissão para criar um diretório em ../lmb, haja espaço em disco, ... 76
  • 78.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 8.5 E tome de test Tem, ainda, mais uma forma de test que te permite usar padrões para comparação. Estes padrões atendem às normas de Geração de Nome de Arquivos (File Name Generation, que são ligeiramente parecidas com as Expressões Regulares, mas não podem ser confundidas com es- tas). A diferença de sintaxe deste para o test que acabamos de ver é que esse trabalha com dois pares de colchete da seguinte forma: [[ expressão ]] Onde expressão é uma das que constam na tabela a seguir: . $ echo $H 13 $ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora inválida Hora 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 caractere var 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 do comando test. No início deste papo, antes do último chope, afirmamos que o comando if do inter- pretador Shell é mais poderoso que o seu similar em outras linguagens. Agora que conhecemos 77
  • 79.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF todo 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á executar uma das opções: inclusão, exclusão, alteração ou fim. Veja como ficaria este fragmento de script: if [ $opc -eq 1 ] then inclusao elif [ $opc -eq 2 ] then exclusao elif [ $opc -eq 3 ] then alteracao elif [ $opc -eq 4 ] then exit else echo Digite uma opção entre 1 e 4 fi Neste exemplo, você viu o uso do elif com um else if, esta á a sintaxe válida e aceita, mas poderíamos fazer melhor, e isto seria com o comando case, que tem a sintaxe a seguir: case $var in padrao1) cmd1 cmd2 cmdn ;; padrao2) cmd1 cmd2 cmdn ;; padraon) cmd1 cmd2 cmdn ;; esac Onde a variável $var é comparada aos padrões padrao1, ..., padraon e caso um deles atenda, o bloco 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: 78
  • 80.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF . Para mostrar como fica melhor, vamos repetir o exemplo anterior, só que desta vez usaremos o case e não o if ... elif ... else ... fi. case $opc in 1) 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 asterisco atende a qualquer coisa, então, ele servirá para qualquer coisa que não esteja no intervalo de 1 a 4. Outro ponto a ser notado é 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: $ date Tue Nov 9 19:37:30 BRST 2004 $ date +%H 19 O comando date informa a data completa do sistema, mas ele tem diversas opções para seu mascaramento. Neste comando, a formatação começa com um sinal de mais (+) e os caracteres de 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 hora Hora=$(date +%H) case $Hora in 0? | 1[01]) echo Bom Dia ;; 1[2-7] ) echo Boa Tarde 79
  • 81.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF ;; esac exit 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 ou até mesmo para falar mal dos políticos. - É fácil, meu e-mail é julio.neves@gmail.com, 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 que receberá como parâmetro o nome de um arquivo e que quando executado salvará este arquivo com o nome original seguido de um til (~) e colocará este arquivo dentro do vi (o melhor editor que se tem notícia) para ser editado. Isso é para ter sempre a última cópia boa deste arquivo caso o cara faça alterações indevidas. Obviamente, você fará as críticas necessárias, como verificar se 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 pra entender? - Hum, hum... - Chico! Traz mais um sem colarinho que o cara aqui já está dando para entender! 80
  • 82.
    Capítulo 9 Parte V 9.1Comandos de Loop (ou laço) Muitos problemas requerem mecanismos de repetiçâo nos quais sequências de instruçôes precisam ser repetidas por várias vezes usando conjuntos diferentes de dados. Mais comumente, uma seçâo de código que se repete é chamada de laço porque após a execuçâo da última instru- çâo o programa se bifurca e retorna à primeira instrução ou encerra a execução. As instruçôes de loop ou laço que veremos são o for, o while e o until que veremos daqui em diante. Começaremos pelo 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 do comando faz parte do código fonte do Shell, ou seja, em bom programa é um built-in), é muito mais 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 ... valn faça cmd1 cmd2 cmdn feito Onde a variável var assume cada um dos valores da lista val1 val2 ... valn e para cada um desses valores executa o bloco de comandos formado por cmd1, cmd2 e cmdn. 9.2.1 Primeira sintaxe do comando for for var in val1 val2 ... valn do cmd1 cmd2 81
  • 83.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF cmdn done Vamos direto para os exemplos, para entender direito o funcionamento deste comando. Vamos escrever um script para listar todos os arquivos do nosso 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ório e o comando echo jogou-os para a tela separados por espaços em branco. Visto isso vamos ver como resolver o problema a que nos propuzemos: $ cat testefor1 #!/bin/bash # 1o. Prog didático para entender o for for Arq in * do echo -n $Arq: # A opcao -n eh para nao saltar linha done Então, vamos executá-lo: $ testefor1 ArqDoDOS.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 uma lista 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 exemplo simples (por enquanto): $ cat testefor2 #!/bin/bash # 2o. Prog didático para entender o for for Palavra in Papo de Botequim do echo $Palavra done E executando vem: 82
  • 84.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF $ testefor2 Papo de Botequim Como você viu, este exemplo é tão simples como o anterior, mas serve para mostrar o com- portamento básico do for. Veja só a força do for: ainda estamos na primeira sintaxe do comando e já estou mostrando novas formas de usá-lo. Lá atrás eu havia falado que o for usava listas separadas por espaços em 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 te mostrarei como se comporta uma variável do sistema chamada de $IFS. Repare seu conteúdo: $ echo "$IFS"| od -h 0000000 0920 0a0a 0000004 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 repre- senta o <TAB> como um ^I. Usei os dois-pontos (:) para mostrar o início e o fim do echo. E desta forma, mais uma vez pudemos notar que os três caracteres estão presentes naquela variável. Agora veja você, IFS significa Inter Field Separator ou, traduzindo, separador entre campos. Uma vez entendido isso, eu posso afirmar (porque vou provar) que o comando for não usa listas sepa- radas por espaços em branco, mas sim pelo conteúdo da variável $IFS, cujo valor padrão (default) 83
  • 85.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF são esses caracteres que acabamos de ver. Para comprovarmos isso, vamos mostrar um script que 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 musicas album 1^Artista1~Musica1:Artista2~Musica2 album 2^Artista3~Musica3:Artista4~Musica4 album 3^Artista5~Musica5:Artista6~Musica6 album 4^Artista7~Musica7:Artista1~Musica3 album 5^Artista9~Musica9:Artista10~Musica10 Em cima deste "leiaute"foi desenvolvido o script a seguir: $ cat listartista #!/bin/bash # Dado um artista, mostra as suas musicas if [ $# -ne 1 ] then echo Voce deveria ter passado um parametro exit 1 fi IFS=" :" for ArtMus in $(cut -f2 -d^ musicas) do echo "$ArtMus"| grep $1 && echo $ArtMus | cut -f2 -d~ done O script, como sempre, começa testando se os parâmetros foram passados corretamente, em seguida o IFS foi setado para <ENTER> e dois-pontos (:) (como demonstram as aspas em linha diferentes), porque é ele que separa os blocos Artistan~Musicam. Desta forma, a 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 cut listará somente o nome da música. Vamos executá-lo: $ listartista Artista1 Artista1 Musica1 Musica1 Artista1 Musica3 Musica3 Artista10 Musica10 Musica10 Aconteceram duas coisas indesejáveis: os blocos também foram listados e a Musica10 idem. Além do mais, o nosso arquivo de músicas está muito simples, na vida real, tanto a música 84
  • 86.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF quanto o artista têm mais de um nome. Suponha que o artista fosse uma dupla sertaneja cha- mada Perereca & Peteleca. Nesta caso o $1 seria Perereca e o resto seria ignorado na pesquisa. Para que isso não ocorresse, eu devia passar o nome do artista entre aspas (") ou alterar $1 por $@ (que significa todos os parâmetros passados), que é a melhor solução, mas neste caso eu teria que modificar a crítica dos parâmetros e o grep. A nova crítica não seria se eu passei um parâmetro, mas pelo menos um parâmetro e quanto ao grep, veja só o que resultaria após a substituiçã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 aspas também foram inseridas para que o nome do artista fosse visto como uma só cadeia monolítica. Ainda falta consertar 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 grep para /dev/null para que os blocos não sejam mais listados. Veja, então, a nova (e definitiva) cara do 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 ] then echo Voce deveria ter passado pelo menos um parametro exit 1 fi IFS=" :" for ArtMus in $(cut -f2 -d^ musicas) do echo "$ArtMus"| grep -i "^$@~" > /dev/null && echo $ArtMus | cut -f2 -d done Que executando vem: $ listartista Artista1 Musica1 Musica3 85
  • 87.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 9.2.2 Segunda sintaxe do comando for for var do cmd1 cmd2 cmdn done - Sem o in como ele saberá que valor assumir? - Esta construção a primeira vista parece exquisita, mas é bastante simples. Neste caso, 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 ] then echo Uso: $0 musica1 [musica2] ... [musican] exit 1 fi IFS=" :" for Musica do echo $Musica Str=$(grep -i "$Musica"musicas) || { echo "Não encontrada" continue } for ArtMus in $(echo "$Str"| cut -f2 -d^) do echo "$ArtMus"| grep -i "$Musica"| cut -f1 -d done done Da mesma forma que os outros, começamos o exercício com uma crítica sobre os parâmetros 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. Em seguida, o outro for pega cada bloco Artista Musica nos registros que estão em $Str e lista cada artista que execute aquela música. 86
  • 88.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Como sempre vamos executá-lo para ver se funciona mesmo: $ listamusica musica3 Musica4 "Eguinha Pocotó" musica3 Artista3 Artista1 Musica4 Artista4 Eguinha Pocotó Não encontrada A listagem ficou feia 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 esta listagem novamente usando todas estas ferramentas. A esta altura dos acontecimentos você deve estar se perguntando: "E aquele for tradicional das outras linguagens em que ele sai contando a partir de um número, com um determinado incre- mento até alcançar uma condição?" E eu te respondo: "Eu não te disse que o nosso for é mais porreta que os outros?"Para fazer isso existem duas formas: 1 - Com a primeira sintaxe que vimos, como nos exemplos a seguir direto no prompt: $ for i in $(seq 9) > do > echo -n "$i " > done 1 2 3 4 5 6 7 8 9 Neste, a variável i assumiu os inteiros de 1 a 9 gerados pelo comando seq e a opção -necho foi usada para não saltar linha a cada número listado (sinto-me ecologicamente correto por não gastar 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 " > done 4 5 6 7 8 9 Ou ainda na forma mais completa do seq: $ for i in $(seq 0 3 9) > do > echo -n "$i " > done 0 3 6 9 87
  • 89.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 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 cmd1 cmd2 cmdn 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ção cond; incr - Significa o incremento que a variável var sofrerá em cada passada do loop. Como sempre vamos aos exemplos que tudo fica mais fácil: $ for ((i=1; i<=9; i++)) > do > echo -n "$i " > done 1 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 o echo) será executado enquanto i menor ou igual (<)= a 9 e o incremento de i1 a cada passada do 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 o uso 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 o bloco de comandos. $ for ((; i<=9;)) > do > let i++ > echo -n "$i " > done 1 2 3 4 5 6 7 8 9 Repare que o incremento saiu do corpo do for e passou para o bloco de comandos, repare também que quando usei o let, não foi necessário sequer inicializar a variável $i. Veja só os 88
  • 90.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF comandos a seguir dados diretamente no prompt para mostrar o que acabo de falar: $ echo $j $ let j++ $ echo $j 1 Ou seja, a variável $j sequer existia e no primeiro let assumiu o valor 0 (zero) para, após o incremento, ter o valor 1. Veja só como as coisas ficam simples: $ for arq in * > do > let i++ > echo "$i -> $Arq" > done 1 -> ArqDoDOS.txt1 2 -> confuso 3 -> incusu 4 -> listamusica 5 -> listartista 6 -> logado 7 -> musexc 8 -> musicas 9 -> musinc 10 -> muslist 11 -> testefor1 12 -> testefor2 - Pois é amigo, tenho certeza que você já tomou um xarope do comando for. Por hoje chega, na 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 arquivo texto, 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ão vale usar o wc -w. 89
  • 91.
    Capítulo 10 Parte VI 10.1Um pouco mais de for e matemática Voltando à vaca fria, na última vez que aqui estivemos, terminamos o nosso papo mostrando o loop de for a seguir: for ((; i<=9;)) do let i++ echo -n "$i " done Uma vez que chegamos neste ponto, creio ser bastante interessante citar que o Shell trabalha com o conceito de "Expansão Aritmética"(Arithmetic Expansion) que é acionado por uma cons- trução da forma $((expressão)) ou let expressão No último for citado usei a expansão das duas formas, mas não poderíamos seguir adiante sem saber que a expressão pode ser de uma das listadas a seguir: 90
  • 92.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF . - Mas você pensa que o papo de loop (ou laço) se encerra no comando for? Enganou-se 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 lingua- gens e nelas, o que normalmente ocorre é que um bloco de comandos é executado, enquanto (enquanto em ingles é while) uma determinada condição for verdadeira. Pois bem, isto é o que ocorre nas linguagens caretas! Em programação Shell, o bloco de comandos é executado en- quanto um comando for verdadeiro. E é claro, se quiser testar uma condição use o comando while junto com o comando test, exatamente como você aprendeu a fazer no if, lembra? Então, a sintaxe do comando fica assim: while comando do cmd1 cmd2 ... cmdn done e desta forma o bloco de comandos formado pelas instruções cmd1, cmd2,... e cmdncomando for 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 sem poder 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 xefe do sleep 30 done echo 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- 91
  • 93.
    CDTC Centro deDifusã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 o chefe estiver logado (Argh!). Assim que ele se desconectar do servidor, o fluxo do script sairá do loop e que será verdadeiro enquanto e dará a tão ansiada mensagem de liberdade. Quando o executei adivinha o que aconteceu? $ logaute.sh xefe pts/0 Jan 4 08:46 (10.2.4.144) xefe pts/0 Jan 4 08:47 (10.2.4.144) ... xefe pts/0 Jan 4 08:52 (10.2.4.144) Isto é a cada 30 segundos seria enviado para a tela a 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. Para evitar 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 sorriso while who | grep xefe > /dev/null do sleep 30 done echo 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 programa que será executado em background e que me informe do seu término. Mas, para você entender este exemplo, primeiro tenho de mostar uma nova variável do sistema. Veja estes comandos diretos no prompt: $ sleep 10& [1] 16317 $ echo $! 16317 [1]+ Done sleep 10 $ echo $! 16317 Isto é, criei um processo em background para dormir por 10 segundos, somente para mostrar que a variável $! guarda o PID (Process IDentification) do último processo em background, mas repare 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 monitorar qualquer processo em background. Veja como: $ cat monbg.sh #!/bin/bash 92
  • 94.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF # Executa e monitora um # processo em background $1 & # Coloca em background while ps | grep -q $! do sleep 5 done echo Fim do Processo $1 Este script é bastante similar ao anterior, mas tem uns macetes a mais, veja só: ele tem que ser executado em background para não prender o prompt, mas o $!background após o monbg.sh propriamente dito. Repare também a opção -q (quiet) do grep, ela serve para tranformá-lo num comando mineiro, isto é, para o grepwhile ps | grep $! > /dev/null, como nos exemplos que vimos até agora. será o do programa passado como parâmetro já que ele foi colocado em "trabalhar 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 (Process IDentifi- cation) 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 resolver este 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 operador 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 programa que inicialmente lerá o nome do álbum e em seguida fara um loop de leitura, pegando a música e o artista. Este loop termina quando for informada uma música vazia, isto é, ao ser solicitada a 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 que o álbum seja todo do mesmo artista) até que ele deseje alterá-lo. Vamos ver como ficou: $ cat musinc #!/bin/bash # Cadastra CDs (versao 4) # clear read -p "Título do Álbum: "Tit [ "$Tit"] || exit 1 # Fim da execução se título vazio 93
  • 95.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF if grep "^$Tit^" musicas > /dev/null then echo Este álbum já está cadastrado exit 1 fi Reg="$Tit^" Cont=1 oArt= while true do echo Dados da trilha $Cont: read -p "Música: "Mus [ "$Mus"] || break # Sai se vazio read -p "Artista: $oArt // "Art [ "$Art"] && oArt="$Art"# Se vazio Art anterior Reg="$Reg$oArt $Mus:"# Montando registro Cont=$((Cont + 1)) # A linha anterior tb poderia ser ((Cont++)) done echo "$Reg»> musicas sort musicas -o musicas$ cat musinc #!/bin/bash # Cadastra CDs (versao 4) # clear read -p "Título do Álbum: "Tit [ "$Tit"] || exit 1 # Fim da execução se título vazio if grep "^$Tit^" musicas > /dev/null then echo Este álbum já está cadastrado exit 1 fi Reg="$Tit^" Cont=1 oArt= while true do echo Dados da trilha $Cont: read -p "Música: "Mus [ "$Mus"] || break # Sai se vazio read -p "Artista: $oArt // "Art [ "$Art"] && oArt="$Art"# Se vazio Art anterior Reg="$Reg$oArt $Mus:"# Montando registro Cont=$((Cont + 1)) # A linha anterior tb poderia ser ((Cont++)) done echo "$Reg»> musicas sort musicas -o musicas 94
  • 96.
    CDTC Centro deDifusã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á a execuçã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 () para protegê-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 $oArt que só terá o seu conteúdo alterado, quando algum dado 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 uma operaçã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 forma oti- mizada 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. É o seguinte: ambos testam comandos; ambos possuem a mesma sintaxe e ambos atuam em loop, porém enquanto o while executa o bloco de instruções do loop enquanto um comando for bem sucedido, o until exe- cuta o bloco do loop até que o comando seja bem sucedido. Parece pouca coisa mas a diferença é fundamental. A sintaxe do comando é praticamente a mesma do while. Veja: until comando do cmd1 cmd2 ... cmdn done E desta forma o bloco de comandos formado pelas instruções cmd1, cmd2,... e cmdncomando é executado até que a execução da instrução seja bem sucedida. 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 para neutralizá-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. Um dia deu um problema da rede, ele me pediu para dar uma olhada no micro dele e me dei- 95
  • 97.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 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 julio do sleep 30 done echo $(date "+ Em %d/%m às %H:%Mh") > relapso.log Olha que safado! O cara estava montando um log com os horários que eu chegava, e ainda por cima chamou o arquivo que me monitorava de relapso.log! O que será que ele quis dizer com isso? Neste script, o pipeline who | grep julio, será bem sucedido somente quando juliowho, isto é, quando eu me "logar"no servidor. Até que isso aconteça, o comando sleep, que forma o bloco de instruções do until, colocará 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 comando Em 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 programa termina. Vejamos como melhorá-lo: $ cat musinc #!/bin/bash # Cadastra CDs (versao 5) # Para= until [ "$Para"] do clear read -p "Título do Álbum: "Tit if [ ! "$Tit"] # Se titulo vazio... then Para=1 # Liguei flag de saída else if grep "^$Tit^" musicas > /dev/null then echo Este álbum já está cadastrado exit 1 fi Reg="$Tit^" 96
  • 98.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Cont=1 oArt= while [ "$Tit"] do echo Dados da trilha $Cont: read -p "Música: "Mus [ "$Mus"] || break # Sai se vazio read -p "Artista: $oArt // "Art [ "$Art"] && oArt="$Art"# Se vazio Art anterior Reg="$Reg$oArt $Mus:"# Montando registro Cont=$((Cont + 1)) # A linha anterior tb poderia ser ((Cont++)) done echo "$Reg»> musicas sort musicas -o musicas fi done Nesta versão, um loop maior foi adicionado antes da leitura do título, que só terminará quando a variável $Para deixar de ser vazia. Caso o título do álbum não seja informado, a variável $Para receberá valor (no caso coloquei 1, mas poderia ter colocado qualquer coisa. O importante é que nã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 da frente. Em algumas oportunidades, temos que colocar um comando que aborte de forma controlada este loop. De maneira inversa, algumas vezes desejamos que o fluxo de execução do programa volte antes de chegar ao done. Para isto, temos respectivamente, os comandos break (que já vimos rapidamente nos exemplos do comado while) e continue, que funcionam da seguinte forma: O que eu não havia dito anteriormente é que nas suas sintaxes genéricas eles aparecem da seguinte forma: break [qtd loop] e continue [qtd loop] Onde qtd loop representa a quantidade dos loops mais internos sobre os quais os comandos irão atuar. Seu valor default é 1. 97
  • 99.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF . Duvido que você nunca tenha deletado um arquivo e logo após se arrependeu porque não devia tê-lo removido. Pois é, na décima vez que fiz isso, criei um script para simular uma lixeira, isto é, quando mando remover um (ou vários) arquivo(s), o programa "finge"que removeu-o, mas 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 remover then echo "Erro -> Uso: erreeme arq [arq] ... [arq]" echo "O uso de metacaracteres é permitido. Ex. erreeme arq*" exit 1 fi 98
  • 100.
    CDTC Centro deDifusã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... then mkdir $MeuDir # Vou cria-lo fi if [ ! -w $MeuDir ] # Se não posso gravar no diretório... then echo Impossivel salvar arquivos em $MeuDir. Mude permissao... exit 2 fi Erro=0 # Variavel para indicar o cod. de retorno do prg for Arq # For sem o "in"recebe os parametros passados do if [ ! -f $Arq ] # Se este arquivo não existir... then echo $Arq nao existe. Erro=3 continue # Volta para o comando for fi DirOrig=’dirname $Arq’ # Cmd. dirname informa nome do dir de $Arq if [ ! -w $DirOrig ] # Verifica permissão de gravacaoo no diretório then echo Sem permissao de remover no diretorio de $Arq Erro=4 continue # Volta para o comando for fi if [ "$DirOrig"= "$MeuDir"] # Se estou "esvaziando a lixeira"... then echo $Arq ficara sem copia de seguranca rm -i $Arq # Pergunta antes de remover [ -f $Arq ] || echo $Arq removido # Será que o usuario removeu? continue fi cd $DirOrig # Guardo no fim do arquivo o seu diretorio pwd » $Arq # original para usa-lo em um script de undelete mv $Arq $MeuDir # Salvo e removido echo $Arq removido 99
  • 101.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF done exit $Erro # Passo eventual numero do erro para o codigo de retorno Como você pode ver, a maior parte do script é formada por pequenas críticas aos parâmetros informados, mas como o script pode ter recebido diversos arquivos para remover, a cada arquivo que não se encaixa dentro do especificado, há um continue, para que a sequência volte para o loop do for de forma a receber outros arquivos. Quando você está no Windows (com perdão da má palavra) e tenta remover aquele monte de lixo com nomes esquisitos como HD04TG.TMP, se der erro em um deles, os outros não são removidos, não é? Então, o continue foi usado para evitar que um impropério desses ocorra, isto é, mesmo que dê erro na remoção de um arquivo, o programa continuará removendo os outros que foram passados. - Eu acho que a esta altura você deve estar curioso para ver o programa que restaura o arquivo removido, não é? Pois então aí vai um desafio: faça-o em casa e me traga para discutirmos no 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 de criticar 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 para julio.neves@gmail.com. Agora chega de papo que eu já estou de goela seca de tanto falar. Me acompanha 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! 100
  • 102.
    Capítulo 11 Parte VII 11.1O comando tput O maior uso deste comando é para posicionar o cursor na tela, mas também é muito usado para apagar dados da tela, saber a quantidade de linhas e colunas para poder posicionar corre- tamente um campo, apagar um campo cuja crítica detectou como errado. Enfim, quase toda a formatação da 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ó: 101
  • 103.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF . $ tput it 8 Neste exemplo, eu recebi o tamanho inicial da <TAB> ( Initial T ab), mas me diga: para que eu quero saber isso? Se você quiser saber tudo sobre o comando tput (e olha que é coisa que não acaba mais), veja em: http://www.cs.utah.edu/dept/old/texinfo/tput/tput.html#SEC4. Vamos fazer um programa bem fácil para mostrar alguns atributos deste comando. É o famoso e famigerado Alô Mundo só que esta frase será escrita no centro da tela e em vídeo reverso e após isso, o cursor voltará para a posição em que estava antes de escrever esta tão criativa frase. Veja: $ cat alo.sh #!/bin/bash # Script bobo para testar # o comando tput (versao 1) Colunas=’tput cols’ # Salvando quantidade colunas Linhas=’tput lines’ # Salvando quantidade linhas Linha=$((Linhas / 2)) # Qual eh a linha do meio da tela? Coluna=$(((Colunas - 9) / 2)) # Centrando a mensagem na tela tput sc # Salvando posicao do cursor tput cup $Linha $Coluna # Posicionando para escrever tput rev # Video reverso echo Alô Mundo tput sgr0 # Restaura video ao normal tput rc # Restaura cursor aa posição original Como o programa já está todo comentado, acho que a única explicação necessária seria para a 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 $#var 4 $ var="Papo de Botequim" $ echo $#var 16 Ahhh, melhorou! Então, agora sabemos que a construção $#variavel devolve a quantidade de caracteres de variável. Assim sendo, vamos otimizar o nosso programa para que ele escreva em vídeo reverso, no centro da tela a cadeia passada como parâmetro e depois o cursor volte à 102
  • 104.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 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 colunas Linhas=’tput lines’ # Salvando quantidade linhas Linha=$((Linhas / 2)) # Qual eh a linha do meio da tela? Coluna=$(((Colunas - $#1) / 2)) #Centrando a mensagem na tela tput sc # Salvando posicao do cursor tput cup $Linha $Coluna # Posicionando para escrever tput rev # Video reverso echo $1 tput sgr0 # Restaura video ao normal tput rc # Restaura cursor aa posição original Este script é igual ao anterior, só que trocamos o valor fixo da versão anterior (9), por $#1, onde este 1 é o $1 ou seja, esta construção devolve o tamanho do primeiro parâmetro passado para o programa. Se o parâmetro que eu quiser passar tiver espaços em branco, teria que colocá-lo todo entre aspas, senão o $1$1 por $*, que como sabemos é o conjunto de todos os parâmetros. Então, aquela linha ficaria assim: seria somente o primeiro pedaço. Para evitar este aborreci- mento, é 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 quando executar, passar a frase que você deseja centrar como parâmetro. 11.2 E agora podemos ler os dados na tela A partir de agora vamos aprender tudo sobre leitura, num pub londrino tomando scotch e não em um boteco desses tomando chope. Mas vamos em frente. Da última vez que nos encontramos aqui eu já dei uma palinha sobre o comando read. Para começarmos a sua análise mais detalhada. Veja só isso: $ read var1 var2 var3 Papo de Botequim $ echo $var1 Papo $ echo $var2 de $ echo $var3 103
  • 105.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Botequim $ read var1 var2 Papo de Botequim $ echo $var1 Papo $ echo $var2 de Botequim Como você viu, o read recebe uma lista separada por espaços em branco e coloca cada item desta lista em uma variável. Se a quantidade de variáveis for menor que a quantidade de ítens, 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 ainda acredita nisso? Vamos testar direto no prompt: $ oIFS="$IFS" $ IFS=: $ read var1 var2 var3 Papo de Botequim $ echo $var1 Papo de Botequim $ echo $var2 $ echo $var3 $ read var1 var2 var3 Papo:de:Botequim $ echo $var1 Papo $ echo $var2 de $ echo $var3 Botequim $ IFS="$oIFS" Viu, estava furado! O read lê uma lista, assim como o for, separada pelos caracteres da va- riável $IFS. Então, veja como isso pode facilitar a sua vida: $ grep julio /etc/passwd julio:x:500:544:Julio C. Neves - 7070:/home/julio:/bin/bash $ oIFS="$IFS"# Salvando IFS $ IFS=: $ grep julio /etc/passwd | read lname lixo uid gid coment home shell $ echo -e "$lnamen$uidn$gidn$comentn$homen$shell" julio 500 104
  • 106.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 544 Julio 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 campos de uma só vez. A opção -e do echo foi usada para que o n new line fosse entendido como um 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. Para ler um campo "Matrícula": $ echo -n "Matricula: "; read Mat # -n nao salta linha Matricula: 12345 $ echo $Mat 12345 Ou simplificando com a opção -p: $ read -p "Matricula: "Mat Matricula: 12345 $ echo $Mat 12345 Para ler uma determinada quantidade de caracteres: $ read -n5 -p"CEP: "Num ; read -n3 -p- Compl CEP: 12345-678$ $ echo $Num 12345 $ echo $Compl 678 105
  • 107.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 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 algarismo teclado, é 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 duas coisas: 1. O comando após o par de barras verticais (||) (o ou lógico, lembra-se?) será executado caso a 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> for teclado. Para ler um dado sem ser exibido na tela: $ read -sp "Senha: " Senha: $ echo $REPLY segredo Aproveitei um erro para mostrar um macete. Quando escrevi a primeira linha, esqueci de colocar o nome da variável que iria receber a senha, e só notei quando ia listar o seu valor. Felizmente a variável $REPLY do Bash, possui a última cadeia lida e me aproveitei disso para não perder a viagem. 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 ir para a tela. Como no exemplo anterior, a falta do new-line fez com que o prompt de comando ($) permanecesse na mesma linha. Bem, agora que sabemos ler na 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 bloco de instruções enquanto este comando for bem sucedido. Quando você está lendo um ar- quivo que 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: 106
  • 108.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 1 - Redirecionando a entrada do arquivo para o bloco do while assim: while read Linha do echo $Linha done < arquivo 2 - Redirecionando a saída de um cat para o while, da seguinte maneira: cat arquivo | while read Linha do echo $Linha done Cada um dos processos tem suas vantagens e desvantagens: Vantagens do primeiro processo: • É mais 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 vezes prejudica a vizualização do código. 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, pesado e 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/ while while read Linha do Ultimo="$Linha" echo -$Ultimo-" 107
  • 109.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF done echo "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 e outro depois de cada, e no final exibe o conteúdo da variável $Ultimo. Repare no entanto que o conteúdo desta variável permanece como (vazio). - Será que a variável não foi atualizada? - Foi, e isso pode ser comprovado porque a linha echo -$Ultimo-"lista corretamente as linhas. - Então, porque isso aconteceu? - Por que como eu disse, o bloco de instruções redirecionado pelo pipe (|) é executado em um subshell e lá as variáveis são atualizadas. Quando este subshell termina, as atualizações das variáveis vão para os píncaros do inferno junto com ele. Repare que vou fazer uma pequena mudança nele, passando o arquivo por redirecionamento de entrada (<) e as coisas passarão a funcionar na mais perfeita ordem: $ cat redirread.sh #!/bin/bash # redirread.sh # Exemplo de read passando arquivo por pipe. Ultimo="(vazio)" while read Linha do Ultimo="$Linha" echo -$Ultimo-" done < $0 # Passando o arq. do script ($0) p/ while echo "Acabou, Último=:$Ultimo:" 108
  • 110.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 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 importante macete que vou mostrar utilizando um exemplo prático. Suponha que você queira listar na tela um arquivo e a cada dez registros esta listagem pararia para que o operador pudesse ler o conteúdo da tela e ela só voltasse a rolar (scroll) após o operador digitar qualquer tecla. Para não gastar muito papel (da Linux Magazine), 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 # Prg de teste para escrever # 10 linhas e parar para ler # Versão 1 while read Num do let ContLin++ # Contando... echo -n "$Num "# -n para nao saltar linha ((ContLin % 10)) > /dev/null || read done < numeros Na tentativa de fazer um programa 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 da divisã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 aconteceu o seguinte problema: $ 10porpag.sh 1 2 3 4 5 6 7 8 9 10 12 13 14 15 16 17 18 19 20 21 23 24 25 26 27 28 29 30 Repare que faltou o número 11 e a listagem não parou no read. O que houve foi que toda a entrada do loop estava redirecionada do arquivo numeros e desta forma, a leitura foi feita em 109
  • 111.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF cima deste arquivo, desta forma perdendo o 11 (e também o 22). Vamos mostrar então como deveria ficar para funcionar: $ cat 10porpag.sh #!/bin/bash # Prg de teste para escrever # 10 linhas e parar para ler # Versão 2 while read Num do let ContLin++ # Contando... echo -n "$Num "# -n para nao saltar linha ((ContLin % 10)) > /dev/null || read < /dev/tty done < numeros Observe que agora a entrada do read foi redirecionada por /dev/tty, que nada mais é senão o terminal corrente, explicitando desta forma que aquela leitura seria feita do teclado e não de números. É bom realçar que isto não acontece somente quando usamos o redirecionamento de entrada, se houvéssemos usado o redirecionamento via pipe (|), o mesmo teria ocorrido. Veja agora a sua execução: $ 10porpag.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Isto está quase bom mas falta um pouco para ficar excelente. Vamos melhorar um pouco o exemplo para que você o reproduza e teste (mas antes de testar aumente o número de registros de numeros ou reduza o tamanho da tela, para que haja quebra). $ cat 10porpag.sh #!/bin/bash # Prg de teste para escrever # 10 linhas e parar para ler # Versão 3 clear while read Num do ((ContLin++)) # Contando... echo "$Num" ((ContLin % (’tput lines’ - 3))) || read -n1 -p"Tecle Algo « /dev/tty # para ler qq caractere clear # limpa a tela apos leitura 110
  • 112.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 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 linhas, listará 22 registros e parará para leitura. No comando read-n1 para ler somente um caractere sem 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 deixar um 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 julio.neves@gmail.com. Vou aproveitar também para mandar o meu jabá: diga para os amigos que quem estiver afim de fazer um curso porreta de programação em Shell (de 40 horas) que mande um e-mail para julio.neves@tecnohall.com.br para informar-se. Valeu! 111
  • 113.
    Capítulo 12 Parte VIII 12.1Funçõ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=$#Msg local Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha tput cup $LinhaMesg $Col echo "$Msg" tput cup $LinhaMesg $((Col + TamMsg + 1)) read -n1 SN [ ! $SN ] && SN=$2 # Se vazia coloca default em SN echo $SN | tr A-Z a-z # A saída de SN será em minúscula tput cup $LinhaMesg $Col; tput el # Apaga msg da tela return # Sai da função } Como podemos ver, uma função é definida quando fazemos nome_da_função () e todo o seu corpo 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 à passagem de parâmetros para programas, também valem para funções, mas é muito importante realçar que os parâmetros passados para um programa nã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 112
  • 114.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF foram criadas como local. A finalidade disso é simplesmente para economizar memória, já que ao sair da rotina, elas serão devidamente detonadas da partição e caso não tivesse usado este artifício, permaneceriam residentes. A linha de código que cria local Msg, concatena ao texto recebido ($1) um abre parênteses, a resposta default ($2) em caixa alta, uma barra, a outra resposta ($3) em caixa baixa e finaliza fechando 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 que no 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=$#Msg local Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha tput cup $LinhaMesg $Col echo "$Msg" read -n1 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela return # Sai da função } Esta é uma outra forma de definir uma função: não a chamamos como no exemplo anterior usando 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 a variável $* que como já sabemos é o conjunto de todos os parâmetros passados, para que o programador não precise usar aspas envolvendo a mensagem que deseja passar para a função. Para terminar com isso, vamos ver as alterações que o programa necessita quando usamos o conceito de funções: $ cat musinc6 #!/bin/bash # Cadastra CDs (versao 6) # # Área de variáveis globais LinhaMesg?=$((’tput lines’ - 3)) # Linha que msgs serão dadas para operador 113
  • 115.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF TotCols?=$(tput cols) # Qtd colunas da tela para enquadrar msgs # Área de funções 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 # abaixo colocaria em Msg o valor "Aceita? (S/n)" local Msg="$1 (’echo $2 | tr a-z A-Z’/’echo $3 | tr A-Z a-z’)" local TamMsg?=$#Msg local Col=$(((TotCols? - TamMsg?) / 2)) # Centra msg na linha tput cup $LinhaMesg $Col echo "$Msg" tput cup $LinhaMesg $((Col + TamMsg? + 1)) read -n1 SN [ ! $SN ] && SN=$2 # Se vazia coloca default em SN echo $SN | tr A-Z a-z # A saída de SN será em minúscula tput cup $LinhaMesg $Col; tput el # Apaga msg da tela return # 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 linha tput cup $LinhaMesg $Col echo "$Msg" read -n1 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela return # Sai da função } # O corpo do programa propriamente dito começa aqui clear echo " Inclusao de Músicas ==== == === 114
  • 116.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Título do Álbum: | Este campo foi Faixa < criado somente para |orientar o preenchimento Nome da Música: Intérprete:"# Tela montada com um único echo while true do tput cup 5 38; tput el # Posiciona e limpa linha read Album [ ! "$Album"] && # Operador deu { Pergunta "Deseja Terminar"s n [ $SN = "n"] && continue # Agora só testo a caixa baixa clear; exit # Fim da execução } grep -iq "^$Album^" musicas 2> /dev/null && { MandaMsg? Este álbum já está cadastrado continue # Volta para ler outro álbum } Reg="$Album^" # $Reg receberá os dados de gravação oArtista= # Guardará artista anterior while true do ((Faixa++)) tput cup 7 38 echo $Faixa tput cup 9 38 # Posiciona para ler musica read Musica [ "$Musica"] || # Se o operador tiver dado ... { Pergunta "Fim de Álbum?"s n [ "$SN"= n ] && continue # Agora só testo a caixa baixa break # Sai do loop para gravar dados } 115
  • 117.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF tput cup 11 38 # Posiciona para ler Artista [ "$oArtista"]&& echo -n "($oArtista) "# Artista anterior é default read Artista [ "$Artista"] && oArtista="$Artista" Reg="$Reg$oArtista $Musica:"# Montando registro tput cup 9 38; tput el # Apaga Musica da tela tput cup 11 38; tput el # Apaga Artista da tela done echo "$Reg»> musicas # Grava registro no fim do arquivo sort musicas -o musicas # Classifica o arquivo done Repare que a estruturação do _script_está conforme o gráfico a seguir: Variáveis Globais Funçõ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 dito porque no ponto em que o programador mencionou seu nome, o interpretador Shell já o havia localizado e registrado que era uma função. Algo interessante no uso de funções é fazê-las o mais genérico possível de forma que elas sirvam para outras aplicações, sem necessidade de serem reescritas. Essas duas que acabamos de ver têm uso generalizado, pois é difícil um script que tenha uma entrada de dados pelo teclado que não use uma rotina do tipo da MandaMsg ou não interage com o operador por algo semelhante à 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 de programaçã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, volte a prestar atenção! Bem, já que você está mesmo sem vontade de pensar e prefere ler a resposta, te darei uma dica: acho que você sabe que o .bash_profile é um dos programas que são automaticamente "executados"quando você se loga. Agora que te dei esta dica olhe novamente para a saída do ls e me diga o que há de diferente nela. 116
  • 118.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Como eu disse o .bash_profile é "executado"em tempo de logon e repare que não tem nenhum direito de execução. Isso se dá porque 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 sob o 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 lhe é passado como parâmetro. Melhor um exemplo que 453 palavras. Veja este scriptizinho a seguir: $ cat script_bobo cd .. 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_bobo jneves juliana paula silvie $ pwd /home/jneves Se eu mandei ele subir um diretório, porque não subiu? Subiu sim! O subshellscript tanto su- biu que 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: $ source script_bobo jneves juliana paula silvie $ pwd /home $ cd - /home/jneves $ . script_bobo jneves juliana paula silvie $ pwd /home Ah! Agora sim! Sendo passado como parâmetro do comando source ou .script foi executado no Shell corrente deixando neste, todo o ambiente criado. Agora damos um rewind para o início da explicação sobre este comando. Lá falamos do .bash_profile, e a esta altura você já deve saber que a sua incumbência é, logo após o login, deixar o ambiente de trabalho preparado para o usuário, e agora entendemos que é por isso mesmo que ele é executado usando este artifício. 117
  • 119.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF E agora você deve estar se perguntando se é só para isso que este comando serve, e eu lhe digo que sim, mas isso nos traz um monte de vantagens e uma das mais usadas é tratar funções como rotinas externas. Veja uma outra forma de fazer o nosso programa para incluir CDs no arquivo musicas: $ cat musinc7 #!/bin/bash # Cadastra CDs (versao 6) # # Área de variáveis globais LinhaMesg?=$((’tput lines’ - 3)) # Linha que msgs serão dadas para operador TotCols?=$(tput cols) # Qtd colunas da tela para enquadrar msgs # O corpo do programa propriamente dito começa aqui clear echo " Inclusao de Músicas ==== == === Título do Álbum: | Este campo foi Faixa < criado somente para |orientar o preenchimento Nome da Música: Intérprete:"# Tela montada com um único echo while true do tput cup 5 38; tput el # Posiciona e limpa linha read Album [ ! "$Album"] && # Operador deu { source pergunta.func "Deseja Terminar"s n [ $SN = "n"] && continue # Agora só testo a caixa baixa clear; exit # Fim da execução 118
  • 120.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF } grep -iq "^$Album^" musicas 2> /dev/null && { . mandamsg.func Este álbum já está cadastrado continue # Volta para ler outro álbum } Reg="$Album^" # $Reg receberá os dados de gravação oArtista= # Guardará artista anterior while true do ((Faixa++)) tput cup 7 38 echo $Faixa tput cup 9 38 # Posiciona para ler musica read Musica [ "$Musica"] || # Se o operador tiver dado ... { . pergunta.func "Fim de Álbum?"s n [ "$SN"= n ] && continue # Agora só testo a caixa baixa break # Sai do loop para gravar dados } tput cup 11 38 # Posiciona para ler Artista [ "$oArtista"]&& echo -n "($oArtista) "# Artista anterior é default read Artista [ "$Artista"] && oArtista="$Artista" Reg="$Reg$oArtista $Musica:"# Montando registro tput cup 9 38; tput el # Apaga Musica da tela tput cup 11 38; tput el # Apaga Artista da tela done echo "$Reg»> musicas # Grava registro no fim do arquivo sort musicas -o musicas # Classifica o arquivo done 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 por qualquer 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 dando maior 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 # $2 - Valor a ser aceito com resposta default 119
  • 121.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF # $3 - O outro valor aceito # Supondo que $1=Aceita?, $2=s e $3=n, a linha # abaixo colocaria em Msg o valor "Aceita? (S/n)" Msg="$1 (’echo $2 | tr a-z A-Z’/’echo $3 | tr A-Z a-z’)" TamMsg=$#Msg Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha tput cup $LinhaMesg $Col echo "$Msg" tput cup $LinhaMesg $((Col + zTamMsg + 1)) read -n1 SN [ ! $SN ] && SN=$2 # Se vazia coloca default em SN echo $SN | tr A-Z a-z # A saída de SN será em minúscula tput 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=$#Msg Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha tput cup $LinhaMesg $Col echo "$Msg" read -n1 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela Em ambos os arquivos, fiz somente duas 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 que só pode ser usada no corpo de funções e, portanto, estas variáveis permanecem no ambiente do 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ão idênticos e significam rotina executada sem erros; 3. O comando que estamos acostumados a usar para gerar código de retorno é o exit, mas a saída de uma rotina externa não pode ser feita desta forma, porque por estar sendo executada no mesmo Shell que o script chamador, o exit simplesmente encerraria este Shell, terminando a execução de todo o script; 4. De onde surgiu a variável LinhaMesg? Ela veio do musinc7, porque ela havia sido declarada antes da chamada das rotinas (nunca esquecendo que o Shell que está interpretando o script e estas rotinas é o mesmo); 5. Se você decidir usar rotinas externas, não se avexe, abunde os comentários (principalmente 120
  • 122.
    CDTC Centro deDifusã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 novidades para melhorar os scriptslistartista no qual você passava o nome de um artista como parâmetro e ele devolvia as suas músicas? Ele era assim: $ cat listartista #!/bin/bash # Dado um artista, mostra as suas musicas # versao 2 if [ $# -eq 0 ] then echo Voce deveria ter passado pelo menos um parametro exit 1 fi IFS=" :" for ArtMus in $(cut -f2 -d^ musicas) do echo "$ArtMus"| grep -i "^$*~" > /dev/null && echo $ArtMus verb=|= cut -f2 -d done - Claro que me lembro!... - Então para firmar os conceitos que te passei, faça ele com a tela formatada, em loop, de forma que ele só termine quando receber um <ENTER> puro no nome do artista. Ahhh! Quando a listagem atingir a antepenúltima linha da tela, o programa deverá dar uma parada para que o operador possa lê-las, isto é, suponha que a tela tenha 25 linhas. A cada 22 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 que acabamos de desenvolver. - Chico, manda mais dois, o meu é com pouca pressão... 121
  • 123.
    Capítulo 13 Parte IX 13.1Envenenando a escrita - Ufa! Agora você já sabe tudo sobre leitura, mas sobre escrita está apenas engatinhando. Já sei que 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 uma instrução muito interessante - é o printf - sua sintaxe é a seguinte: printf formato [argumento...] Onde: formato - é uma cadeia de caracteres que contém 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 caractere % 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: 122
  • 124.
    CDTC Centro deDifusã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 de detalhes e, portanto, chato para explicar e, pior ainda para ler ou estudar, vamos passar direto aos 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 "%cn1 caracter" 1 Saltou linha mas ainda não listou a cadeia inteira $ printf "%c caracteren"1 1 caractere Esta é a forma correta o %c recebeu o 1 $ a=2 $ printf "%c caracteresn"$a 2 caracteres O %c recebeu o valor da variável $a $ printf "%10c caracteresn"$a 2 caracteres $ printf "%10cn"$a caracteres 2 c Repare que nos dois últimos exemplos, em virtude do %c, só foi listado um caractere de cada cadeia. 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 "%dn"32 32 $ printf "%10dn"32 32 Preenche com brancos à esquerda e não com zeros $ printf "%04dn"32 0032 04 após % significa 4 dígitos com zeros à esquerda $ printf "%en"$(echo "scale=2 ; 100/6" | bc) 1.666000e+01 O default do %e é 6 decimais $ printf "%.2en"‘echo "scale=2 ; 100/6" | bc‘ 1.67e+01 O .2 especificou duas decimais $ printf "%fn"32.3 32.300000 O default do %f é 6 decimais $ printf "%.2fn"32.3 123
  • 125.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 32.30 O .2 especificou duas decimais $ printf "%.3fn"‘echo "scale=2 ; 100/6" | bc‘ 33.330 O bc devolveu 2 decimais. o printf colocou 0 à direita $ printf "%on"10 12 Converteu o 10 para octal $ printf "%03on"10octal, né? $ printf "%sn"Peteleca Peteleca $ printf "%15sn"Peteleca Peteleca Peteleca com 15 caracteres enchidos com brancos $ printf "%-15sNevesn"Peteleca Peteleca Neves O menos (-) encheu à direita com brancos $ printf "%.3sn"Peteleca Pet 3 trunca as 3 primeiras $ printf "%10.3san"Peteleca Peta Pet com 10 caracteres concatenado com a (após o s) $ printf "EXEMPLO %xn"45232 EXEMPLO b0b0 Transformou para hexa mas os zeros não combinam $ printf "EXEMPLO %Xn"45232 EXEMPLO B0B0 Assim disfarçou melhor (repare o X maiúsculo) $ printf "%X %XL%Xn"49354 192 10 C0CA 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 como resultado 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 que o 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 lhe ajudar na elaboração de scripts. Então, as principais são: 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 124
  • 126.
    CDTC Centro deDifusã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 do locale). 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 o próximo prompt primário (definido em $PS1). Se esta variável estiver sem valor ou com um valor menor ou igual a zero, a verificação de novas correspondências não serão efetuada. PATH Caminhos que serão pesquisados para tentar localizar um arquivo especificado. Como cada script é um arquivo, caso use o diretório corrente (.) na sua variável $PATH, você não necessitará de usar o ./scrp para que scrp seja executado. Basta fazer scrp. Este é o modo que procedo aqui no Botequim. PIPESTATUS É uma variável do tipo vetor (array) que contém uma lista valores de código de retorno do último pipeline executado, isto é, um array que abriga cada um dos $? de cada instruçã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 se está repetindo muito uma determinada instrução. PS1 É o prompt principal. No "Papo de Botequim"usamos os seus defaults: $ para usuário comum 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 uma googlada) PS2 Também chamado prompt de continuação, é aquele sinal de maior (>) que aparece após um <ENTER> sem o comando ter sido encerrado. PWD Possui o caminho completo ($PATH) do diretório corrente. Tem o mesmo efeito do comando pwd. 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. 125
  • 127.
    CDTC Centro deDifusã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ário 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 timeout do comando read. No prompt, este valor é interpretado como o tempo de espera por uma ação antes de expirar a seção. Supondo que a variável contenha 30, o Shell dará logout apó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 dos seus antecessores (., .. e ~), o cd foi executado para /usr/local/bin • LANG $ date Thu Apr 14 11:54:13 BRT 2005 $ LANG=pt_BR date Qui Abr 14 11:55:14 BRT 2005 Com a especificação da variável LANG=pt_BR (português do Brasil), a data passou a ser in- formada 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 $ who jneves pts/0 Apr 11 16:26 (10.2.4.144) jneves pts/1 Apr 12 12:04 (10.2.4.144) $ who | grep ^botelho $ echo $PIPESTATUS[*] 0 1 Neste exemplo, mostramos que o usuário botelho não estava "logado", em seguida executa- mos um pipeline que procurava por ele. Usa-se a notação [*] em um array para listar todos os seus elementos, e desta forma vimos que a primeira instrução (who) foi bem sucedida (código de retorno 0) 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)) 126
  • 128.
    CDTC Centro deDifusã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 da divisão de qualquer número por 101 varia entre 0 e 100. • REPLY $ who jneves pts/0 Apr 11 16:26 (10.2.4.144) jneves pts/1 Apr 12 12:04 (10.2.4.144) $ who | grep ^botelho $ echo $PIPESTATUS[*] 0 1 $ read -p "Digite S ou N: " Digite S ou N: N $ echo $REPLY N Eu sou do tempo que memória era um bem precioso que custava muito caro. Então, para pe- gar um S ou um N, não costumo a alocar um espaço especial e assim sendo, pego o que foi digitado 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 ajudam muito, facilitam a visualização, manutenção e depuração do código, mas não são tão eficientes quanto os intrínsecos (built-ins). Quando o nosso problema for performance, devemos dar preferência ao uso dos intrínsecos e a partir de agora te mostrarei algumas técnicas para o seu programa. 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, o tr, o sed e outras de forma mais ágil. 127
  • 129.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF • Se em uma pergunta o S é oferecido como valor default (padrão) e a saída for para a variá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 $#cadeia 4 • Para extrair de uma cadeia da posição um até o final, fazemos: $ cadeia=abcdef $ echo $cadeia:1 bcdef 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, a origem da contagem é zero e não um. 128
  • 130.
    CDTC Centro deDifusã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 do Shell (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 somente Botequim:. $ 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 como no 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 texto da seguinte forma: echo Uso: $0##*/ texto da mensagem de erro Neste exemplo, seria suprimido à esquerda tudo até a última barra (/) do caminho (path), desta forma sobrando somente o nome do programa. • O uso do percentual (%) é como se olhássemos o jogo-da-velha (#) no espelho, isto é, são simétricos. Então vejamos um exemplo para provar isso: 129
  • 131.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF $ echo $cadeia Papo 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 sempre combinarão com a maior possibilidade, veja o exemplo a seguir onde a intenção era trocar Papo de Botequim por Conversa de Botequim: $ echo $cadeia Papo de Botequim $ echo $cadeia/*o/Conversa Conversatequim 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/Conversa Conversa de Botequim $ echo $cadeia/????/Conversa Conversa de Botequim • Trocando todas as ocorrências de uma subcadeia por outra. Quando fazemos: $ echo $cadeia//o/a Papa de Batequim Trocamos todos as letras o por a. Outro exemplo mais útil é para contarmos a quantidade de arquivos existentes no diretório corrente. Observe a linha a seguir: $ ls | wc -l 30 Viu? O wc produz um monte de espaços em branco no início. Para tirá-los, podemos fazer: 130
  • 132.
    CDTC Centro deDifusã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, montei esta 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 $Passaro quero quero $ echo "Como diz o sulista - "$Passaro/#quero/não Como diz o sulista - não quero Para trocar no final, fazemos: $ echo "Como diz o nordestino - "$Passaro/%quero/não Como diz o nordestino - quero não - Agora já chega, o papo hoje foi muito chato porque foi muita decoreba, mas o principal é você ter entendido o que te falei e, quando precisar, consulte estes guardanapos em que rabisquei estas dicas e depois guarde-os para consultas futuras. Mas voltando à vaca fria: tá na hora de tomar outro e ver o jogo do mengão. Na próxima vou te dar moleza e só vou cobrar o seguinte: pegue a rotina pergunta.func, (a que na qual falamos no início do nosso bate papo de hoje) e otimize-a para 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. 131
  • 133.
    Capítulo 14 Parte X 14.1O comando eval - Vou te mostrar 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 de execuçã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 parecia bastante lógico... - O seu raciocínio realmente foi lógico, o problema é que você esqueceu de uma das primeiras coisas que te falei aqui no Boteco e vou repetir. O Shell usa a seguinte ordem para resolver uma linha de comandos: • Resolve os redirecionamentos; • Substitui as 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 132
  • 134.
    CDTC Centro deDifusã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 prompt do terminal. Quando você põe o eval na frente, no entanto, o que ocorre é que o Shell trata cmd como 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 $$var2 3 Na primeira passada a contrabarra () seria retirada e $var2 seria resolvido produzindo var1, para a segunda passada teria sobrado echo $var1, que produziria o resultado esperado. Agora vou colocar um comando dentro de var2: $ var2=ls Vou executar: $ $var2 10porpag1.sh alo2.sh listamusica logaute.sh 10porpag2.sh confuso listartista mandamsg.func 10porpag3.sh contpal.sh listartista3 monbg.sh alo1.sh incusu logado Agora vamos colocar em var2 o seguinte: ls $var1; e em var1 vamos colocar l*, vejamos: $ var2=’ls $var1’ $ var1=’l*’ $ $var2 ls: $var1: No such file or directory $ eval $var2 listamusica listartista listartista3 logado logaute.sh Novamente, no tempo de substituição das variáveis, $var1 ainda não havia se apresentado ao 133
  • 135.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Shell para ser resolvida, desta forma só nos resta executar o comando eval para dar as duas passadas 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 operador escolhesse 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=1 printf "%11st%snn"Opção Programa CASE=’case $opt in’ for arq in *.sh do printf "t%03dt%sn"$i $arq CASE="$CASE "$(printf "%03d)t %s;;"$i $arq) i=$((i+1)) done CASE="$CASE *) . erro;; esac" read -n3 -p "Informe a opção desejada: "opt echo eval "$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 seguida come- cei a montar dinamicamente a variável $CASE, na qual ao final será feito um eval para execução do programa escolhido. Repare no entanto que dentro do loop do for existem dois printf: o pri- meiro serve para formatar a tela e o segundo para montar o case (se antes do comando read você colocar uma linha echo "$CASE", verá que o comando case montado dentro da variável está todo indentado. Frescura, né?. Na saída do for, foi adicionada uma linha à variável $CASE, para no caso de se fazer uma opção inválida, ser executada uma função externa para dar men- sagens de erro. Vamos executá-lo para ver a saída gerada: $ fazmenu.sh Opcao Programa 001 10porpag1.sh 002 10porpag2.sh 003 10porpag3.sh 134
  • 136.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF 004 alo1.sh 005 alo2.sh 006 contpal.sh 007 fazmenu.sh 008 logaute.sh 009 monbg.sh 010 readpipe.sh 011 redirread.sh Informe a opção desejada: Neste programa seria interessante darmos uma opção de término, e para isso seria necessário a inclusão de uma linha após o loop de montagem da tela e alterarmos a linha na qual fazemos a atribuiçã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=1 printf "%11st%snn" Opção Programa CASE=’case $opt in’ for arq in *.sh do printf "t%03dt%sn" $i $arq i=$((i+1)) done printf "t%dt%snn"999 "Fim do programa"# Linha incluida CASE="$CASE 999) exit;; # Linha alterada *) ./erro;; esac" read -n3 -p "Informe a opção desejada: "opt echo eval "$CASE" 14.2 Sinais de Processos Existe no Linux uma coisa chamada sinal (signal). Existem diversos sinais que podem ser mandados para (ou gerados por) processos em execução. Vamos, de agora em diante, dar uma olhadinha nos sinais mandados para os processos e mais à frente vamos dar uma passada rápida pelos sinais gerados por processos. 135
  • 137.
    CDTC Centro deDifusã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 comando kill, algumas seqüências de teclas também podem gerar sig. A tabela a seguir mostra os sinais mais importantes para monitorarmos: . Além destes sinais, existe o famigerado -9 ou SIGKILL que, para o processo que o está rece- bendo, equivale a colocar o dedo no botão de desligar do computador o que seria altamente indesejável já que muitos programas necessitam "limpar o meio de campo"ao seu término. Se o seu final ocorrer 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 de arquivos 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 anormais de 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 ... SN ou trap ’cmd1; cmd2; cmdn’ S1 S2 ... SN Onde os comandos cmd1, cmd2, cmdn serão executados caso o programa receba os sinais 136
  • 138.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF S1 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 podem ser também uma função interna, uma externa ou outro script. Para entender o uso de aspas (") e apóstrofos (') vamos recorrer a um exemplo que trata um fragmento de um script que faz um ftp para uma máquina remota ($RemoComp), na qual o usuário é $Fulano, sua senha é $Segredo e vai transmitir o arquivo contido em $Arq. Suponha ainda que estas quatro variáveis foram recebidas em uma rotina anterior de leitura e que este script é muito usado por diversas pessoas da instalação. Vejamos este trecho de código: ftp -ivn $RemoComp « FimFTP » /tmp/$ $ 2» /tmp/$ $ user $Fulano $Segredo binary get $Arq FimFTP Repare que, tanto as saídas dos diálogos do ftp, como os erros encontrados, estão sendo re- direcionados 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úmero do processo (PID), que é único, e com este tipo de construção evita-se que dois ou mais usuários disputem a posse e os direitos sobre o arquivo. Caso este ftp seja interrompido por um kill ou um <CTRL+C>, certamente deixará lixo no disco. É exatamente esta a forma como mais se usa o comando trap. Como isto é trecho de um script, devemos, logo no seu 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 programa encerrar (no exit dentro do comando trap) ou um fim normal (sinal 0), o arquivo /tmp/$ $ seria removido. Caso na linha de comandos do trap não houvesse a instrução exit, ao final da execução desta linha o fluxo do programa retornaria ao ponto em que estava quando recebeu o sinal que originou a execução deste trap. Este trap poderia ser subdividido, ficando da seguinte forma: trap "rm -f /tmp/$ $"0 trap "exit"1 2 3 15 Assim, ao receber um dos sinais o programa terminaria e ao terminar, geraria um sinal 0 que removeria o arquivo. Caso seu fim seja normal, o sinal 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 é interpretado 137
  • 139.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF (e é por isso que é usual colocá-lo no início do programa) e novamente quando um dos sinais listados é recebido. Então, no último exemplo, o valor de $ $ será substituído no momento que o comando trap foi lido da primeira vez, já que as aspas (") não protegem o cifrão ($) da interpre- taçã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, o Shell 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 script1: Neste 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 interpretada neste momento e o PID do script2 seria diferente do encontrado em /tmp/$ $ (não esqueça que $ $ é a variável que contém o PID do processo ativo). • 2º Caso: O ftp acima encontra-se em script2: Neste caso, o argumento do comando trap deveria estar entre apóstrofos ('), pois caso a interrupção se desse durante a execução de script1, o arquivo não teria sido criado, caso ocorresse durante a execução de script2, o valor de $ $ seria o PID deste processo, que coincidiria com o de /tmp/$ $. O comando trap, quando executado sem argumentos, lista os sinais que estão sendo monitora- dos no ambiente, bem como a linha de comando que será executada quando tais sinais forem recebidos. Se a linha de comandos do trap for nula (vazia), isto significa que os sinais especificados 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, quando nã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 o seguinte, 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ém tomar 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. 138
  • 140.
    CDTC Centro deDifusã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 for gerado um sinal de interrupção, este não terá efeito nem sobre o Shell principal nem sobre os Subshell 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 se encontre 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 -echo read Senha stty 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 o programa e, caso ele agisse desta forma, o que quer que ele escrevesse, não apareceria na tela do seu terminal. Para evitar que isso aconteça, o melhor a fazer é: echo -n "Senha: " trap "stty echo exit"2 3 stty -echo read Senha stty echo trap 2 3 Para terminar este assunto, abra uma console gráfica e escreva no prompt de comando o se- guinte: $ trap "echo Mudou o tamanho da janela"28 Em seguida, pegue o mouse e arraste-o de forma a variar o tamanho da janela corrente. Sur- preso? É o Shell orientado a eventos Agora escreva assim: $ trap "echo já era"17 Em seguida faça: $ sleep 3 & 139
  • 141.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Você acabou de criar um subshell que irá dormir durante três segundos em background. Ao fim deste tempo, você receberá a mensagem já era, porque o sinal 17 é emitido a cada vez que um subshell termina a sua execução. Para devolver estes sinais aos seus defaults, faça: $ trap 17 28 Ou $ trap ? 17 28 Acabamos de ver mais dois sinais que não são tão importantes como os que vimos anterior- mente, mas vou registrá-los na tabela a seguir: . Muito legal este comando, né? Se você descobrir algum caso bacana de uso de sinais, por favor me informe por e-mail porque é muito rara a literatura sobre o assunto. 14.5 O comando getopts O comando getopts recupera as opções e seus argumentos de uma lista de parâmetros de acordo com a sintaxe POSIX.2, isto é, letras (ou números) após um sinal de menos (-) seguidas ou não de 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 da letra, 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 140
  • 142.
    CDTC Centro deDifusã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 comando getopts 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ão definida 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. Quando o getopts obtém uma opção, ele armazena o índice do próximo parâmetro a ser processado na variável OPTIND. Quando uma opção tem um argumento associado (indicado pelo : na cadeiadeopcoes), getopts armazena 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 de có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_LETRA do echo "getopts fez a variavel OPT_LETRA igual a ’$OPT_LETRA’" echo "OPTARG eh ’$OPTARG’" done used_up=’expr $OPTIND - 1’ echo "Dispensando os primeiros $OPTIND-1 = $used_up argumentos" shift $used_up 141
  • 143.
    CDTC Centro deDifusã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 arq2 getopts fez a variavel OPT_LETRA igual a ’h’ OPTARG eh ” getopts fez a variavel OPT_LETRA igual a ’P’ OPTARG eh ’impressora’ Dispensando os primeiros $OPTIND-1 = 2 argumentos O que sobrou da linha de comandos foi ’arq1 arq2’ Desta forma, sem ter muito trabalho, separei todas as 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) separado da opção (-P), o resultado seria exatamente o mesmo, exceto pelo $OPTIND, já que neste caso ele identifica um conjunto de três opções/argumentos e no anterior somente dois. Veja só: $ getoptst.sh -h -P impressora arq1 arq2 getopts fez a variavel OPT_LETRA igual a ’h’ OPTARG eh ” getopts fez a variavel OPT_LETRA igual a ’P’ OPTARG eh ’impressora’ Dispensando os primeiros $OPTIND-1 = 3 argumentos O que sobrou da linha de comandos foi ’arq1 arq2’ Repare, no exemplo a seguir, que se passarmos uma opção inválida, a variável $OPT_LETRA receberá 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 – f getopts fez a variavel OPT_LETRA igual a ’?’ OPTARG eh ” getopts fez a variavel OPT_LETRA igual a ’P’ OPTARG eh ’impressora’ Dispensando os primeiros $OPTIND-1 = 2 argumentos O que sobrou da linha de comandos foi ’arq1 arq2’ - Me diz uma coisa: você 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 foi didático, mas imagine um programa que aceitasse muitas opções e seus parâmetros poderiam ou não estar colados às opções, suas opções também poderiam ou não estar coladas, ia ser um case 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 tanta informação nova na minha cabeça. Vamos tomar a saideira ou você ainda quer explicar alguma particularidade do Shell? - Nem um nem outro, eu também já cansei, mas hoje não vou tomar a saideira porque estou 142
  • 144.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF indo dar aula na UniRIO, que é a primeira universidade federal que está preparando no uso de Software Livre, seus alunos do curso de graduação em informática. Mas antes vou te deixar um problema: 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 a minha conta. 143
  • 145.
    Capítulo 15 Parte XI 15.1Named Pipes Um outro tipo de pipe é o named pipe, que também é chamado de FIFO. FIFO é um acrônimo de First In First Out que se refere à propriedade em que a ordem dos bytes entrando no pipe é a mesma 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 pipe1 prw-r-r– 1 julio dipao 0 Jan 22 23:11 pipe1| O p na coluna mais à esquerda indica que fifo1 é um named pipe. O resto dos bitspipe funci- onam como um arquivo normal. Nos sistemas mais modernos uma barra vertical (|) colocado ao fim do nome do arquivo, é outra dica, e nos sistemas LINUX, onde a 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, normalmente situado no diretório /etc. Nos sistemas mais modernos, a mesma tarefa é feita pelo mkfifo. O programa mkfifo recebe um ou mais nomes como argumento e cria pipes com estes nomes. Por exemplo, para criar um named pipe com o nome pipe1, faça: $ mkfifo pipe1 Como sempre, a melhor forma de mostrar como algo funciona é dando exemplos. Suponha que nós tenhamos criado o named pipe mostrado anteriormente. Vamos agora trabalhar com duas seções ou duas consoles virtuais ou uma de cada. Em uma delas faça: $ ls -l > pipe1 e em outra faça: $ cat < pipe1 144
  • 146.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF A saída do comando executado na primeira console foi exibida na segunda. Note que a 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ão, o sistema operacional suspendeu o primeiro processo até que o segundo "abrisse"o pipe. Para que um processo que usa pipe não fique em modo de wait, é necessário que em uma ponta do pipe 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 um determinado named pipe e ele só sairá, se outro processo "falar"ou "ouvir"aquele pipe. Você já viu que o uso desta ferramenta é ótimo para sincronizar processos e para fazer bloqueio em arquivos de forma a evitar perda/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 blocos de 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 BigFile3 do if sort $Arq then Manda=va 145
  • 147.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF else Manda=pare break fi done echo $Manda > pipe1 [ $Manda = pare ] && { echo Erro durante a classificação dos arquivos exit 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 cadeia pare é enviada pelo pipe1 e programa1 é descontinuado com um fim anormal. Enquanto, o Programa1 executava o seu primeiro bloco (as classificações) o Programa2 exe- cutava o seu BLOCO1, processando as suas rotinas de abertura e menu paralelamente ao Pro- grama1, 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 OK exit 1 fi Após a execução de seu primeiro bloco, o Programa2 passará a "ouvir"o pipe1, ficando parado até que as classificações do Programa1 terminem, testando a seguir a mensagem passada pelo pipe1 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 quantos hits recebe uma determinada URL e a rotina de contagem está da seguinte maneira: Hits="$(cat page.hits 2> /dev/null)"|| Hits=0 echo $((Hits=Hits++)) > page.hits 146
  • 148.
    CDTC Centro deDifusã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 antes da sua gravação, isto é, basta que o segundo acesso seja feito após o primeiro ter executado a primeira 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 incrementar o contador. Note que ele vai ser usado por qualquer página no nosso site que precise de um contador. $ cat contahits.sh #!/bin/bash PIPE="/tmp/pipe_contador"# arquivo named pipe # dir onde serao colocados os arquivos contadores de cada pagina DIR="/var/www/contador" [ -p "$PIPE"] || mkfifo "$PIPE" while : do for URL in $(cat < $PIPE) do FILE="$DIR/$(echo $URL | sed ’s,.*/„’)" # OBS1: no sed acima, como precisava procurar # uma barra,usamos vírgula como separador. # OBS2: quando rodar como daemon comente a proxima linha echo "arquivo = $FILE" n="$(cat $FILE 2> /dev/null)"|| n=0 echo $((n=n+1)) > "$FILE" done done Como só este script altera os arquivos, não existe problema de concorrência. Este script será um daemon, isto é, rodará em background. Quando uma página sofrer um acesso, ela escreverá a sua URL no arquivo de pipe. Para testar, execute este comando: echo "teste_pagina.html» /tmp/pipe_contador Para evitar erros, em cada página que quisermos adicionar o contador acrescentamos a seguinte linha: <!–#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. 147
  • 149.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Este último exemplo, é fruto de uma idéia que troquei com o amigo e mestre em Shell, Thobias Salazar Trevisan que escreveu o script e colocou-o em sua excelente URL. Aconselho a todos que querem aprender Shell a dar uma olhada nela (Dê uma olhada e inclua-a nos favoritos). Ahhh! Você pensa que o assunto sobre named pipes está esgotado? Enganou-se. Vou mos- trar um uso diferente a partir de agora. 15.4 Substituição de processos Acabei várias 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 ou um pipeline de comandos entre parênteses e um < ou um > grudado na frente do parêntese da esquerda. Por exemplo, teclando-se o comando: $ cat <(ls -l) Resultará no comando ls -l executado em um subshell como é normal (por estar entre 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 dispositivo ló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 o comando cmp, fazendo: $ cmp <(cat dir/*) <(cat dir.bkp/*) || echo backup furado ou, melhor ainda: $ cmp <(cat dir/*) <(cat dir.bkp/*) >/dev/null || echo backup furado Da forma acima, a 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 de ambos os diretórios, pois qualquer modificação que um arquivo sofra é mostrada na data/hora de alteração e/ou no tamanho do arquivo. Veja como ficaria: $ cmp <(ls -l dir) <(ls -l dir.bkp) >/dev/null || echo backup furado 148
  • 150.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Este é um exemplo meramente didático, mas são tantos os comandos que produzem mais de uma linha de saída, que serve como guia para outros. Eu quero gerar uma listagem dos meus arquivos, numerando-os e ao final dar o total de arquivos do diretório corrente: while read arq do ((i++)) # assim nao eh necessario inicializar i echo "$i: $arq" done < <(ls) echo "No diretorio corrente (’pwd’) existem $i arquivos" Eu sei que existem outras formas de executar a mesma tarefa. Usando o comando while, a forma mais comum de resolver esse problema seria: ls | while read arq do ((i++)) # assim nao eh necessario inicializar i echo "$i: $arq" done echo "No diretorio corrente (’pwd’) existem $i arquivos" Quando executasse o script, pareceria estar tudo certo, porém no comando echodone, você verá que o valor de $i foi perdido. Isso deve-se ao fato desta variável estar sendo incrementada em um subshell criado pelo pipe (|) e que terminou no comando done, levando com ele todas as variáveis criadas no seu interior e as alterações feitas em todas as variáveis, inclusive as criadas externamente. Somente para te mostrar que uma variável criada fora do subshell e alterada em seu interior perde as alterações feitas ao seu final, execute o script a seguir: #!/bin/bash LIST= # Criada no shell principal ls | while read FILE # Inicio do subshell do LIST="$FILE $LIST"# Alterada dentro do subshell done # Fim do subshell echo :$LIST: Ao final da execução você verá que aperecerão apenas dois-pontos (::). Mas no início deste exemplo eu disse que era meramente didático porque existem formas melhores de fazer a mesma tarefa. Veja só estas duas: $ ls | ln ou então, usando a própria substituição de processos: $ cat -n <(ls) 149
  • 151.
    CDTC Centro deDifusão de Tecnologia e Conhecimento Brasil/DF Um último exemplo: você deseja comparar arq1 e arq2 usando o comando comm, mas este comando 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 mal dos políticos é só mandar um e-mail para julio.neves@gmail.com. Vou aproveitar também para mandar o meu jabá: diga para os amigos que quem estiver afim de fazer um curso porreta de programação em Shell que mande um e-mail para julio.neves@uniriotec.br para informar-se. Valeu! 150