shell script

57
Shell Script Linux está sendo uma revolução para a computação. Ele une liberdade com conhecimento e tecnologia. De uma forma bem simples envolve e estimula o usuário a conhecer a sua própria máquina e o seu sistema operacional. Uma das formas dessa interação entre o homem e a máquina é a interface que nela contém, sendo ela o próprio terminal na forma de comandos. 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. Lições Comece aqui Expressões Regulares Parte I o Ambiente Shell; o Linhas de comando; o Caracteres de remoção, redirecionamento e ambiente. Parte II o A família grep; o Passagens de parâmetros. Parte III o Comando cut; o Comando tr; o Usando separadores; o Comando condicionais. Parte IV o Comando test; o Simplificações dos comandos condicionais. Parte V o Comandos de Loop (ou laço); o O Comando for; o Primeira sintaxe do comando for: ; o Segunda sintaxe do comando for: ; o Terceira sintaxe do comando for: . Expressões Regulares Introdução

Upload: api-3838125

Post on 07-Jun-2015

974 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Shell Script

Shell ScriptLinux está sendo uma revolução para a computação. Ele une liberdade com conhecimento e tecnologia. De uma forma bem simples envolve e estimula o usuário a conhecer a sua própria máquina e o seu sistema operacional. Uma das formas dessa interação entre o homem e a máquina é a interface que nela contém, sendo ela o próprio terminal na forma de comandos.

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.

Lições

Comece aqui

Expressões Regulares

Parte I

o Ambiente Shell;

o Linhas de comando;

o Caracteres de remoção, redirecionamento e ambiente.

Parte II

o A família grep;

o Passagens de parâmetros.

Parte III

o Comando cut;

o Comando tr;

o Usando separadores;

o Comando condicionais.

Parte IV

o Comando test;

o Simplificações dos comandos condicionais.

Parte V

o Comandos de Loop (ou laço);

o O Comando for;

o Primeira sintaxe do comando for: ;

o Segunda sintaxe do comando for: ;

o Terceira sintaxe do comando for: .

Expressões Regulares

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 "metacaracteres" 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

Page 2: Shell Script

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 investido em seu aprendizado é recompensado pela larga variedade de aplicativos onde ele pode ser praticado.

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 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 : shell

Voltando ao grep.

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

aluno@computador:~$ grep root /etc/passwdroot:x:0:0:root:/root:/bin/bash

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 caracteres normais formam algo maior, uma expressão. O importante é compreender bem cada um individualmente, e depois apenas lê-los em seqüência.

1. 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, ...".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:

