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
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
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:
Voltando ao grep.
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
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:
n[ãa]o não, nao, ...
[Tt]eclado teclado, Teclado, ....
12[:. ]45 12:45, 12 45, 12.45, ...
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:
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 com
Ondas? Onda Ondas
Senadora? Senador Senadora
[BFM]?ala ala Bala Fala Mala
Asterisco
Pode aparecer em qualquer quantidade. O curinga .* é o tudo e o nada, qualquer coisa.
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
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
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
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.
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
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!
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 \
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]
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://
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.
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:
(#|n\.|núm) 6 # 6, n. 6, núm 6
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
(mini|(su|hi)per)?mercado
(quero)-\1
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
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:
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
- Quem é o Bash?
- 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:
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).
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.
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.
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.
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
Neste exemplo o Shell identificou o ls como um programa e o linux como um parâmetro passado para o programa ls.
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 = 1000
bash: valor: not found
Neste caso, o Bash achou a palavra valor isolada por brancos e julgou que você estivesse mandando
executar um programa chamado valor, para o qual estaria passando dois 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
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:
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.
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).
É 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:
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*.
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:
Você estaria fazendo a maior encrenca, pois o rm removeria todos os arquivos do diretório corrente. A melhor forma de
fazer o pretendido é:
Desta forma, o Shell não interpretaria o asterisco, e em conseqüência não faria a sua expansão.
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.
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!
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.
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:
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
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.
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:
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:
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:
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
- 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:
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:
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 <<.
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 (|).
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.
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:
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.
Ora para mandar frases para a tela eu uso o comando echo, então vamos ver como é que fica:
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:
Para eliminar esse monte de brancos antes do 8 que o wc -l produziu, basta tirar as aspas. Assim:
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:
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:
- 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á!
- 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.
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:
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:
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:
ou:
Mas veja:
- 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:
- 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.
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
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:
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.
• 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:
ou
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.
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ª
- r w x
Valores Possíveis - - s (suid)
-
Assim sendo, para descobrir todos os arquivos executáveis em um determinado diretório eu deveria fazer:
e agilizaríamos a pesquisa.
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).
Passando parâmetros
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.
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:
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 parametros
echo "1o. parm -> $1"
echo "2o. parm -> $2"
echo "3o. parm -> $3"
Vamos executá-lo:
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:
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:
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:
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:
Vamos executá-lo:
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):
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:
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:
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:
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!
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:
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:
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.
- 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:
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" musicas
album 1^Artista1~Musica1:Artista2~Musica2
album 3^Artista5~Musica5:Artista6~Musica6
album 4^Artista7~Musica7:Artista8~Musica8
Conforme eu expliquei antes, o grep do exemplo listou todos os registros de 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
Pelo título acima não pense você que vou lhe ensinar a ser carcereiro! Estou me referindo à cadeia de caracteres!
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.
Onde:
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.
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.
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:
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:
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.
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:
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.
Opção Significado
-s Comprime n ocorrências de cadeia1 em apenas uma
-d Remove os caracteres de cadeia1
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)
Se o meu arquivo ArqEnt está todo escrito com letras maiúsculas e desejo passá-las para minúsculas eu faço:
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?).
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:
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:
Agora veja a diferença entre os dois comandos date: o que fiz hoje e outro que foi executado há duas semanas:
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:
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:
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.
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:
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 2004
Desta 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:
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:
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?
- 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
- 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:
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.
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:
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:
Depois destas alterações e de fazer um su – (me tornar root) vejamos o seu comportamento:
E novamente:
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:
- 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.
Parte IV
Diálogo
- 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:
- 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 jneves
jneves pts/0 Oct 18 12:02 (10.2.4.144)
jneves está logado
Realmente funcionou. Passei o meu login como parâmetro e ele disse que eu estava logado, porém ele mandou uma linha
que eu não pedi. Esta linha é a saída do comando who, e para evitar que isso aconteça é só mandá-la para o buraco
negro que a esta altura você já sabe que é o /dev/null. Vejamos então como ficaria:
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:
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:
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.
Vamos ver dois exemplos para entender a diferença comparação entre números e entre cadeias.
cad1=1
cad2=01
then
else
fi
cad1=1
cad2=01
then
else
fi
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):
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.
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.
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:
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:
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:
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:
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
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.
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.
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
cd dir
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:
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:
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:
[[ expressão ]]
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:
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?
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
then
exclusao
elif [ $opc -eq 3 ]
then
alteracao
then
exit
else
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.
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 ;;
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.
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:
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:
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 é julio.neves@gmail.com , 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
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
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.
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.
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 *
Isto é, o Shell viu o asterisco (*) expandindo-o com o nome de todos os arquivos do diretório e o comando echo jogou-os para a
tela separados por espaços em branco. Visto isso vamos ver como resolver o problema a que nos propuzemos:
$ cat testefor1
#!/bin/bash
# 1o. Prog didático para entender o for
for Arq in *
do
echo -n $Arq: # A opcao -n eh para nao saltar linha
done
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):
do
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
0000004
Isto é, mandei a variável (protegida da interpretação do Shell pelas aspas) para um dump hexadecimal (od
-h) e resultou:
Onde o último 0a foi proveniente do <ENTER> dado ao final do comando. Para melhorar a explicação, vamos ver isso de outra
forma:
: ^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
:$ cat listartista
#!/bin/bash
# Dado um artista, mostra as suas musicasif [ $# -ne 1 ]
then
echo Voce deveria ter passado um parametro
exit 1
fi
IFS="
:"
for ArtMus in $(cut -f2 -d^ musicas)
do
echo "$ArtMus" | grep $1 && echo $ArtMus | cut -f2 -d~
done
O script, como sempre, começa testando se os parâmetros foram passados corretamente, em seguida o IFS foi setado para
<ENTER> e dois-pontos (:) (como demonstram as aspas em linha diferentes), porque é ele que separa os blocos
Artistan~Musicam. Desta forma, a variável $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:
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
# versao 2
if [ $# -eq 0 ]
then
exit 1
fi
IFS="
:"
for ArtMus in $(cut -f2 -d^ musicas)
do
echo "$ArtMus" | grep -i "^$@~" > /dev/null && echo $ArtMus | cut -f2 -d~
done
$ listartista Artista1
Musica1
Musica3
for var
do
cmd1
cmd2
cmdn
done
- 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:
$ cat listamusica
#!/bin/bash
# Recebe parte dos nomes de musicas como parametro e
# lista os interpretes. Se o nome for composto, deve
# ser passado entre aspas.
# ex. "Eu nao sou cachorro nao" "Churrasquinho de Mae"
#
if [ $# -eq 0 ]
then
echo Uso: $0 musica1 [musica2] ... [musican]
exit 1
fi
IFS="
:"
for Musica
do
echo $Musica
Str=$(grep -i "$Musica" musicas) ||
{
echo " Não encontrada"
continue
}
for ArtMus in $(echo "$Str" | cut -f2 -d^)
do
echo " $ArtMus" | grep -i "$Musica" | cut -f1 -d~
done
done
Da mesma forma que os outros, começamos o exercício com uma crítica sobre os parâmetros recebidos, em seguida fizemos um
for em que a variável $Musica receberá cada um dos parâmetros passados, colocando em $Str todos os álbuns que contém as
músicas passadas. Em seguida, o outro for pega cada bloco Artista~Musica nos registros que estão em $Str e lista cada artista
que execute aquela música.
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:
$ for i in $(seq 9)
> do
> echo -n "$i "
> done
1 2 3 4 5 6 7 8 9
Neste a variável i assumiu os inteiros de 1 a 9 gerados pelo comando seq e a opção -necho foi usada para não saltar linha a cada
número listado (sinto-me ecologicamente correto por não gastar um monte de papel da revista quando isso pode ser evitado).
Ainda usando o for com seq: do
$ for i in $(seq 3 9)
> do
> echo -n "$i "
> done
4 5 6 7 8 9
$ for i in $(seq 0 3 9)
> do
> echo -n "$i "
> done
0 3 6 9
2 – A outra forma de fazer o desejado é com uma sintaxe muito semelhante ao for da linguagem C, como veremos mais adiante.
do
cmd1
cmd2
cmdn
done
Onde:
Como sempre vamos aos exemplos que a coisa fica mais fácil:
> do
> done
1 2 3 4 5 6 7 8 9
Neste caso a variável i partiu do valor inicial 1, o bloco de comando (neste caso somente o echo) será executado enquanto i
menor ou igual (<=) a 9 e o incremento de i1 a cada passada do loop. será de 1.
Repare que no for propriamente dito (e não no bloco de comandos) não coloquei um cifrão ($) antes do i, e a notação para
incrementar (i++) é diferente do que vimos até agora. Isto é porque o uso de parênteses duplos (assim como o comando let)
chama o interpretador aritmético do Shell, que é mais tolerante.
Como me referi ao comando let, só para mostrar como ele funciona e a versatilidade do for, vamos fazer a mesma coisa, porém
omitindo a última parte do escopo do for, passando-a para o bloco de comandos.
> do
> done
1 2 3 4 5 6 7 8 9
Repare que o incremento saiu do corpo do for e passou para o bloco de comandos, repare também que quando usei o let, não foi
necessário sequer inicializar a variável $i. Veja só os comandos a seguir dados diretamente no prompt para mostrar o que acabo
de falar:
$ echo $j
$ let j++
$ echo $j
1
Ou seja, a variável $j sequer existia e no primeiro let assumiu o valor 0 (zero) para, após o incremento, ter o valor 1.
$ for arq in *
> do
> done
1 -> ArqDoDOS.txt1
2 -> confuso
3 -> incusu
4 -> listamusica
5 -> listartista
6 -> logado
7 -> musexc
8 -> musicas
9 -> musinc
10 -> muslist
11 -> testefor1
12 -> testefor2
- Pois é amigo, tenho certeza que você já tomou um xarope do comando for. Por hoje chega, na próxima vez que nos
encontrarmos falaremos sobre outras instruções de loop, mas eu 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.