Page 3: Shell Script

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:EvitePrefira:[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:[:;,.!?][^ ]

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 palavras, eles dizem a quantidade de repetições que o átomo anterior pode ter, quantas vezes ele pode aparecer.São eles:

opcional ?asterisco *

mais +chaves {}

Opcional

É útil para procurar palavras no singular e plural e pode ser tornar opcionais caracteres e metacaracteres.Exemplos:Expressão Casa comOndas? Onda OndasSenadora? Senador Senadora[BFM]?ala ala Bala Fala Mala

Asterisco

Pode aparecer em qualquer quantidade. O curinga .* é o tudo e o nada, qualquer coisa.Exemplos: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.Exemplos:6+0 60, 660, 6660, ..., 666666660, ...bi+p bip, biip, biiip, biiiip...b[ip]+ bi, bip, biipp, bpipipi, biiiiip, bppp, ...

Chaves

Page 4: Shell Script

As chaves são a solução para uma quantificação mais controlada, onde se pode especificar exatamente 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 6{1,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

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 asterisco e as chaves não têm influência sobre âncoras:

São eles: cincunflexo - ^ cifrão - $ borda - /b

Explicando cada metacaractere

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]

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:[0-9]$ - casa linhas que terminam com um número^$ - casa com linhas vazias^.{20,60}$ - casa com linhas que têm entre 20 e 60 caracteres

3. Borda - a limítrofe \b

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 [A-Za-z0-9_] apenas, ou seja, letras, números e o sublinhado.

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!

Page 5: Shell Script

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

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

Explicando-os melhor...

1. Escape - a criptonita \

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

Usando Listas: Lua[*] casa com Lua* "Escapando" o Caractere: Lua\* casa com Lua*

Isto é, a contrabarra (\) "escapa" qualquer metacaractere, tirando todos os seus poderes. O escape é tão poderoso que pode escapar a si próprio! O \ casa uma barra invertida \ literal. Então, agora que sabemos muito sobre ERs, que tal uma

expressão para casar um número de RG? Lembre que ele tem o formato n.nnn.nnn-n, é fácil!

[0-9]\.[0-9]{3}\.[0-9]{3}-[0-9]

O \* = [*] = asterisco literal

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

2. Ou - o alternativo |

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://

3. Grupo - o pop (...)

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

Page 6: Shell Script

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

(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. Retrovisor - o saudosista \1 ... \9

(quero)-\1

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

Pois é, lembra que o escape (\) servia para tirar os poderes do metacaractere seguinte. Entã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":

([A-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

Page 7: Shell Script

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

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

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

o (lenta)(mente) é \2 \1 lentamente é mente lentao ((band)eira)nte \1 \2a bandeirante bandeira banda

o in(d)ol(or) é sem \1\2 indolor é sem doro ((((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.

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

Parte I

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

- Quem é o Bash?

- O Bash é o filho mais novo da família Shell.

- Pô cara! Estás a fim de me deixar maluco? Eu tinha uma dúvida e você me deixa com duas!

- Não, maluco você já é há muito tempo. Desde que se decidiu a usar aquele sistema 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?".

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:

Page 8: Shell Script

Neste gráfico dá para ver que a camada de hardware é a mais profunda e é formada pelos componentes 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 controle. Os programas e comandos que envolvem o kernel, dele se utilizam para realizar as tarefas aplicativas para que foram desenvolvidos. Fechando tudo isso vem o Shell que leva este nome porque em inglês, Shell significa concha, carapaça, isto é, fica entre o usuário e o sistema operacional, de forma que tudo que interage com o sistema operacional, tem que passar pelo seu crivo.

O ambiente Shell

Bom já que para chegar ao núcleo do Linux, no seu kernel que é o que interessa a todo aplicativo, é necessária a filtragem do

Shell, vamos entender como ele funciona de forma a tirar o má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 permitir 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, também provê informações para o login daqueles que passaram por esta primeira barreira. O último

campo de seus registros informa ao sistema qual Shell a pessoa 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).

Uma rapidinha nos principais sabores de Shell

Bourne Shell (sh)

Desenvolvido por Stephen Bourne da Bell Labs (da AT&T onde também foi desenvolvido o Unix), este foi durante muitos anos o Shell default do sistema operacional Unix. É também chamado 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.

Page 9: Shell Script

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 diversidade de comandos, que incorpora inclusive diversos instruções características do C Shell.

C 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 irei falar contigo somente sobre os três primeiros, tratando-os genericamente por Shell e assinalando as especificidades de cada um que porventura hajam.

Quando eu disse que o último campo do /etc/passwd informa ao sistema qual é o Shell que o usuário vai receber ao se "logar", é para ser interpretado ao pé-da-letra, isto é, se neste campo do seu registro estiver prog, a pessoa ao acessar o sistema receberá a tela de execução do programa prog e ao terminar a sua execução ganhará imediatamente um logout. Imagine o quanto se pode incrementar a segurança com este simples artifício.

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.

- Êpa! Esse negócio de interpretar comando não tem nada a haver com interpretador não, né?

- Tem sim, na verdade o Shell é um interpretador (ou será intérprete) que traz consigo 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 vou te mostrar.

Vou te explicar as principais tarefas que o Shell cumpre, na sua ordem de execução. Preste atenção nesta ordem porque ela é fundamental para o entendimento do resto do nosso bate papo.

Exame da Linha de 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 espaç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 entre eles, identifica esta seqüência como uma atribuição.

Exemplos

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; margin-top: 1em; margin-bottom: 1em; margin-

right: 0pt; text-align: justify; font-weight: bold; color: rgb(0, 0, 0);">$ ls linuxlinux

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

Page 10: Shell Script

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; margin-top: 1em; margin-bottom: 1em; margin-

right: 0pt; text-align: justify; color: rgb(0, 0, 0);">$ valor=1000

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

Jamais Faça: http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;">$ valor = 1000bash: valor: not found

Neste caso, o Bash achou a palavra valor isolada por brancos e julgou que você estivesse mandando executar um programa chamado valor, para o qual estaria passando dois 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), conforme será explicado a seguir.

Substituição de Variáveis

Neste ponto, o Shell verifica se as eventuais variáveis (parâmetros começados por $), encontradas 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 corrente começado pela letra n seja um diretório chamado "nomegrandeprachuchu", se você fizer:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; margin-top: 1em; margin-bottom: 1em; margin-

right: 0pt; text-align: justify;">$ cd n*

Passa Linha de Comando para o kernel

Completadas as tarefas anteriores, o Shell monta a linha de comandos, já com todas as substituições feitas, chama o kernel para executá-la em um novo Shell (Shell filho), ganhando um nú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.

Decifrando a Pedra da Roseta

Para tirar aquela sensação que você tem quando vê um script Shell, que mais parece uma sopa de letrinhas ou um hieróglifo vou lhe mostrar os principais caracteres especiais para que você saia por ai como o Jean-François Champollion decifrando a Pedra da Roseta (dê uma googlada para descobrir quem é este cara, acho que vale a pena).

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 cadeia e não interpreta seu conteúdo.

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0); font-weight:

bold;"> $ ls linux*linuxmagazine

$ ls 'linux*'bash: linux* no such file or directory

Page 11: Shell Script

No primeiro caso o Shell "expandiu" o asterisco e descobriu o arquivo linuxmagazine para listar. 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 (\)

IIdê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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(255, 255, 0); text-align: justify;"> $

rm *

Você estaria fazendo a maior encrenca, pois o rm removeria todos os arquivos do diretório corrente. A melhor forma de fazer o pretendido é:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;"> $ rm \*

Desta forma, o Shell não interpretaria o asterisco, e em conseqüência não faria a sua expansão.

Faça a seguinte experiência científica:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(255, 255, 0); text-align: justify;"> $

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 estressar, eu não te dei exemplos do uso das aspas por que você ainda não conhece o cifrão ( $) nem a crase (`). Daqui para frente veremos com muita constância o uso destes caracteres especiais, o mais importante é entender o significado de cada um.

Caracteres de redirecionamento

A maioria dos comandos tem uma entrada, uma 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ cat

O cat é uma instrução que lista o conteúdo do arquivo especificado para a Saída Padrão (stdout). Caso a entrada não seja definida, ele espera os dados da stdin. 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!

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 programa gago em um editor de textos (que pretensão heim!). http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -

moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ cat > Arq

Page 12: Shell Script

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 eu fizer novamente:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ 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 eu deveria ter feito: criará um

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ cat >> Arq

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

Com isso dá para notar que o >> (maior maior) serve para inserir texto no final do arquivo.

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.

Preste 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 tomado 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ rm /tmp/seraqueexiste$

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

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

Dica # 1

O $ $ (dois cifrões juntos) contêm o PID, isto é, o número do seu processo. Como 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, vou dar mais um exemplo. Faça:

Page 13: Shell Script

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0); font-weight:

bold;">$ ls naoexistebash: naoexiste no such file or directory

$ ls naoexiste 2> arquivodeerros$

$ cat arquivodeerrosbash: 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 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 ai.

Dica # 2

- Quem é esse tal de /dev/null?

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

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ ls naoexiste 2>>

arquivodeerros

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

Redirecionamento da Entrada Padrão

Para fazermos o redirecionamento da Entrada Padrão usamos o < (menor que).

- E prá que serve isso? - você vai me perguntar.

- Deixa eu te dar um exemplo que você vai entender rapidinho.

Suponha que você queira mandar um mail para o seu chefe. Para o chefe nós caprichamos, né? então ao invés de sair redigindo o mail direto no prompt da tela de forma a tornar impossível 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ mail chefe <

arquivocommailparaochefe

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

Um outro tipo de redirecionamento muito louco que o Shell te permite é o chamado here 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(255, 255, 0); text-align: justify;">$

ftp –ivn hostremoto << fimftpuser $Usuário $Senha

binaryget arquivoremoto

fimftp

Neste pedacinho de programa temos um monte de detalhes interessantes:

Page 14: Shell Script

1. As opções que usei para o ftp (-ivn) servem para ele ir listando tudo que está acontecendo (—v de verbose), para não perguntar se você tem certeza de que deseja transmitir cada arquivo ( —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 eu usei o << fimftp, estava dizendo o seguinte para o intérprete: "Olhe aqui Shell, não se meta em nada a partir daqui até encontrar o label fimftp. Você não entenderia nada, já que são instruções específicas do comando ftp e você não entende nada de =ftp=".

Se fosse só isso seria simples, mas pelo próprio exemplo dá para ver que existem duas variáveis ($Usuário e $Senha), que o Shell vai resolver antes do redirecionamento. Mas a grande 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 acabei de dizer. Logo a seguir explico como esse negócio funciona. Agora ainda não dá, está faltando ferramenta.

1. O comando user é do repertório de instruções do ftp e serve para passar o usuário e 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 arquivoremoto 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.

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 costuma dar uma boa surra no programador, até que seja detectado. Lembre-se: um label que se preze tem que ter uma linha inteira só para ele.

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

Redirecionamento de Comandos

Os redirecionamentos que falamos até aqui sempre se referiam a arquivos, isto é mandavam para arquivo, recebiam de arquivo, simulavam arquivo local, ... O que veremos a partir de agora redireciona a saída de um comando para a entrada de outro. É utilíssimo e quebra os maiores galhos. 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://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0); font-weight:

bold;"> $ ls | wc -l21

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;"> $ cat /etc/passwd |sort |

lp

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

Caracteres de Ambiente

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

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; font-weight: bold; color: rgb(0,

0, 0);">$ who | wc -l8

O comando who passa a lista de usuários conectados para o comando wc –l que conta quantas linhas recebeu e lista a resposta na tela. Pois bem, mas ao invés de ter um oito solto na tela, o que eu quero é que ele esteja no meio de uma frase.

Page 15: Shell Script

Ora para mandar frases para a tela eu uso o comando echo, então vamos ver como é que fica:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0); font-weight:

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

Hi! Olha só, não funcionou! É mesmo, não funcionou e não foi por causa das aspas que 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; font-weight: bold; color: rgb(0,

0, 0);">$ 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; font-weight: bold; color: rgb(0,

0, 0);">$ echo Existem `who | wc -l` usuários conectadosExistem 8 usuários conectados

Como eu disse 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, o monte de espaços será trocado por um único após a retirada das aspas.

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; font-weight: bold; color: rgb(0,

0, 0);">$pwd ; cd /etc; pwd; cd -; pwd/home/meudir

/etc//home/meudir

Neste exemplo, listei o nome do diretório corrente com o comando pwd, mudei para o diretório /etc, novamente listei o nome do diretório e finalmente voltei para o diretório onde estava anteriormente (cd -), listando seu nome. Repare que coloquei o ponto-e-vírgula (;) de todas as formas possíveis para mostrar que não importa se existe 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ (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 a seguir:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ mail suporte << FIM

>Ola suporte, hoje as ‘date"+%hh:mm"‘>ocorreu novamente aquele problema

Page 16: Shell Script

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

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, poré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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0); font-weight:

bold;">$ echo Existem $(who | grep wc -l) usuários conectadosExistem 8 usuários conectados

Veja só este caso:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; font-weight: bold; color: rgb(0,

0, 0);">$ 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 interpretadas 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ Arqs='ls'

ou:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ 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 seguidos de um único caractere (?). Eu deveria fazer:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ Arqs=$(ls -l arq?)

ou:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ Arqs=`ls -l arq?`

Mas veja:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ echo

$Arqs

Page 17: Shell Script

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ 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 desejamos fazer um comentário.

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ exit # pede a conta ao

garcon

Parte II

Diálogo

- Garçom! Traz um "chops" e dois "pastel". O meu amigo hoje não vai beber por que ele finalmente 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 estruturados, que o Shell permite. Você verá porque até na TV já teve programa chamado "O Shell é o Limite".- Para começar vamos falar dos comandos da família grep.- grep? Não conheço nenhum termo em inglês com este nome...- É claro, grep é um acrônimo Global Regular Expression 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.

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: http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ grep rafael /etc/passwd

Pesquisando em vários arquivos:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ grep grep *.sh

Pesquisando na saída de comando:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ grep '^rafael'

/etc/passwd

Page 18: Shell Script

E para que serve este circunflexo e os apóstrofos, você vai me perguntar. O circunflexo ( ^), se você tivesse lido os artigos anteriores sobre expressões regulares que te falei, saberia 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.

Olha que legal! 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".

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.

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 e, já vou adiantando, não gosto nem um pouquinho desta solução.

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

Exemplos

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ egrep (Linux | linux)

arquivo.txt

ou

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ grep [Ll]inux

arquivo.txt

No primeiro caso, a expressão regular complexa "(Linux | linux)" usa os parênteses para agrupar 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 segunda forma, já que o egrep tornaria a pesquisa mais lenta.

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ ls -l |

Page 19: Shell Script

grep '^d'drwxr-xr-x 3 root root 4096 Dec 18 2000 doc

drwxr-xr-x 11 root root 4096 Jul 13 18:58 freecivdrwxr-xr-x 3 root root 4096 Oct 17 2000 gimpdrwxr-xr-x 3 root root 4096 Aug 8 2000 gnome

drwxr-xr-x 2 root root 4096 Aug 8 2000 idldrwxrwxr-x 14 root root 4096 Jul 13 18:58 locale

drwxrwxr-x 12 root root 4096 Jan 14 2000 lyxdrwxrwxr-x 3 root root 4096 Jan 17 2000 pixmaps

drwxr-xr-x 3 root root 4096 Jul 2 20:30 scribusdrwxrwxr-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 (^).

Vamos ver mais um. Sabemos que as quatro primeiras posições possíveis de um ls -lde um arquivo comum (arquivo comum! Não é diretório, nem link, nem...) devem ser:

Posição 1ª 2ª 3ª 4ª

Valores Possíveis

- r w x

- - s (suid)

-

Assim sendo, para descobrir todos os arquivos executáveis em um determinado diretório eu deveria fazer:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ ls -la | grep '^-..[xs]'

e agilizaríamos a pesquisa.

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 "queimadores" 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 softwareLinux é multiusuário, e como tal deve ser explorado), desta forma ganhando muitos pontos com seu adorado chefe. para todos que trabalham com você (o Linux é multiusuário, e como tal deve ser explorado).

- Péra ai! De onde eu vou receber os dados dos CDs? - Inicialmente, vou lhe mostrar como o seu programa pode receber parâmetros de quem o estiver executando e em breve,

ensinarei a ler os dados pela tela ou de um arquivo.

Passando parâmetros

O layout do arquivo musicas será o seguinte:

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

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

Page 20: Shell Script

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ 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 recebem 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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0); font-weight:

bold;">$ cat teste#!/bin/bash

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

Vamos executá-lo:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0); font-weight:

bold;">$ teste passando parametros para testarbash: teste: cannot execute

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ chmod

755 teste$ teste passando parametros para testar

1o. parm -> passando2o. parm -> parametros

3o. parm -> para

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ teste

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

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

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

Macetes paramétricos

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

Variável Significado

$0 Contém o nome do programa

$# Contém a quantidade de parâmetros passados

$* Contém o conjunto de todos os parâmetros (muito parecido com $@)

Exemplos

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; font-weight: bold; color: rgb(0,

Page 21: Shell Script

0, 0);">$ 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ teste

passando parametros para testarO programa teste recebeu 4 parametros

1o. parm -> passando2o. parm -> parametros

3o. parm -> paraTodos de uma só "tacada": passando parametros para testar

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

Exemplo:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat

teste#!/bin/bash

# Programa para testar passagem de parametros (3a. Versao)echo O programa $0 recebeu $# parametros

echo "11o. parm -> $11"shift

echo "2o. parm -> $1"shift 2

echo "4o. Parm -> $1"

Vamos executá-lo:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ teste

passando parametros para testarO programa teste recebeu 4 parametros que são:

11o. parm -> passando12o. 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.

Bem, agora que você já sabe mais sobre passagem de parâmetros do que eu, vamos voltar à nossa "cdteca" para fazer o script de inclusão de CDs no meu banco chamado musicas. O programa é muito simples (como tudo em Shell) e vou listá-lo para você ver:

Exemplos

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ 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):

Page 22: Shell Script

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ 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.

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat

musicasalbum 3^Artista5~Musica5:Artista6~Musica6album 1^Artista1~Musica1:Artista2~Musica2album 2^Artista3~Musica3:Artista4~Musica4

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat

musinc#!/bin/bash

# Cadastra CDs (versao 2)#

echo $1 >> musicassort musicas -o musicas

Vamos cadastrar mais um:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ musinc "album

4^Artista7~Musica7:Artista8~Musica8"

Agora vamos ver o que aconteceu com o arquivo musicas:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat

musicasalbum 1^Artista1~Musica1:Artista2~Musica2album 2^Artista3~Musica3:Artista4~Musica4album 3^Artista5~Musica5:Artista6~Musica5album 4^Artista7~Musica7:Artista8~Musica8

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

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

Exemplos

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat

muslist#!/bin/bash

# Consulta CDs (versao 1)#

grep $1 musicas

Vamos executá-lo, procurando pelo album 2. Como já vimos antes, para passar a cadeia album 2 é necessário protegê-la da interpretação do Shell, para que ele não a interprete como dois parâmetros. Vamos fazer assim:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0); font-weight:

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

musicas: album 1^Artista1~Musica1:Artista2~Musica2

Page 23: Shell Script

musicas: album 2^Artista3~Musica3:Artista4~Musica4musicas: album 3^Artista5~Musica5:Artista6~Musica6musicas: album 4^Artista7~Musica7:Artista8~Musica8

Que lambança! 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.

Sempre que a cadeia de caracteres a ser passada para o comando grep possuir 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ 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 caracteres 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é por um pedaço de qualquer um destes. Quando conhecermos os comandos condicionais, montaremos uma nova versão de muslist que permitirá especificar por qual campo pesquisar.

Aí você vai me dizer:

- Poxa, mas é um saco ter que colocar o argumento de pesquisa entre aspas na hora de 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat

muslist#!/bin/bash

# Consulta CDs (versao 3)#

grep -i "$*" musicas

Page 24: Shell Script

$ muslist album 2album 2^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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ grep -v

"album 2" musicasalbum 1^Artista1~Musica1:Artista2~Musica2album 3^Artista5~Musica5:Artista6~Musica6album 4^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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat

musexc#!/bin/bash

# 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 registros 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ê ir para casa praticar em cima dos exemplos dados porque, eu prometo, chegaremos a desenvolver um sistema bacana para controle dos seus CDs. - Quando nos encontrarmos da próxima vez, vou te ensinar como funcionam os comandos condicionais e aprimoraremos mais um pouco estes scripts. - Por hoje chega! Já falei demais e preciso molhar a palavra porque estou de goela seca! - Garçom! Mais um sem colarinho!

Parte III

Trabalhando com cadeias

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

O comando cut (que não é a central de trabalhadores)

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.

Page 25: Shell Script

O comando cut com a opção -c

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

cut -c PosIni-PosFim [arquivo]

Onde:

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat numeros1234567890098765432112345543219876556789$ cut -c1-5 numeros12345098761234598765$ cut -c-6 numeros123456098765123455987655$ cut -c4- numeros4567890765432145543216556789$ cut -c1,3,5,7,9 numeros13579086421354297568$ cut -c -3,5,8- numeros1235890098632112353219875789

Como dá para ver, no duro mesmo existem quatro sintaxes distintas: na primeira (-c 1-5), 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, foi só para mostrar que podemos misturar tudo.

O comando cut com 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.

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat musicasalbum 1^Artista1~Musica1:Artista2~Musica2album 2^Artista3~Musica3:Artista4~Musica4album 3^Artista5~Musica5:Artista6~Musica5album 4^Artista7~Musica7:Artista8~Musica8

Então, recapitulando, o seu "leiaute" é o seguinte:

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

Page 26: Shell Script

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cut -f2 -d: musicasArtista2~Musica2Artista4~Musica4Artista6~Musica5Artista8~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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cut -f2 -d: musicas | cut -f1 -d~Artista2Artista4Artista6Artista8

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ head -1 musicasalbum 1^Artista1~Musica1:Artista2~Musica2

Então observe o que foi feito:

Delimitador do primeiro cut (:)

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">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 (~)

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">Artista2~Musica2

Agora, primeiro campo do delimitador (-d) til (~), que é o que nos interessa, é Artista2 e o segundo é Musica2.

Se o raciocínio que fizemos para a primeira linha for aplicado no restante do arquivo, chegaremos à resposta anteriormente dada.

O comando tr

Outro comando muito interessante é o tr que serve para substituir, comprimir ou remover caracteres. 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.

Page 27: Shell Script

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

Opção Significado

-s Comprime n ocorrências de cadeia1 em apenas uma

-d Remove os caracteres de cadeia1

Trocando caracteres com tr

Primeiro vou te dar um exemplo bem bobo:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ echo

bobo | tr o ababa

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

Suponha que em um determinado ponto do meu script eu peça ao operador para teclar sn (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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ 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 minúsculas eu faço:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ 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 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: Seqüênci

a Significado Octal

\t Tabulação \011

\n Nova linha \012

\v Tabulação Vertical \013

\f Nova Página \014

\r Início da linha <^M> \015

\ Uma barra invertida \0134

Removendo caracteres com tr

Então deixa eu te contar um "causo": um aluno que estava danado comigo, resolveu complicar 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 e idiota de uma "tripa" assim:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ confuso

leia Programação Shell Linux do Julio Cezar Neves/home/jneves/LM

confuso livro musexc musicas musinc muslist numeros

Page 28: Shell Script

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ tr ";" "\n" < confuso

echo leia Programação Shell Linux do Julio Cezar Nevespwdcd ~ls -l

rm -f lixo 2>/dev/null

O cara 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;">$ tr ";" "\012" < confuso

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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ date | cut -f 4 -d ' '

14:59:54

Mas duas semanas antes ocorreria o seguinte:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ date | cut -f 4 -d ' '

5

Mas observe porque:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ date # Há duas

semanasSun Sep 5 10:12:33 2004

Como você pode notar, existem 2 caracteres em branco antes do 5 (dia), o que estraga 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 resultantes do comando date da mesma forma, e isso se faz assim:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ date | tr -s " "

Sun Sep 5 10:12:33 2004

E agora eu poderia cortar:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ date | tr -s " " | cut -f 4

-d " "10:12:33

Olha só como o Shell já está quebrando o galho. Veja este arquivo que foi baixado de uma máquina com aquele sistema operacional que pega vírus:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ cat -ve ArqDoDOS.txt

Este arquivo^M$foi gerado pelo^M$

Page 29: Shell Script

DOS/Rwin e foi^M$baixado por um^M$

ftp mal feito.^M$E agora eu quero te dar duas 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 então limpar este arquivo.

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;">$ tr -d '\r' < ArqDoDOS.txt >

/tmp/ArqDoLinux.txt$ mv -f /tmp/ArqDoLinux.txt ArqDoDOS.txt

Agora vamos ver o que aconteceu:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ cat -ve ArqDoDOS.txt

Este arquivo$foi gerado pelo$DOS/Rwin e foi$baixado por um$

ftp mal feito.$

Bem 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;">$ tr -d '\015' < ArqDoDOS.txt >

/tmp/ArqDoLinux.txt

Isto aconteceu porque o ftp foi feito do modo binário (ou image), isto é, sem a interpretação do texto. Se antes da transmissão do arquivo tivesse sido estipulada a opção ascii do ftp, isto não teria ocorrido.

- Olha, depois desta dica tô começando a gostar deste tal de Shell, mas ainda tem muita coisa que não consigo fazer.

- Pois é, ainda não te falei quase nada sobre programação em Shell, ainda tem muita coisa para aprender, mas com o que aprendeu, já dá para resolver muitos problemas, desde que você adquira o “modo Shell de pensar”. Você seria capaz de

fazer um script para me dizer quais são as pessoas que estão “logadas” há mais de um dia no seu servidor?

- Claro que não! Para isso seria necessário eu conhecer os comandos condicionais que você ainda não me explicou como funcionam.

- Deixa eu tentar mudar um pouco a sua lógica e trazê-la para o “modo Shell de pensar”, mas antes é melhor tomarmos um chope... Ô Chico, traz mais dois...

- Agora que já molhei a palavra, vamos resolver o problema que te propus. Repare como funciona o comando who:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ who

jneves pts/1 Sep 18 13:40rtorres pts/0 Sep 20 07:01rlegaria pts/1 Sep 20 08:19lcarlos pts/3 Sep 20 10:01

E veja também o date:

Page 30: Shell Script

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-

initial; color: rgb(0, 0, 0);">$ dateMon Sep 20 10:47:19 BRT 2004

Repare que o mês e o dia estão no mesmo formato em ambos os comandos.

Algumas vezes um comando tem a saída em português e o outro em inglês. Quando isso ocorrer, você pode usar o seguinte artifício:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ date

Mon Sep 20 10:47:19 BRT 2004$ LANG=pt_BR date

Seg Set 20 10:47:19 BRT 2004Desta forma passando a saída do comando date para português.

Ora, se em algum registro do who eu não encontrar a data de hoje, é sinal que o cara está "logado" há mais de um dia, já que ele não pode ter se "logado" amanhã... Então vamos guardar o pedaço que importa da data de hoje para procurá-la

na saída do who:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;">$ Data=$(date | cut -f 2-3 -d' ')

Eu usei a construção $(...), para priorizar a execução dos comandos antes de atribuir a sua saída à variável $Data. Vamos ver se funcionou:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ echo $Data

Sep 20

Beleza! Agora, o que temos que fazer é procurar no comando who os registros que não possuem esta data.

- Ah! Eu acho que estou entendendo! Você falou em procurar e me ocorreu o comando grep, estou certo?

- Certíssimo! Só que eu tenho que usar o grep com aquela opção que ele só lista os registros nos quais ele não encontrou a cadeia. Você se lembra que opção é essa?

- Claro, é a opção -v...

- Isso! Tá ficando bão! Então vamos ver:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ who | grep -v "$Data"

jneves pts/1 Sep 18 13:40

- E se eu quisesse mais um pouco de perfumaria eu faria assim:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; color: rgb(0, 0, 0);">$ who | grep -v "$Data" |

cut -f1 -d ' 'jneves

- Viu? Não foi necessário usar nenhum comando condicional, até porque o nosso mais usado comando condicional, o famoso if, não testa condição, mas sim instruções, como veremos agora.

Comandos condicionais

Veja as linhas de comando a seguir:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ ls musicas

musicas$ echo $?

0$ ls ArqInexistente

ls: ArqInexistente: No such file or directory$ echo $?

1$ who | grep jneves

Page 31: Shell Script

jneves pts/1 Sep 18 13:40 (10.2.4.144)$ echo $?

0$ who | grep juliana

$ echo $?1

- O que é esse $? faz aí? Começando por cifrão ($) parece ser uma variável, certo?

- Sim é uma variável que contém o código de retorno da última instrução executada. Posso te garantir que se esta instrução foi bem sucedida, $? terá o valor zero, caso contrário seu valor será diferente de zero.

O Comando if

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

if cmd

then

cmd1

cmd2

cmdn

else

cmd3

cmd4

cmdm

fi

ou seja: caso comando cmdtenha 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat

incusu#!/bin/bash# Versão 1

if grep ^$1 /etc/passwdthen

echo Usuario '$1' já existeelse

if useradd $1then

echo Usuário '$1' incluído em /etc/passwdelse

echo "Problemas no cadastramento. Você é root?"fifi

Repare que o if está testando direto o comando grepe 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 comandos do bloco do thenserã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 iftesta se o comando useraddfoi 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ incusu

jnevesjneves:x:54002:1001:Julio Neves:/home/jneves:/bin/bash

Usuario 'jneves' ja existe

Page 32: Shell Script

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat

incusu#!/bin/bash# Versão 2

if grep ^$1 /etc/passwd > /dev/nullthen

echo Usuario '$1' já existeelse

if useradd $1then

echo Usuário '$1' incluído em /etc/passwdelse

echo "Problemas no cadastramento. Você é root?"fifi

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ 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 também a saída de erro ( strerr, lembra?) do useradd para /dev/null, ficando na versão final assim:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat

incusu#!/bin/bash# Versão 3

if grep ^$1 /etc/passwd > /dev/nullthen

echo Usuario '$1' já existeelse

if useradd $1 2> /dev/nullthen

echo Usuário '$1' incluído em /etc/passwdelse

echo "Problemas no cadastramento. Você é root?"fifi

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ incusu

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

E novamente:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ incusu

botelhoUsuá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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat

musinc#!/bin/bash

# Cadastra CDs (versao 3)#

if grep "^$1$" musicas > /dev/null

Page 33: Shell Script

thenecho Este álbum já está cadastrado

elseecho $1 >> musicas

sort musicas -o musicasfi

Como você viu, é uma pequena evolução da versão anterior, assim, antes de incluir um registro (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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ musinc

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

E agora um não cadastrado:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ musinc

"album 5^Artista9~Musica9:Artista10~Musica10"$ cat musicas

album 1^Artista1~Musica1:Artista2~Musica2album 2^Artista3~Musica3:Artista4~Musica4album 3^Artista5~Musica5:Artista6~Musica5album 4^Artista7~Musica7:Artista8~Musica8

album 5^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 ifpara testar condições, ou seja, o uso normal do comando.

- Cara, para isso existe o comando test, ele é que testa condições. O comando iftesta o comando test. Mas isso está meio confuso e como já falei muito, estou precisando de uns chopes para molhar a palavra. Vamos parando por aqui e na próxima vez te explico direitinho o uso do teste 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...

Parte IV

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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat

logado#!/bin/bash

# Pesquisa se uma pessoa está logada ou nãoif who | grep $1

thenecho $1 está logado

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

fi

Page 34: Shell Script

- Calma rapaz! Já vi que você chegou cheio de tesão, primeiro vamos pedir os nossos chopes de praxe e depois vamos ao Shell. Chico traz dois chopes, um sem colarinho!

- Agora que já molhamos os nossos bicos, vamos dar uma olhadinha na execução do seu bacalho:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ logado

jnevesjneves pts/0 Oct 18 12:02 (10.2.4.144)

jneves está logado

Realmente funcionou. Passei o meu login como parâmetro e ele disse que eu estava logado, porém ele mandou uma linha que eu não pedi. Esta linha é a saída do comando who, e 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat

logado#!/bin/bash

# Pesquisa se uma pessoa está logada ou não (versão 2)if who | grep $1 > /dev/null

thenecho $1 está logado

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

fi

Agora vamos aos testes:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ logado

jnevesjneves está logado

$ logado chicochico não se encontra no pedaç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

O comando Test

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

Opções do Comando test para arquivos

Opção Verdadeiro se:

-e arq arq existe

-s arq arq existe e tem tamanho maior que zero

-f arq arq existe e é um arquivo regular

-d arq arq existe e é um diretório;

-r arq arq existe e com direito de leitura

-w arq arq existe e com direito de escrita

-x arq arq existe e com direito de execução

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

Opções do comando test para cadeias de

Page 35: Shell Script

caracteres

Opção Verdadeiro se:

-z cadeia Tamanho de cadeia é zero

-n cadeia Tamanho de cadeia é maior que zero

cadeia A cadeia cadeia tem tamanho maior que zero

c1 = c2 Cadeia c1 e c2 são idênticas

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:

Opções do comando test para números

Opção Verdadeiro se: Significado

n1 -eq n2 n1 e n2 são iguais equal

n1 -ne n2 n1 e n2 não são iguais not equal

n1 -gt n2 n1 é maior que n2 greater than

n1 -ge n2 n1 é maior ou igual a n2 greater or equal

n1 -lt n2 n1 é menor que n2 less than

n1 -le n2 n1 é menor ou igual a n2 less or equal

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

Operadores

Operador Finalidade

Parênteses Agrupar

Exclamação ! Negar

-a E lógico

-o OU lógico

Ufa! Como você viu tem coisa prá chuchu, e como eu te disse no início, o nosso if é muito mais poderoso 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 queria 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

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 fragmento de script, o programa estaria com certeza dentro do diretório lmb.

Page 36: Shell Script

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.

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 animal 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):

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$

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 familia felina E (-a) do gênero leão, seria dado um alerta, caso contrário a mensagem seria de incentivo.

Page 37: Shell Script

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 Shellidentifica 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$

Familia=felino$ Genero=gato

$ 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

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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ 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, priorizando as suas execuções e resultando:

VERDADEIRO -a FALSO

Para que seja VERDADEIRO o resultado duas expressões ligadas pelo conector -a é necessá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 tentados a usar um if com o conector -a, mas é sempre bom lembrarmos que o bash nos dá muito recursos, e isso poderia ser feito de forma muito mais simples com um único comando grep, da seguinte maneira:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ egrep (Artista1|

Artista2) musicas

Page 38: Shell Script

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ 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

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

if test ! -d lmb

then

mkdir lmb

fi

cd lmb

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

if [ ! -d lmb ]

then

mkdir lmb

fi

cd lmb

Ou seja, o comando test pode ser 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.

Querida, encolheram o comando condicional

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

Valores Booleanos E OU

VERDADEIRO-VERDADEIRO VERDADEIRO VERDADEIRO

VERDADEIRO-FALSO FALSO VERDADEIRO

FALSO-VERDADEIRO FALSO VERDADEIRO

FALSO-FALSO FALSO FALSO

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 resultado dependerá da segunda condição.

Ora, os caras que desenvolveram o interpretador não são bobos e 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.

Page 39: Shell Script

Aproveitando disso, criaram uma forma abreviada de fazer testes. Batizaram o conector E de && e o OU de || e para ver como isso funciona, vamos usá-los como teste no nosso velho exemplo de trocarmos de diretório, que em sua última versão estava assim:

if [ ! -d lmb ]

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á legal, porque caso o diretório não exista, o cd dará a mensagem de erro correspondente. 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, ...

E tome de test

Ufa! Você pensa que acabou? Ledo engano! Ainda tem uma forma de test a mais. Essa é legal porque ela te permite usar 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 estas). A diferença de sintaxe deste para o test que acabamos de ver é que esse trabalha com dois pares de colchete da seguinte forma:

Page 40: Shell Script

[[ expressão ]]

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

Expressões Condicionais Para Padrões

Expressão Retorna

cadeia == padrãocadeia1 = padrao Verdadeiro se cadeia1 casa com padrão

cadeia1 ! padrao Verdadeiro se cadeia1 não casa com padrao.

cadeia1 < cadeia1 Verdadeiro se cadeia1 vem antes de cadeia1 alfabeticamente.

cadeia1 > cadeia1 Verdadeiro se cadeia1 vem depois de cadeia1 alfabeticamente

expr1 && expr2 "E" lógico, verdadeiro se ambos expr1 e expr2 são verdadeiros

expr1 ¦¦ expr2 "OU" lógico, verdadeiro se expr1 ou expr2 for verdadeiro http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -

moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ echo $H13

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

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

$

Neste exemplo, testamos se o conteúdo da variável $H estava compreendido entre zero e nove ([0-9]) ou (||) se estava entre dez e doze (1[0-2]), dando uma mensagem de erro caso não fosse.

Exemplos:

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify;">$ var=a

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

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

$

Como você pode imaginar, este uso de padrões para comparação, aumenta muito o poderio do comando test. No início deste papo, antes do último chope, afirmamos que o comando if do interpretador Shell é mais poderoso que o seu similar em outras linguagens. Agora que conhecemos todo o seu espectro de funções, diga-me: você concorda ou não com esta assertiva?

Acaso casa com case

Vejamos um exemplo didático: dependendo do valor da variável $opc o script deverá executar uma 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

Page 41: Shell Script

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:

Caracteres Para Formação de Padrões

Caractere Significado

* Qualquer caractere ocorrendo zero ou mais vezes

? Qualquer caractere ocorrendo uma vez

[...] Lista de caracteres

¦ OU lógico

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. Outra coisa a ser notada é que o duplo ponto-e-vírgula não é necessário antes do esac.

Page 42: Shell Script

Exemplos:

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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(255, 255, 0);">$

dateTue Nov 9 19:37:30 BRST 2004

$ date +%H19

O comando date informa a data completa do sistema, mas ele tem diversas opções para 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">$ cat

boasvindas.sh#!/bin/bash

# Programa bem educado que# dá bom-dia, boa-tarde ou# boa-noite conforme a hora

Hora=$(date +%H)case $Hora in

0? | 1[01]) echo Bom Dia;;

1[2-7] ) echo Boa Tarde;;

* ) echo Boa Noite;;

esacexit

Peguei pesado, né? Que nada vamos esmiuçar a resolução caso-a-caso (ou seria case-a-case? )

0? | 1[01] - Significa zero seguido de qualquer coisa (?), ou (|) um seguido de zero ou um ([01]) ou seja, esta linha pegou 01, 02, ... 09, 10 e 11;

1[2-7] - Significa um seguido da lista de dois a sete, ou seja, esta linha pegou 12, 13, ... 17;

*- Significa tudo que não casou com nenhum dos padrões anteriores.

- Cara, até agora eu falei muito e bebi pouco. Agora eu vou te passar um exercício para você fazer em casa e me dar a resposta da próxima vez que nos encontrarmos aqui no botequim, tá legal?

- Tá, mas antes informe ao pessoal que está acompanhando este curso conosco como eles podem 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 é [email protected], mas pare de me embromar que eu não vou esquecer 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 prá entender?

- Hum, hum...

- Chico! Traz mais um sem colarinho que o cara aqui já está dando para entender!

Parte V

Comandos de Loop (ou laços)

Muitos problemas requerem mecanismos de repetiçâo nos quais sequências de intruçô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

Page 43: Shell Script

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 programês é 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.

Primeira sintaxe do comando for

for var in val1 val2 ... valn

do

cmd1

cmd2

cmdn

done

Vamos direto para os exemplos, para entender direito o funcionamento deste comando. Vamos escrever um script para listar todos os arquivos do nosso 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 forfor 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 forfor Palavra in Papo de Botequim

do

Page 44: Shell Script

echo $Palavra

done

E executando vem:

$ testefor2

Papo

de

Botequim

Como você viu, este exemplo é tão bobo e simples como o anterior, mas serve para mostrar o comportamento 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, deixa eu te mostrar 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 hexadecimal (od -h) e resultou:

Conteúdo da Variável $IFS

Hexadecimal Significado

09 <TAB>

20 <ESPAÇO>

0a <ENTER>

Onde o último 0a foi proveniente do <ENTER> dado ao final do comando. Para melhorar a explicaçã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:

No comando cat, a opção -e representa o <ENTER> como um cifrão ($) e a opção -t representa 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 separadas por espaços em branco, mas sim pelo conteúdo da variável $IFS, cujo valor padrão (default) são esses caracteres que acabamos de ver. Para comprovarmos isso, vamos mostrar um script que recebe o nome do artista como parâmetro e lista as músicas que ele executa, mas primeiramente vamos ver como está o nosso arquivo musicas:

$ cat musicas

album 1^Artista1~Musica1:Artista2~Musica2

album 2^Artista3~Musica3:Artista4~Musica4 album 3^Artista5~Musica5:Artista6~Musica6 album 4^Artista7~Musica7:Artista1~Musica3 album 5^Artista9~Musica9:Artista10~Musica10

Em cima deste "leiaute" foi desenvolvido o script a seguir

Page 45: Shell Script

:$ cat listartista#!/bin/bash

# Dado um artista, mostra as suas musicasif [ $# -ne 1 ]then

echo Voce deveria ter passado um parametro exit 1

fiIFS="

:"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 $ArtMus irá receber cada um destes blocos do arquivo (repare que o for já recebe os registros sem o álbum em virtude do cut na sua linha). Caso encontre o 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

Êpa! 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 quanto o artista têm mais de um nome. Suponha que o artista fosse

uma dupla sertaneja chamada Perereca & Peteleca (não gosto nem de dar a idéia com receio que isso se torne realidade . Nesta caso o $1 seria Perereca e o resto deste lindo nome seria ignorado na pesquisa.

Para que isso não ocorresse, eu deveia 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 2if [ $# -eq 0 ]

then

echo Voce deveria ter passado pelo menos um parametro

exit 1

fi

Page 46: Shell Script

IFS="

:"for ArtMus in $(cut -f2 -d^ musicas)

do echo "$ArtMus" | grep -i "^$@~" > /dev/null && echo $ArtMus | cut -f2 -d~

done

Que executando vem:

$ listartista Artista1

Musica1

Musica3

Segunda sintaxe do comando for

for var

do cmd1 cmd2 cmdn done

- Ué, sem o in como ele vai saber que valor assumir?

- Pois é, né? Esta construção a primeira vista parece xquisita mas é bastante simples. Neste caso, var assumirá um-a-um cada um dos parâmetros passados para o progama.

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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">

$ cat listamusica#!/bin/bash

# Recebe parte dos nomes de musicas como parametro e# lista os interpretes. Se o nome for composto, deve

# ser passado entre aspas.# ex. "Eu nao sou cachorro nao" "Churrasquinho de Mae"

#if [ $# -eq 0 ]

thenecho Uso: $0 musica1 [musica2] ... [musican]

exit 1fi

IFS=":"

for Musicado

echo $MusicaStr=$(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~donedone

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.

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

Page 47: Shell Script

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">

$ listamusica musica3 Musica4 "Eguinha Pocotó"musica3Artista3Artista1Musica4Artista4

Eguinha PocotóNão encontrada

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

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

E é aí que eu te respondo: "Eu não te disse que o nosso for é mais porreta que os outros?" Para fazer isso existem duas formas:

1 - Com a primeira sintaxe que vimos, como nos exemplos a seguir direto no prompt:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">

$ 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

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">

$ 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:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">

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

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

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

Terceira sintaxe do comando for

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

do

cmd1

cmd2

cmdn

Page 48: Shell Script

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 a coisa fica mais fácil:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">

$ for ((i=1; i<=9; i++))

> do

> echo -n "$i "

> done1 2 3 4 5 6 7 8 9

Neste caso a variável i partiu do valor inicial 1, o bloco de comando (neste caso somente 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.

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">

$ for ((; i<=9;))

> do

> let i++

> echo -n "$i "

> done1 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 comandos a seguir dados diretamente no prompt para mostrar o que acabo de falar:

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">

$ 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:

Page 49: Shell Script

http://twiki.softwarelivre.org/pub/twiki/twikidocgraphics/pencil.gif) no-repeat scroll right top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; text-align: justify; color: rgb(0, 0, 0);">

$ for arq in *

> do

> let i++

> echo "$i -> $Arq"

> done1 -> ArqDoDOS.txt1

2 -> confuso3 -> incusu

4 -> listamusica5 -> listartista

6 -> logado7 -> musexc8 -> musicas9 -> musinc

10 -> muslist11 -> testefor112 -> testefor2

- Pois é amigo, tenho certeza que você já tomou um xarope do comando for. Por hoje chega, na próxima vez que nos encontrarmos falaremos sobre outras instruções de loop, mas eu gostaria 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.