Académique Documents
Professionnel Documents
Culture Documents
Papo de Botequim
Você não agüenta mais aquele seu porque, em inglês, Shell significa con-
cha, carapaça, isto é, fica entre o u-
amigo usuário de Linux enchendo o suário e o sistema operacional, de
forma que tudo que interage com
seu saco com aquela história de que o o sistema operacional, tem que
passar pelo seu crivo.
sistema é fantástico e o Shell é uma
D
iálogo entreouvido em uma mesa O Linux, por definição, é um sistema
de um botequim, entre um multiusuário – não podemos nunca nos
usuário de Linux e um empur- esquecer disto – e para permitir o acesso
rador de mouse: O ambiente Linux de determinados usuários e barrar a en-
• Quem é o Bash? Para você entender o que é e como fun- trada de outros, existe um arquivo cha-
• É o filho caçula da família Shell. ciona o Shell, primeiro vou te mostrar mado /etc/passwd, que além de fornecer
• Pô cara! Estás a fim de me deixar como funciona o ambiente em camadas dados para esta função de “leão-de-chá-
maluco? Eu tinha uma dúvida e você do Linux. Dê uma olhada no gráfico cara” do Linux, também provê informa-
me deixa com duas! mostrado na Figura 1. ções para o início de uma sessão (ou
• Não, maluco você já é há muito tem- Neste gráfico podemos ver que a ca- “login”, para os íntimos) daqueles que
po: desde que decidiu usar aquele sis- mada de hardware é a mais profunda e é passaram por esta primeira barreira. O
tema operacional que você precisa formada pelos componentes físicos do último campo de seus registros informa
reiniciar dez vezes por dia e ainda por seu computador. Em torno dela, vem a ao sistema qual é o Shell que a pessoa
cima não tem domínio nenhum sobre camada do kernel que é o cerne do vai receber ao iniciar sua sessão.
o que esta acontecendo no seu com- Linux, seu núcleo, e é quem põe o hard- Lembra que eu te falei de Shell, fa-
putador. Mas deixa isso prá lá, pois ware para funcionar, fazendo seu geren- mília, irmão? Pois é, vamos começar a
vou te explicar o que é Shell e os com- ciamento e controle. Os programas e entender isto: o Shell é a conceituação
ponentes de sua família e ao final da comandos que envolvem o kernel, dele de concha envolvendo o sistema opera-
nossa conversa você dirá: “Meu Deus se utilizam para realizar as tarefas para cional propriamente dito, é o nome
do Shell! Porque eu não optei pelo que foram desenvolvidos. Fechando tudo genérico para tratar os filhos desta idéia
Linux antes?”. isso vem o Shell, que leva este nome que, ao longo dos muitos anos de exis-
maiores galhos. Seu nome é “pipe” (que $ echo "Existem who | wc -l U $ (pwd ; cd /etc ; pwd)
em inglês significa tubo, já que ele cana- usuarios conectados" /home/meudir
liza a saída de um comando para a Existem who | wc -l usuarios U /etc
entrada de outro) e sua representação é a conectados $ pwd
| (barra vertical). /home/meudir
Hi! Olha só, não funcionou! É mesmo,
$ ls | wc -l não funcionou e não foi por causa das “Quequeiiisso” minha gente? Eu estava
21 aspas que eu coloquei, mas sim por que no /home/meudir, mudei para o /etc,
eu teria que ter executado o who | wc -l constatei que estava neste diretório com
O comando ls passou a lista de arquivos antes do echo. Para resolver este proble- o pwd seguinte e quando o agrupamento
para o comando wc, que quando está ma, tenho que priorizar a segunda parte de comandos terminou, eu vi que conti-
com a opção -l conta a quantidade de li- do comando com o uso de crases: nuava no /etc/meudir!
nhas que recebeu. Desta forma, pode- Hi! Será que tem coisa do mágico
mos afirmar categoricamente que no $ echo "Existem `who | wc -l` U Mandrake por aí? Nada disso. O interes-
meu diretório existiam 21 arquivos. usuarios conectados" sante do uso de parênteses é que eles
Existem 8 usuarios U invocam um novo Shell para executar os
$ cat /etc/passwd | sort | lp conectados comandos que estão em seu interior.
Desta forma, fomos realmente para o
A linha de comandos acima manda a Para eliminar esse monte de brancos diretório /etc, porém após a execução de
listagem do arquivo /etc/passwd para a antes do 8 que o wc -l produziu, basta todos os comandos, o novo Shell que
entrada do comando sort. Este a classi- retirar as aspas. Assim: estava no diretório /etc morreu e retor-
fica e envia para o lp que é o gerenciador namos ao Shell anterior que estava em
da fila de impressão. $ echo Existem `who | wc -l` U /home/meudir.
usuarios conectados Que tal usar nossos novos conceitos?
Caracteres de ambiente Existem 8 usuarios conectados
Quando queremos priorizar uma expres- $ mail suporte@linux.br << FIM
são, nós a colocamos entre parênteses, As aspas protegem da interpretação do Ola suporte, hoje as `date U
não é? Pois é, por causa da aritmética é Shell tudo que está dentro dos seus lim- “+%hh:mm”` ocorreu novamente U
normal pensarmos deste jeito. Mas em ites. Como para o Shell basta um espaço aquele problema que eu havia U
Shell o que prioriza mesmo são as crases em branco como separador, o monte de reportado por telefone. De U
(`) e não os parênteses. Vou dar exemp- espaços será trocado por um único após acordo com seu pedido segue a U
los para você entender melhor. a retirada das aspas. listagem do diretorio:
Eu quero saber quantos usuários estão Outra coisa interessante é o uso do `ls -l`
“logados” no computador que eu admi- ponto-e-vírgula. Quando estiver no Shell, Abracos a todos.
nistro. Eu posso fazer: você deve sempre dar um comando em FIM
cada linha. Para agrupar comandos em
$ who | wc -l uma mesma linha, temos que separá-los Finalmente agora podemos demonstrar o
8 por ponto-e-vírgula. Então: que conversamos anteriormente sobre
“here document”. Os comandos entre
O comando who passa a lista de usuários $ pwd ; cd /etc; pwd ;cd -;pwd crases tem prioridade, portanto o Shell
conectados ao sistema para o comando /home/meudir os executará antes do redirecionamento
wc -l, que conta quantas linhas recebeu e /etc do “here document”. Quando o suporte
mostra a resposta na tela. Muito bem, /home/meudir receber a mensagem, verá que os
mas ao invés de ter um número oito comandos date e ls foram executados
solto na tela, o que eu quero mesmo é Neste exemplo, listei o nome do diretório antes do comando mail, recebendo então
que ele esteja no meio de uma frase. Ora, corrente com o comando pwd, mudei um instantâneo do ambiente no
para mandar frases para a tela eu só pre- para o diretório /etc, novamente listei o momento de envio do email.
ciso usar o comando echo; então vamos nome do diretório e finalmente voltei pa- - Garçom, passa a régua! ■
ver como é que fica: ra o diretório onde estava anteriormente
(cd -), listando seu nome. Repare que Julio Cezar Neves é Analista de Su-
SOBRE O AUTOR
Buraco Negro coloquei o ponto-e-vírgula de todas as porte de Sistemas desde 1969 e tra-
Em Unix existe um arquivo fantasma. formas possíveis, para mostrar que não balha com Unix desde 1980, quando
Chama-se /dev/null.Tudo que é enviado importa se existem espaços em branco fez parte da equipe que desenvolveu
para este arquivo some. Assemelha-se a um antes ou após este caracter. o SOX, sistema operacional, similar
Buraco Negro. No caso do exemplo, como ao Unix, da Cobra Computadores. É
Finalmente, vamos ver o caso dos
não me interessava guardar a possível men- professor do curso de Mestrado em
parênteses. No exemplo a seguir, colo-
sagem de erro oriunda do comando rm, redi- Software Livre das Faculdades Estácio
camos diversos comandos separados por
recionei-a para este arquivo. de Sá, no Rio de Janeiro.
ponto-e-vírgula entre parênteses:
G
arçom! Traz um “chops” e dois Olha que legal! O grep aceita como
“pastel”. O meu amigo hoje não entrada a saída de outro comando, redi-
vai beber porque está finalmente recionado por um pipe (isso é muito
sendo apresentado a um verdadeiro sis- comum em Shell e é um tremendo
tema operacional, e ainda tem muita acelerador da execução de coman-
coisa a aprender! dos). Dessa forma, no 3° exemplo,
– E então, amigo, tá entendendo o comando who listou as pessoas
tudo que te expliquei até agora? “logadas” na mesma máquina que
– Entendendo eu tô, mas não vi você (não se esqueça jamais: o
nada prático nisso… Linux é multiusuário) e o grep foi
– Calma rapaz, o que te falei até usado para verificar se o Carvalho
agora serve como base ao que há estava trabalhando ou “coçando”.
de vir daqui pra frente. Vamos usar O grep é um comando muito con-
essas ferramentas que vimos para hecido, pois é usado com muita fre-
montar programas estruturados. Você qüência. O que muitas pessoas não
verá porque até na TV já teve pro- sabem é que existem três comandos na
grama chamado “O Shell é o Limite”. família grep: grep, egrep e fgrep. A princi-
Para começar vamos falar dos coman- pais diferenças entre os 3 são:
dos da família grep • grep - Pode ou não usar expressões
– Grep? Não conheço nenhum termo em $ grep franklin /etc/passwd regulares simples, porém no caso de
inglês com este nome… não usá-las, o fgrep é melhor, por ser
– É claro, grep é um acrônimo (sigla) Pesquisando em vários arquivos: mais rápido.
para Global Regular Expression Print, • egrep (“e” de extended, estendido) - É
que usa expressões regulares para $ grep grep *.sh muito poderoso no uso de expressões
pesquisar a ocorrência de cadeias de regulares. Por ser o mais poderoso dos
caracteres na entrada definida. Pesquisando na saída de um comando: três, só deve ser usado quando for
Por falar em expressões regulares (ou necessária a elaboração de uma
regexp), o Aurélio Marinho Jargas es- $ who | grep carvalho expressão regular não aceita pelo grep.
creveu dois artigos [1 e 2] imperdíveis • fgrep (“f” de fast, rápido) - Como o
para a Revista do Linux sobre esse No 1º exemplo, procurei a palavra nome diz, é o ligeirinho da família,
assunto e também publicou um livro [3] franklin em qualquer lugar do arquivo executando o serviço de forma muito
pela Editora Novatec. Acho bom você ler /etc/passwd. Se quisesse procurar um veloz (por vezes é cerca de 30% mais
esses artigos, eles vão te ajudar no que nome de usuário, isto é, somente no iní- rápido que o grep e 50% mais que o
está para vir. cio dos registros desse arquivo, poderia egrep), porém não permite o uso de
digitar $ grep ‘^franklin’ /etc/passwd. expressões regulares na pesquisa.
Eu fico com grep,você com gripe “E para que servem o circunflexo e os –Agora que você já conhece as difer-
Esse negócio de gripe é brincadeira, só apóstrofos?”, você vai me perguntar. Se enças entre os membros da família, me
um pretexto para pedir umas caipirinhas. tivesse lido os artigos que mencionei, diga: o que você acha dos três exemplos
Eu te falei que o grep procura cadeias de saberia que o circunflexo serve para limi- que eu dei antes das explicações?
caracteres dentro de uma entrada defi- tar a pesquisa ao início de cada linha e – Achei que o fgrep resolveria o teu prob-
nida, mas o que vem a ser uma “entrada os apóstrofos servem para o Shell não lema mais rapidamente que o grep.
definida”? Bem, existem várias formas interpretar esse circunflexo, deixando-o – Perfeito! Tô vendo que você está
de definir a entrada do comando grep. passar incólume para o comando grep. atento, entendendo tudo que estou te
Veja só. Para pesquisar em um arquivo: No 2º exemplo mandei listar todas as explicando! Vamos ver mais exemplos
para clarear de vez as diferenças de -l). Os apóstrofos foram usados para o Isto é, o nome do álbum será separado
uso entre os membros da família. Shell não “ver” o circunflexo. Vamos ver por um circunflexo do resto do registro,
Eu sei que em um arquivo qualquer mais um. Veja na Tabela 1 as quatro formado por diversos grupos compostos
existe um texto falando sobre Linux, só primeiras posições possíveis da saída de pelo intérprete de cada música do CD e a
não tenho certeza se está escrito com L um ls -l em um arquivo comum (não é música interpretada. Estes grupos são
maiúsculo ou minúsculo. Posso fazer diretório, nem link, nem …). separados entre si por dois pontos (:) e,
uma busca de duas formas: Para descobrir todos os arquivos exe- internamente, o intérprete será separado
cutáveis em um determinado diretório por um til (~) do nome da música.
egrep (Linux | linux) arquivo.txt eu poderia fazer: Quero escrever um programa chamado
musinc, que incluirá registros no meu
ou então: $ ls -la | egrep ‘^-..(x|s)’ arquivo músicas. Passarei cada álbum
como parâmetro para o programa:
grep [Ll]inux arquivo.txt novamente usamos o circunflexo para
limitar a pesquisa ao início de cada $ musinc “álbum^interprete~U
No primeiro caso, a expressão regular linha, ou seja, listamos as linhas que musica:interprete~musica:...”
complexa (Linux | linux) usa os parênte- começam por um traço (-), seguido de
ses para agrupar as opções e a barra ver- qualquer coisa (o ponto), novamente Desta forma, musinc estará recebendo os
tical (|) é usada como um “ou” (or, em seguido de qualquer coisa, e por fim um dados de cada álbum como se fosse uma
inglês) lógico, isto é, estou procurando x ou um s. Obteríamos o mesmo resul- variável. A única diferença entre um
Linux ou linux. tado se usássemos o comando: parâmetro recebido e uma variável é que
No segundo, a expressão regular os primeiros recebem nomes numéricos
[Ll]inux significa: começado por L ou l $ ls -la | grep ‘^-..[xs]’ (o que quis dizer é que seus nomes são
seguido de inux. Como esta é uma formados somente por um algarismo,
expressão simples, o grep consegue e além disso, agilizaríamos a pesquisa. isto é, $1, $2, $3, …, $9). Vamos, fazer
resolvê-la, por isso é melhor usar a mais alguns testes:
segunda forma, já que o egrep tornaria a A “CDteca”
pesquisa mais lenta. Vamos começar a desenvolver progra- $ cat teste
Outro exemplo. Para listar todos os mas! Creio que a montagem de um #!/bin/bash
subdiretórios do diretório corrente, basta banco de dados de músicas é bacana #Teste de passagem de parametros
usar o comando $ ls -l | grep ‘^d’. Veja o para efeito didático (e útil nestes tempos echo “1o. parm -> $1”
resultado no Quadro 1. de downloads de arquivos MP3 e echo “2o. parm -> $2”
No exemplo, o circunflexo (^) serviu queimadores de CDs). Não se esqueça echo “3o. parm -> $3”
para limitar a pesquisa à primeira que, da mesma forma que vamos desen-
posição da saída do ls longo (parâmetro volver um monte de programas para Agora vamos rodar esse programinha:
organizar os seus CDs de música, com
Tabela 1 pequenas adaptações você pode fazer o $ teste passando parametros para U
mesmo para organizar os CDs de soft- testar
Posição Valores possíveis
1ª -
ware que vêm com a Linux Magazine e bash: teste: cannot execute
2ª r ou -
outros que você compra ou queima, e
3ª w ou - disponibilizar esse banco de software Ops! Esqueci-me de tornar o script exe-
4ª x,s(suid) ou - para todos os que trabalham com você cutável. Vou fazer isso e testar nova-
(o Linux é multiusuário, e como tal deve mente o programa:
$ chmod 755 teste Execute o programa: inclusão de CDs no meu banco chamado
$ teste passando parametros para U musicas. O programa é muito simples
testar $ teste passando parametros para testar (como tudo em Shell). Veja a Listagem 1.
1o. parm -> passando O programa teste recebeu 4 U O script é simples e funcional; limito-
2o. parm -> parametros parametros me a anexar ao fim do arquivo musicas o
3o. parm -> para 1o. parm -> passando parâmetro recebido. Vamos cadastrar 3
2o. parm -> parametros álbuns para ver se funciona (para não
Repare que a palavra testar, que seria o 3o. parm -> para ficar “enchendo lingüiça,” suponho que
quarto parâmetro, não foi listada. Isso Para listar todos de uma U em cada CD só existem duas músicas):
ocorreu porque o programa teste só lista “tacada” eu faco passando U
os três primeiros parâmetros recebidos. parametros para testar $ musinc “album3^Artista5U
Vamos executá-lo de outra forma: ~Musica5:Artista6~Musica5”
Repare que antes das aspas usei uma $ musinc “album1^Artista1U
$ teste “passando parametros” U barra invertida, para escondê-las da ~Musica1:Artista2~Musica2”
para testar interpretação do Shell (se não usasse as $ musinc “album 2^Artista3U
1o. parm -> passando parametros contrabarras as aspas não apareceriam). ~Musica3:Artista4~Musica4”
2o. parm -> para Como disse, os parâmetros recebem
3o. parm -> testar números de 1 a 9, mas isso não significa Listando o conteúdo do arquivo musicas:
que não posso usar mais de nove
As aspas não deixaram o Shell ver o parâmetros. Significa que só posso $ cat musicas
espaço em branco entre as duas endereçar nove. Vamos testar isso: album3^Artista5~Musica5:Artista6U
primeiras palavras, e elas foram consid- ~Musica6
eradas como um único parâmetro. E $ cat teste album1^Artista1~Musica1:Artista2U
falando em passagem de parâmetros, #!/bin/bash ~Musica2
uma dica: veja na Tabela 2 algumas var- # Programa para testar passagem U album2^Artista3~Musica3:Artista4U
iáveis especiais. Vamos alterar o pro- de parametros (3a. Versao) ~Musica4
grama teste para usar as novas variáveis: echo O programa $0 recebeu $# U
parametros Podia ter ficado melhor. Os álbuns estão
$ cat teste echo “11o. parm -> $11” fora de ordem, dificultando a pesquisa.
#!/bin/bash shift Vamos alterar nosso script e depois testá-
# Programa para testar passagem U echo “2o. parm -> $1” lo novamente. Veja a listagem 2. Sim-
de parametros (2a. Versao) shift 2 plesmente inseri uma linha que classifica
echo O programa $0 recebeu $# U echo “4o. parm -> $1” o arquivo musicas, redirecionando a
parametros saída para ele mesmo (para isso serve a
echo “1o. parm -> $1” Execute o programa: opção -o), após cada álbum ser anexado.
echo “2o. parm -> $2”
echo “3o. parm -> $3” $ teste passando parametros para U $ cat musicas
echo Para listar todos de uma U testar album1^Artista1~Musica1:Artista2U
\”tacada\” eu faco $* O programa teste recebeu 4 U ~Musica2
parametros que são: albu2^Artista3~Musica3:Artista4U
Listagem 1: Incluindo CDs 11o. parm -> passando1 ~Musica4
na “CDTeca” 2o. parm -> parametros album3^Artista5~Musica5:Artista6U
4o. parm -> testar ~Musica6
$ cat musinc
#!/bin/bash Duas coisas muito interessantes aconte- Oba! Agora o programa está legal e
# Cadastra CDs (versao 1) ceram neste script. Para mostrar que os quase funcional. Ficará muito melhor em
# nomes dos parâmetros variam de $1 a $9 uma nova versão, que desenvolveremos
echo $1 >> musicas digitei echo $11 e o que aconteceu? O após aprender a adquirir os dados da tela
Shell interpretou como sendo $1 seguido e formatar a entrada.
do algarismo 1 e listou passando1;
Listagem 2 O comando shift, cuja sintaxe é shift n, Tabela 2: Variáveis especiais
podendo o n assumir qualquer valor
$ cat musinc Variável Significado
numérico, despreza os n primeiros
#!/bin/bash $0 Contém o nome do programa
parâmetros, tornando o parâmetro de
# Cadastra CDs (versao 2) $# Contém a quantidade de
ordem n+1. parâmetros passados
#
Bem, agora que você já sabe sobre $* Contém o conjunto de todos os
echo $1 >> musicas
passagem de parâmetros, vamos voltar à parâmetros (muito parecido com $@)
sort -o musicas musicas
nossa “cdteca” para fazer o script de
Analista de Suporte de
Listagem 4 ~Musica2
Sistemas desde 1969 e
muslist melhorado album3^Artista5~Musica5:Artista6U trabalha com Unix
~Musica6 desde 1980, quando
$ cat muslist
fez parte da equipe
#!/bin/bash
Conforme expliquei antes, o grep do que desenvolveu o
# Consulta CDs (versao 2) SOX, um sistema
exemplo listou todos os registros de
# operacional similar ao Unix, produzido
musicas exceto o referente a album 2, pela Cobra Computadores.
grep -i “$1” musicas
porque atendia ao argumento do co-
Papo de
botequim III
Um chopinho, um aperitivo e o papo continua. Desta vez vamos aprender
alguns comandos de manipulação de cadeias de caracteres, que serão muito
é usado para cortar um determinado circunflexo (^) do resto do registro, que Para entender melhor isso, vamos anali-
pedaço de um arquivo. A sintaxe e é formado por diversos grupos compos- sar a primeira linha de musicas:
alguns exemplos de uso podem ser vis- tos pelo intérprete de cada música do
tos no Quadro 1: CD e a respectiva música interpretada. $ head -1 musicas
Como dá para ver, existem quatro Estes grupos são separados entre si por album 1^Artista1~Musica1: U
sintaxes distintas: na primeira (-c 1-5) dois-pontos (:) e o intérprete será sepa- Artista2~Musica2
especifiquei uma faixa, na segunda rado do nome da música por um til (~).
(-c -6) especifiquei todo o texto até Então, para pegar os dados referentes Então observe o que foi feito:
uma posição, na terceira (-c 4-) tudo de a todas as segundas músicas do arquivo
uma determinada posição em diante e musicas, devemos digitar: album 1^Artista1~Musica1: U
na quarta (-c 1,3,5,7,9), só as posições Artista2~Musica2
determinadas. A última possibilidade $ cut -f2 -d: musicas
(-c -3,5,8-) foi só para mostrar que pode- Artista2~Musica2 Desta forma, no primeiro cut o primeiro
mos misturar tudo. Artista4~Musica4 campo do delimitador (-d) dois-pon-
Mas não pense que acabou por aí! Artista6~Musica5 tos (:) é album 1^Artista1~Musica1 e o
Como você deve ter percebido, esta Artista8~Musica8@10_L: segundo, que é o que nos interessa, é
forma de cut é muito útil para lidar com Artista2~Musica2. Vamos então ver o
arquivos com campos de tamanho fi xo, Ou seja, cortamos o segundo campo que aconteceu no segundo cut:
mas atualmente o que mais existe são z(-f de field, campo em inglês) delimi-
arquivos com campos de tamanho vari- tado (-d) por dois-pontos (:). Mas, se Artista2~Musica2
ável, onde cada campo termina com um quisermos somente os intérpretes, deve-
delimitador. Vamos dar uma olhada no mos digitar: Agora, primeiro campo do delimitador
arquivo musicas que começamos a pre- (-d) til (~), que é o que nos interessa,
parar na última vez que viemos aqui no $ cut -f2 -d: musicas | cut U é Artista2 e o segundo é Musica2. Se
botequim. Veja o Quadro 2. -f1 -d~ o raciocínio que fizemos para a pri-
�����������������������������
www.linuxmagazine.com.br Outubro 2004 85
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim
$ tr A-Z a-z < ArqEnt > / tmp/$$ $ tr ”;” ”\012” < confuso
Quadro 2 – O arquivo $ mv -f /tmp/$$ ArqEnt
musicas Agora veja a diferença entre o resultado
Note que neste caso usei a notação A- de um comando date executado hoje e
Z para não escrever ABCD…YZ. Outro outro executado há duas semanas:
$ cat musicas
tipo de notação que pode ser usada são
album 1^Artista1~Musica1:U
as escape sequences (como eu traduzi- Sun Sep 19 14:59:54 2004
Artista2~Musica2
ria? Seqüências de escape? Meio sem Sun Sep 5 10:12:33 2004
album 2^Artista3~Musica3:U
sentido, né? Mas vá lá…) que também
Artista4~Musica4
são reconhecidas por outros comandos Notou o espaço extra após o “Sep” na
album 3^Artista5~Musica5:U
e também na linguagem C, e cujo signi- segunda linha? Para pegar a hora eu
Artista6~Musica5
ficado você verá na Tabela 2: deveria digitar:
album 4^Artista7~Musica7:U
Deixa eu te contar um “causo”: um
Artista8~Musica8
aluno que estava danado comigo resol- $ date | cut -f 4 -d ’ ’
veu complicar minha vida e como res- 14:59:54
�����������������������������
86 Outubro 2004 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER
Mas há duas semanas ocorreria o Bem a opção -d do tr remove do arquivo E veja também o date:
seguinte: todas as ocorrências do caractere espe-
cificado. Desta forma eu removi os $ date
$ date | cut -f 4 -d ’ ’ caracteres indesejados, salvei o texto Mon Sep 20 10:47:19 BRT 2004
5 em um arquivo temporário e posterior-
mente renomeei-o para o nome original. Repare que o mês e o dia estão no
Isto porque existem 2 caracteres em Uma observação: em um sistema Unix mesmo formato em ambos os coman-
branco antes do 5 (dia). Então o ideal eu deveria digitar: dos. Ora, se em algum registro do who
seria transformar os espaços em branco eu não encontrar a data de hoje, é sinal
consecutivos em somente um espaço $ tr -d ’\015’ < ArqDoDOS.U que o usuário está “logado” há mais
para poder tratar os dois resultados do txt > /tmp/$$ de um dia, já que ele não pode ter se
comando date da mesma forma, e isso “logado” amanhã… Então vamos guar-
se faz assim: Uma dica: o problema com os termina- dar o pedaço que importa da data de
dores de linha (CR/LF) só aconteceu hoje para depois procurá-la na saída do
$ date | tr -s ” ” porque a transferência do arquivo foi comando who:
Sun Sep 5 10:12:33 2004 feita no modo binário (ou image), Se
antes da transmissão do arquivo tivesse $ Data=$(date | cut -f 2-3 U
E agora eu posso cortar: sido estipulada a opção ascii do ftp, isto -d’ ’)
não teria ocorrido.
$ date | tr -s ” ” | cut -f 4 U – Olha, depois desta dica tô começando a Eu usei a construção $(...), para prio-
-d ” ” gostar deste tal de shell, mas ainda tem rizar a execução dos comandos antes
10:12:33 muita coisa que não consigo fazer. de atribuir a sua saída à variável Data.
– Pois é, ainda não te falei quase nada Vamos ver se funcionou:
Olha só como o Shell está quebrando o sobre programação em shell, ainda
galho. Veja o conteúdo de um arquivo tem muita coisa para aprender, mas $ echo $Data
baixado de uma máquina Windows: com o que aprendeu, já dá para resol- Sep 20
ver muitos problemas, desde que você
$ cat -ve ArqDoDOS.txt adquira o “modo shell de pensar”. Você Beleza! Agora, o que temos que fazer é
Este arquivo^M$ seria capaz de fazer um script que diga procurar no comando who os registros
foi gerado pelo^M$ quais pessoas estão “logadas” há mais que não possuem esta data.
DOS/Win e foi^M$ de um dia no seu servidor?
baixado por um^M$ – Claro que não! Para isso seria necessá- – Ah! Eu acho que estou entendendo!
ftp mal feito.^M$ rio eu conhecer os comandos condicio- Você falou em procurar e me ocorreu o
nais que você ainda não me explicou comando grep, estou certo?
Dica: a opção -v do cat mostra os carac- como funcionam. - Deixa eu tentar – Certíssimo! Só que eu tenho que usar o
teres de controle invisíveis, com a nota- mudar um pouco a sua lógica e trazê- grep com aquela opção que ele só lista
ção ^L, onde ^ é a tecla Control e L é la para o “modo shell de pensar”, mas os registros nos quais ele não encon-
a respectiva letra. A opção -e mostra o antes é melhor tomarmos um chope. trou a cadeia. Você se lembra que
fi nal da linha como um cifrão ($). Agora que já molhei a palavra, vamos opção é essa?
Isto ocorre porque no DOS o fi m dos resolver o problema que te propus. Veja – Claro, a -v…
registros é indicado por um Carriage como funciona o comando who: – Isso! Tá ficando bom! Vamos ver:
Return (\r – Retorno de Carro, CR) e
um Line Feed (\f – Avanço de Linha, ou $ who $ who | grep -v ”$Data”
LF). No Linux porém o fi nal do regis- jneves pts/ U jneves pts/ U
tro é indicado somente pelo Line Feed. 1 Sep 18 13:40 1 Sep 18 13:40
Vamos limpar este arquivo: rtorres pts/ U
0 Sep 20 07:01 Se eu quisesse um pouco mais de perfu-
$ tr -d ’\r’ < ArqDoDOS.txt > /tmp/$$ rlegaria pts/ U maria eu faria assim:
$ mv -f /tmp/$$ ArqDoDOS.txt 1 Sep 20 08:19
lcarlos pts/ U $ who | grep -v ”$Data” |U
Agora vamos ver o que aconteceu: 3 Sep 20 10:01 cut -f1 -d ’ ’
jneves
$ cat -ve ArqDoDOS.txt Tabela 1 – O comando tr
Este arquivo$ Opção Significado Viu? Não foi necessário usar comando
foi gerado pelo$ condicional, até porque o nosso
-s Comprime n ocorrências de
DOS/Rwin e foi$ cadeia1 em apenas uma
comando condicional, o famoso if, não
baixado por um$ testa condição, mas sim instruções.
-d Remove os caracteres de cadeia1
ftp mal feito.$ Mas antes veja isso:
�����������������������������
www.linuxmagazine.com.br Outubro 2004 87
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim
�����������������������������
88 Outubro 2004 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER
Lembra que eu falei que ao longo dos no início da cadeia e cifrão ($) no fi m, Como você viu, o programa melho-
nossos papos e chopes os nossos pro- servem para testar se o parâmetro (o rou um pouquinho, mas ainda não está
gramas iriam se aprimorando? Então álbum e seus dados) é exatamente igual pronto. À medida que eu te ensinar a
vejamos agora como podemos melhorar a algum registro já existente. Vamos programar em shell, nossa CDteca vai
o nosso programa para incluir músicas executar nosso programa novamente, ficar cada vez melhor.
na "CDTeca": mas desta vez passamos como parâme- – Entendi tudo que você me explicou,
tro um álbum já cadastrado, pra ver o mas ainda não sei como fazer um if
$ cat musinc que acontece: para testar condições, ou seja o uso
#!/bin/bash normal do comando.
# Cadastra CDs (versao 3) $ musinc ”album 4^Artista7~ U – Para isso existe o comando test, que
# Musica7:Artista8~Musica8” testa condições. O comando if testa
if grep ”^$1$” musicas > U Este álbum já está cadastrado o comando test. Como já falei muito,
/dev/null preciso de uns chopes para molhar a
then E agora um não cadastrado: palavra. Vamos parar por aqui e na
echo Este álbum já está U próxima vez te explico direitinho o
cadastrado $ musinc ”album 5^Artista9~ U uso do test e de diversas outras sinta-
else Musica9:Artista10~Musica10” xes do if.
echo $1 >> musicas $ cat musicas – Falou! Acho bom mesmo porque eu
sort musicas -o musicas album 1^Artista1~Musica1: U também já tô ficando zonzo e assim
fi Artista2~Musica2 tenho tempo para praticar esse monte
album 2^Artista3~Musica3: U de coisas que você me falou hoje.
Como você viu, é uma pequena evolu- Artista4~Musica4 Para fi xar o que você aprendeu, tente
ção em relação à versão anterior. Antes album 3^Artista5~Musica5: U fazer um scriptizinho para informar se
de incluir um registro (que na versão Artista6~Musica5 um determinado usuário, cujo nome
anterior poderia ser duplicado), testa- album 4^Artista7~Musica7: U será passado como parâmetro, está
mos se o registro começa (^) e termina Artista8~Musica8 “logado” no sistema ou não.
($) de forma idêntica ao parâmetro album 5^Artista9~Musica9: U – Aê Chico! traz mais dois chopes pra
álbum passado ($1). O circunflexo (^) Artista10~Musica10 mim por favor… ■
�����������������������������
www.linuxmagazine.com.br Outubro 2004 89
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim
Papo de
botequim IV
O garçon já perdeu a conta das cervejas, e o assunto não acaba. Desta
Dave Hamilton - www.sxc.hu
�����������������������������
84 edição 04 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER
Tabela 2 – Opções do test No exemplo, testei a existência do cadeia de caracteres “01” é realmente
para cadeias de caracteres diretório lmb. Se não existisse (else), ele diferente de “1”. Porém, a coisa muda
seria criado. Já sei, você vai criticar a de figura quando as variáveis são testa-
Opção Verdadeiro se:
minha lógica dizendo que o script não das numericamente, já que o número 1
-z cadeia Tamanho de cadeia é zero
está otimizado. Eu sei, mas queria que é igual ao número 01.
-n cadeia Tamanho de cadeia é maior que zero
cadeia A cadeia cadeia tem tamanho maior que zero você o entendesse assim, para então Para mostrar o uso dos conectores -o
c1 = c2 Cadeia c1 e c2 são idênticas poder usar o “ponto-de-espantação” (!) (ou) e -a (e), veja um exemplo “animal”,
como um negador do test. Veja só: programado direto no prompt do Bash.
Me desculpem os zoólogos, mas eu
diferente de. Para testar condições em if test ! -d lmb não entendo nada de reino, fi lo, classe,
Shell Script usamos o comando test, só then ordem, família, gênero, espécie e outras
que ele é muito mais poderoso do que mkdir lmb coisas do tipo, desta forma o que estou
aquilo com que estamos acostuma- fi chamando de família ou de gênero tem
dos. Primeiramente, veja na Tabela 1 cd lmb grande chance de estar total e comple-
as principais opções (existem muitas tamente incorreto:
outras) para testar arquivos em disco Desta forma o diretório lmb seria
e na Tabela 2 as principais opções para criado somente se ele ainda não exis- $ Familia=felinae
teste de cadeias de caracteres. tisse, e esta negativa deve-se ao ponto $ Genero=gato
de exclamação (!) precedendo a opção $ if test $Familia = canidea U
Tabela 3 – Opções do -d. Ao fi m da execução desse fragmento -a $Genero = lobo -o $Familia = U
test para números de script, com certeza o programa esta- felina -a $Genero = leão
ria dentro do diretório lmb. Vamos ver > then
Opção Verdadeiro se Significado
dois exemplos para entender a diferença > echo Cuidado
n1 -eq n2 n1 e n2 são iguais equal
na comparação entre números e entre > else
n1 -ne n2 n1 e n2 não são iguais not equal
cadeias de caracteres. > echo Pode passar a mão
n1 -gt n2 n1 é maior que n2 greater than
n1 -ge n2 n1 é maior ou igual a n2 greater or equal > fi
n1 -lt n2 n1 é menor que n2 less than cad1=1 Pode passar a mão
n1 -le n2 n1 é menor ou igual a n2 less or equal cad2=01
if test $cad1 = $cad2 Neste exemplo, caso o animal fosse
then da família canídea e (-a) do gênero lobo,
Pensa que acabou? Engano seu! Agora echo As variáveis são iguais. ou (-o) da familia felina e (-a) do gênero
é hora de algo mais familiar, as famosas else leão, seria dado um alerta, caso contrá-
comparações com valores numéricos. echo As variáveis são diferentes. rio a mensagem seria de incentivo.
Veja a Tabela 3, e some às opções já fi Atenção: Os sinais de maior (>) no
apresentadas os operadores da Tabela 4. início das linhas internas ao if são os
Ufa! Como você viu, tem coisa pra Executando o fragmento de programa prompts de continuação (que estão
chuchu, e o nosso if é muito mais pode- acima, teremos como resultado: defi nidos na variável $PS2). Quando o
roso que o dos outros. Vamos ver em shell identifica que um comando con-
uns exemplos como isso tudo funciona. As variáveis são diferentes. tinuará na linha seguinte, automatica-
Testamos a existência de um diretório: mente ele coloca este caractere, até que
Vamos agora alterá-lo um pouco para o comando seja encerrado.
if test -d lmb que a comparação seja numérica: Vamos mudar o exemplo para ver se o
then programa continua funcionando:
cd lmb cad1=1
else cad2=01 $ Familia=felino
mkdir lmb if test $cad1 -eq $cad2 $ Genero=gato
cd lmb then $ if test $Familia = felino -o U
fi echo As variáveis são iguais. $Familia = canideo -a $Genero = U
else onça -o $Genero = lobo
Tabela 4 echo As variáveis são diferentes. > then
fi > echo Cuidado
Operador Finalidade > else
Parênteses () 0
E vamos executá-lo novamente: > echo Pode passar a mão
> fi
Exclamação ! 0
As variáveis são iguais. Cuidado
-a 0
-o 0
Como você viu, nas duas execuções Obviamente a operação resultou em
obtive resultados diferentes, porque a erro, porque a opção -a tem precedência
�����������������������������
www.linuxmagazine.com.br edição 04 85
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim
sobre a -o e, dessa, forma o que foi ava- Da mesma forma, para escolhermos mente a legibilidade, pois o comando if
liado primeiro foi a expressão: CDs que tenham a participação do irá ficar com a sintaxe semelhante à das
Artista1 e do Artista2, não é necessá- outras linguagens; por isso, esse será o
$Familia = canideo -a $Genero = U rio montar um if com o conector -o. O modo como o comando test será usado
onça egrep também resolve isso para nós. daqui para a frente.
Veja como: Se você pensa que acabou, está muito
Que foi avaliada como falsa, retor- enganado. Preste atenção à “Tabela Ver-
nando o seguinte: $ egrep (Artista1|Artista2) U dade” na Tabela 5.
musicas
$Familia = felino -o FALSO -o U Tabela 5 - Tabela Verdade
$Genero = lobo Ou (nesse caso específico) o próprio
Combinação E OU
grep poderia nos quebrar o galho:
VERDADEIRO-VERDADEIRO TRUE TRUE
Que resolvida resulta em:
VERDADEIRO-FALSO FALSE TRUE
$grep Artista[12] musicas FALSO-VERDADEIRO FALSE TRUE
VERDADEIRO -o FALSO -o FALSO FALSO-FALSO FALSE FALSE
No egrep acima, foi usada uma
Como agora todos os conectores são expressão regular, na qual a barra ver-
-o, e para que uma série de expressões tical (|) trabalha como um “ou lógico” e Ou seja, quando o conector é e e a
conectadas entre si por diversos “ou” os parênteses são usados para limitar primeira condição é verdadeira, o resul-
lógicos seja verdadeira, basta que uma a amplitude deste “ou”. Já no grep da tado fi nal pode ser verdadeiro ou falso,
delas o seja. A expressão fi nal resultou linha seguinte, a palavra Artista deve dependendo da segunda condição; já no
como VERDADEIRO e o then foi exe- ser seguida por um dos valores da lista conector ou, caso a primeira condição
cutado de forma errada. Para que isso formada pelos colchetes ([]), isto é, 1 ou 2. seja verdadeira, o resultado sempre será
volte a funcionar façamos o seguinte: verdadeiro. Se a primeira for falsa, o resul-
– Tá legal, eu aceito o argumento, o if tado dependerá da segunda condição.
$ if test \($Familia = felino U do shell é muito mais poderoso que Ora, os caras que desenvolveram o
-o $Familia = canideo\) -a U os outros caretas - mas, cá entre nós, interpretador não são bobos e estão
\($Genero = onça -o $Genero = U essa construção de if test ... é muito sempre tentando otimizar ao máximo
lobo\) esquisita, é pouco legível. os algoritmos. Portanto, no caso do
> then – É, você tem razão, eu também não conector e, a segunda condição não será
> echo Cuidado gosto disso e acho que ninguém gosta. avaliada, caso a primeira seja falsa, já
> else Acho que foi por isso que o shell que o resultado será sempre falso. Já
> echo Pode passar a mão incorporou outra sintaxe, que substi- com o ou, a segunda será executada
> fi tui o comando test. somente caso a primeira seja falsa.
Pode passar a mão Aproveitando-se disso, uma forma
Para isso vamos pegar aquele exem- abreviada de fazer testes foi criada. O
Desta forma, com o uso dos parên- plo para fazer uma troca de diretórios, conector e foi batizado de && e o ou de
teses agrupamos as expressões com o que era assim: ||. Para ver como isso funciona, vamos
conector -o, priorizando a execução e usá-los como teste no nosso velho
resultando em VERDADEIRO -a FALSO. if test ! -d lmb exemplo de troca de diretório, que em
Para que seja VERDADEIRO o resul- then sua última versão estava assim:
tado de duas expressões ligadas pelo mkdir lmb
conector -a, é necessário que ambas fi if [ ! -d lmb ]
sejam verdadeiras, o que não é o caso cd lmb then
do exemplo acima. Assim, o resultado mkdir lmb
fi nal foi FALSO, sendo então o else cor- e utilizando a nova sintaxe, vamos fazê- fi
retamente executado. lo assim: cd lmb
Se quisermos escolher um CD que
tenha faixas de 2 artistas diferentes, nos if [ ! -d lmb ] O código acima também poderia ser
sentimos tentados a usar um if com o then escrito de maneira abreviada:
conector -a, mas é sempre bom lembrar mkdir lmb
que o bash nos oferece muitos recursos fi [ ! -d lmb ] && mkdir lmb
e isso poderia ser feito de forma muito cd lmb cd dir
mais simples com um único comando
grep, da seguinte forma: Ou seja, o comando test pode ser Também podemos retirar a negação (!):
substituído por um par de colchetes ([]),
$ grep Artista1 musicas | grep U separados por espaços em branco dos [ -d lmb ] || mkdir lmb
Artista2 argumentos, o que aumentará enorme- cd dir
�����������������������������
86 edição 04 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER
Tabela 6 diretório para dentro dele. Para execu- sempre estará dentro de lmb, desde que
tar mais de um comando dessa forma, tenha permissão para entrar neste dire-
Caractere Significado
é necessário fazer um grupamento de tório, permissão para criar um subdire-
* Qualquer caractere ocorrendo zero ou
comandos, o que se consegue com o tório dentro de ../lmb, que haja espaço
mais vezes
? Qualquer caractere ocorrendo uma vez
uso de chaves ({}). Veja como seria o em disco suficiente...
[...] Lista de caracteres modo correto: Vejamos um exemplo didático: depen-
| “ou” lógico dendo do valor da variável $opc o script
cd lmb || deverá executar uma das opções a
{ seguir: inclusão, exclusão, alteração ou
No primeiro caso, se o primeiro mkdir lmb encerrar sua execução. Veja como fica-
comando (o test, que está represen- cd lmb ria o código:
tado pelos colchetes) for bem sucedido, }
isto é, se o diretório lmb não existir, o if [ $opc -eq 1 ]
comando mkdir será executado porque Ainda não está legal porque, caso o then
a primeira condição era verdadeira e o diretório não exista, o cd exibirá uma inclusao
conector era e. mensagem de erro. Veja o modo certo: elif [ $opc -eq 2 ]
No exemplo seguinte, testamos se o then
diretório lmb existia (no anterior testa- cd lmb 2> /dev/null || exclusao
mos se ele não existia) e, caso isso fosse { elif [ $opc -eq 3 ]
verdade, o mkdir não seria executado mkdir lmb then
porque o conector era ou. Outra forma cd lmb alteracao
de escrever o programa: } elif [ $opc -eq 4 ]
then
cd lmb || mkdir lmb Como você viu, o comando if nos per- exit
mitiu fazer um cd seguro de diversas else
Nesse caso, se o comando cd fosse maneiras. É sempre bom lembrar que o echo Digite uma opção entre U
mal sucedido, o diretório lmb seria “seguro” a que me refi ro diz respeito ao 1 e 4
criado mas não seria feita a mudança de fato de que ao fi nal da execução você fi
�����������������������������
www.linuxmagazine.com.br edição 04 87
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim
�����������������������������
88 edição 04 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER
Papo de
Botequim V
Blocos de código e laços (ou loops, como preferem alguns)
Dave Hamilton - www.sxc.hu
–
F ala cara! E as idéias estão em
ordem? Já fundiu a cuca ou você
ainda agüenta mais Shell?
Até agora já vimos alguns blocos de
código, como quando te mostrei um
exemplo para fazer um cd para dentro
Güento! Tô gostando muito! Gostei de um diretório:
tanto que até caprichei no exercício
Quadro 1: vira.sh
$ cat vira.sh
#!/bin/bash
#
que você passou. Lembra que você cd lmb 2> /dev/null || # vira - vi resguardando
me pediu para fazer um programa { # arquivo anterior
que recebe como parâmetro o nome mkdir lmb # Verifica se algum parâmetro foi
de um arquivo e que quando execu- cd lmb # passado
tado salva esse arquivo com o nome } if [ “$#” -ne 1 ]
original seguido de um til (~) e o then
abre dentro do vi? O fragmento contido entre as duas echo “Erro -> Uso: $0 U
– Claro que lembro, me mostre e expli- chaves ({}) forma um bloco de código. <arquivo>”
que como você fez. Também nesse exercício que acabamos exit 1
– Beleza, dá uma olhada no quadro 1 de ver, em que salvamos o arquivo antes fi
– É, beleza! Mas me diz uma coisa: por de editá-lo, existem vários blocos de Arq=$1
que você terminou o programa com código compreendidos entre os coman- # Caso o arquivo não exista, não
um exit 0? dos then e fi do if. Um bloco de código # há cópia a ser salva
– Eu descobri que o número após o também pode estar dentro de um case if [ ! -f “$Arq” ]
exit indica o código de retorno do ou entre um do e um done. then
programa (o $?, lembra?) e assim, – Peraí, Julio, que do e done são esses? vi $Arq
como a execução foi bem sucedida, Não me lembro de você ter falado exit 0
ele encerra com o $?=0. Porém, se nisso, e olha que estou prestando fi
você observar, verá que caso o pro- muita atenção... # Se eu não puder alterar o
grama não tenha recebido o nome do – Pois é, ainda não tinha falado porque #arquivo, vou usar o vi para que?
arquivo ou caso o operador não tenha não havia chegado a hora certa. if [ ! -w “$Arq” ]
permissão de gravação nesse arquivo, Todas as instruções de loop ou laço then
o código de retorno ($?) seria dife- executam os comandos do bloco com- echo “Você não tem permissão U
rente do zero. preendidos entre um do e um done. As de escrita em $Arq”
– Grande garoto, aprendeu legal, mas é instruções de loop ou laço são for, while exit 2
bom deixar claro que exit 0, simples- e until , que serão explicadas uma a fi
mente exit ou não colocar exit produ- uma a partir de hoje. # Já que está tudo OK, vou
zem igualmente um código de retorno # salvar a cópia e chamar o vi
($?) igual a zero. Agora vamos falar O comando For cp -f $Arq $Arq~
sobre as instruções de loop ou laço, Se você está habituado a programar, vi $Arq
mas antes vou passar o conceito de certamente já conhece o comando for, exit 0
bloco de código. mas o que você não sabe é que o for,
�����������������������������
www.linuxmagazine.com.br edição 05 89
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim
que é uma instrução intrínseca do Shell Então vamos executá-lo: $ echo “$IFS” | od -h
(isso significa que o código fonte do 0000000 0920 0a0a
comando faz parte do código fonte do $ testefor1 0000004
Shell, ou seja, em bom programês é um ArqDoDOS.txt1:confuso:incusu:
built-in), é muito mais poderoso que os logado:musexc:musicas:musinc: Isto é, mandei a variável (protegida
seus correlatos das outras linguagens. muslist:$ da interpretação do Shell pelas aspas)
Vamos entender a sua sintaxe, pri- para um dump hexadecimal (od -h). O
meiro em português e, depois, como Como você viu, o Shell transformou resultado pode ser interpretado com a
funciona pra valer. Olhe só: o asterisco (que odeia ser chamado tabela abaixo:
de asterístico) em uma lista de arqui-
para var em val1 val2 ... valn vos separados por espaços em branco. Tabela 1: Resultado do od -h
faça Quando o for viu aquela lista, disse:
cmd1 “Opa, listas separadas por espaços é Valor Hexadecimal Significado
�����������������������������
90 edição 05 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER
�����������������������������
www.linuxmagazine.com.br edição 05 91
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim
Da mesma forma que os outros, come- Ou na forma mais completa do seq: Repare que o incremento saiu do
çamos o exercício com uma crítica sobre corpo do for e passou para o bloco de
os parâmetros recebidos, em seguida for i in $(seq 0 3 9) código; repare também que, quando
fizemos um for em que a variável do usei o let, não foi necessário iniciali-
$Musica receberá cada um dos parâme- echo -n “$i “ zar a variável $i. Veja só os comandos a
tros passados, colocando em $Str todos done seguir, digitados diretamente no prompt,
os álbuns que contêm as músicas dese- 0 3 6 9 para demonstrar o que acabo de falar:
jadas. Em seguida, o outro for pega cada
bloco Artista~Musica nos registros que A outra forma de fazer isso é com $ echo $j
estão em $Str e lista cada artista que uma sintaxe muito semelhante ao for da $ let j++
toca aquela música. Vamos executar o linguagem C, como vemos a seguir: $ echo $j
programa para ver se funciona mesmo: 1
for ((var=ini; cond; incr))
$ listamusica musica3 Musica4 U do Ou seja, a variável $j sequer existia e
“Egüinha Pocotó” cmd1 no primeiro let assumiu o valor 0 (zero)
musica3 cmd2 para, após o incremento, ter o valor 1.
Artista3 cmdn Veja só como as coisas ficam simples:
Artista1 done
Musica4 for arq in *
Artista4 Onde var=ini significa que a variá- do
Egüinha Pocotó vel var começará de um valor inicial let i++
Não encontrada ini; cond significa que o loop ou laço for echo “$i -> $Arq”
será executado enquanto var não atingir done
A listagem ficou feinha porque ainda a condição cond e incr significa o incre- 1 -> ArqDoDOS.txt1
não sabemos formatar a saída; mas mento que a variável var sofrerá a cada 2 -> confuso
qualquer dia desses, quando você sou- passada do loop. Vamos aos exemplos: 3 -> incusu
ber posicionar o cursor, trabalhar com 4 -> listamusica
cores etc., faremos esse programa nova- for ((i=1; i<=9; i++)) 5 -> listartista
mente usando todas essas perfumarias. do 6 -> logado
A esta altura dos acontecimentos, echo -n “$i “ 7 -> musexc
você deve estar se perguntando: “E done 8 -> musicas
aquele for tradicional das outras lingua- 1 2 3 4 5 6 7 8 9 9 -> musinc
gens em que ele sai contando a partir 10 -> muslist
de um número, com um determinado A variável i partiu do valor inicial 1, o 11 -> testefor1
incremento, até alcançar uma condi- bloco de código (aqui somente o echo) 12 -> testefor2
ção?”. E é aí que eu te respondo: “Eu será executado enquanto i for menor ou
não te disse que o nosso for é mais por- igual (<=) a 9 e o incremento de i será – Pois é amigo, tenho certeza que você
reta que o dos outros?” Para fazer isso, de 1 a cada passada do loop. já tomou um xarope do comando
existem duas formas. Com a primeira Repare que no for propriamente dito for. Por hoje chega, na próxima vez
sintaxe que vimos, como no exemplo: (e não no bloco de código) não coloquei em que nos encontrarmos falaremos
um cifrão ($) antes do i e a notação sobre outras instruções de loop, mas
for i in $(seq 9) para incrementar (i++) é diferente do eu gostaria que até lá você fi zesse um
do que vimos até agora. O uso de parênte- pequeno script para contar a quanti-
echo -n “$i “ ses duplos (assim como o comando let) dade de palavras de um arquivo texto,
done chama o interpretador aritmético do cujo nome seria recebido como parâ-
1 2 3 4 5 6 7 8 9 Shell, que é mais tolerante. metro. Essa contagem tem que ser
Só para mostrar como o let funciona feita com o comando for, para se habi-
A variável i assumiu os valores intei- e a versatilidade do for, vamos fazer a tuar ao seu uso. Não vale usar o wc -w.
ros entre 1 a 9 gerados pelo comando mesma coisa, mas omitindo a última Aê Chico! Traz a saideira! ■
seq e a opção -n do echo foi usada para parte do escopo do for, passando-a para
não saltar uma linha a cada número lis- o bloco de código: Julio Cezar Neves é Analista de
SOBRE O AUTOR
tado. Ainda usando o for com seq: Suporte de Sistemas desde 1969 e tra-
for ((; i<=9;)) balha com Unix desde 1980, quando
for i in $(seq 4 9) do participou do desenvolvimento do
do let i++ SOX, um sistema operacional similar
echo -n “$i “ echo -n “$i “ ao Unix produzido pela Cobra Com-
putadores. Pode ser contatado no
done done
e-mail julio.neves@gmail.com
4 5 6 7 8 9 1 2 3 4 5 6 7 8 9
�����������������������������
92 edição 05 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim
Papo de
Botequim VI
Blocos de código e laços (ou loops, como preferem alguns)
Dave Hamilton - www.sxc.hu
�����������������������������
86 edição 06 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER
�����������������������������
www.linuxmagazine.com.br edição 06 87
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim
�����������������������������
88 edição 06 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
Papo de Botequim LINUX USER
Em 20/01 às 11:23h
Atalhos no
loop �� ��
�����������������������������
www.linuxmagazine.com.br edição 06 89
��������������������������������������������������������������������������������
���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim
Listagem 3: erreeme.sh
$ cat erreeme.sh then
#!/bin/bash echo "$Arq nao existe."
# Erro=3
# Salvando cópia de um arquivo antes de removê-lo continue # Volta para o comando for
fi
# Tem de ter um ou mais arquivos a remover
if [ $# -eq 0 ] # Cmd. dirname informa nome do dir de $Arq
then DirOrig=`dirname $Arq`
echo "Erro -> Uso: erreeme arq [arq] ... [arq]" # Verifica permissão de gravacao no diretório
echo "O uso de metacaracteres e’ permitido. Ex.U if [ ! -w $DirOrig ]
erreeme arq*" then
exit 1 echo "Sem permissão no diretorio de $Arq"
fi Erro=4
continue # Volta para o comando for
# Variável do sistema que contém o nome do usuário. fi
MeuDir="/tmp/$LOGNAME"
# Se não existir o meu diretório sob o /tmp... # Se estou "esvaziando a lixeira"...
if [ ! -d $MeuDir ] if [ "$DirOrig" = "$MeuDir" ]
then then
mkdir $MeuDir # Vou criá-lo echo "$Arq ficara sem copia de seguranca"
fi rm -i $Arq # Pergunta antes de remover
# Será que o usuário removeu?
# Se não posso gravar no diretório... [ -f $Arq ] || echo "$Arquivo removido"
if [ ! -w $MeuDir ] continue
then fi
echo "Impossivel salvar arquivos em $MeuDir. U
Mude as permissões..." # Guardo no fim do arquivo o seu diretório originalU
exit 2 para usá-lo em um script de undelete
fi cd $DirOrig
pwd >> $Arq
# Variável que indica o cod. de retorno do programa mv $Arq $MeuDir # Salvo e removo
Erro=0 echo "$Arq removido"
# Um for sem o in recebe os parametros passados done
for Arq
do # Passo eventual número do erro para o código
# Se este arquivo não existir... # de retorno
if [ ! -f $Arq ] exit $Erro
um continue, para que a seqüência volte faça-o em casa e me traga para dis- um email para julio.neves@gmail.
para o loop do for de forma a receber cutirmos no nosso próximo encontro com. Agora chega de papo que eu já
outros arquivos. aqui no boteco. estou de goela seca de tanto falar. Me
Quando você está no Windows (com – Poxa, mas nesse eu acho que vou dan- acompanha no próximo chope ou já
perdão da má palavra) e tenta remover çar, pois não sei nem como começar... vai sair correndo para fazer o script
aquele monte de lixo com nomes esqui- – Cara, este programa é como tudo que passei?
sitos como HD04TG.TMP, se der erro o que se faz em Shell: extrema- – Deixa eu pensar um pouco...
em um dos arquivos os outros não são mente fácil. É para ser feito em, no – Chico, traz mais um chope enquanto
removidos, não é? Então, o continue foi máximo, 10 linhas. Não se esqueça ele pensa!
usado para evitar que uma improprie- de que o arquivo está salvo em /tmp/
dade dessas ocorra, isto é, mesmo que $LOGNAME e que sua última linha é Julio Cezar Neves é Analista de
SOBRE O AUTOR
dê erro na remoção de um arquivo, o o diretório em que ele residia antes Suporte de Sistemas desde 1969 e tra-
programa continuará removendo os de ser “removido”. Também não se balha com Unix desde 1980, quando
outros que foram passados. esqueça de criticar se foi passado o participou do desenvolvimento do
– Eu acho que a esta altura você deve nome do arquivo a ser removido. SOX, um sistema operacional similar
estar curioso para ver o programa – É eu vou tentar, mas sei não... ao Unix produzido pela Cobra Com-
putadores. Pode ser contatado no
que restaura o arquivo removido, não – Tenha fé, irmão, eu tô te falando que
e-mail julio.neves@gmail.com
é? Pois então aí vai vai um desafio: é mole! Qualquer dúvida é só passar
�����������������������������
90 edição 06 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
Linux User Papo de botequim
Papo de Botequim
Curso de Shell Script
Parte VII
C
umequié, rapaz! Derreteu os pen- – Peraí, deixa eu ver se entendi o que você – É, tô vendo que o bichinho do shell te
samentos para fazer o scriptzinho fez: você coloca na variável Dir a última pegou. Vamos ver como ler dados, mas
que eu te pedi? linha do arquivo a ser restaurado, em antes vou te mostrar um comando que
– É, eu realmente tive de colocar muita nosso caso /tmp/$LOGNAME/$1 (onde te dá todas as ferramentas para formatar
pensação na tela preta, mas acho que $LOGNAME é o nome do usuário logado, uma tela de entrada de dados.
fi nalmente consegui! Bem, pelo menos e $1 é o primeiro parâmetro que você
nos testes que fi z a coisa funcionou, passou ao script), já que foi lá que arma- O comando tput
mas você tem sempre que botar chifres zenamos o nome e caminho originais do O principal uso desse comando é o posi-
em cabeça de cachorro! arquivo antes de movê-lo para o diretório cionamento do cursor na tela. Alguns
– Não é bem assim. É que programar em (defi nido na variável Dir). O comando parâmetros podem não funcionar se o
Shell Script é muito fácil, mas o que é grep -v apaga essa linha, restaurando modelo de terminal defi nido pela vari-
realmente importante são as dicas e o arquivo ao estado original, e o manda ável de ambiente $TERM não suportá-los.
macetes que não são triviais. As cor- de volta pra onde ele veio. A última linha A tabela 1 apresenta apenas os principais
reções que faço são justamente para o apaga da “lixeira”. Sensacional! Impe- parâmetros e os efeitos resultantes, mas
mostrá-los. Mas vamos pedir dois cho- cável! Nenhum erro! Viu? Você já está existem muito mais deles. Para saber tudo
pes enquanto dou uma olhadela no teu pegando as manhas do shell! sobre o tput, veja a referência [1].
script lá na listagem 1. Aê Chico, traz – Então vamos lá, chega de lesco-lesco Vamos fazer um programa bem besta
dois chopes! E não se esqueça que um e blá-blá-blá, sobre o quê nós vamos e fácil para ilustrar melhor o uso desse
deles é sem colarinho! falar hoje? comando. É uma versão do famigerado
“Alô Mundo”, só que dessa vez a frase
Listagem 1 – restaura.sh será escrita no centro da tela e em vídeo
reverso. Depois disso, o cursor voltará para
01 #!/bin/bash
a posição original. Veja a listagem 2.
02 #
Como o programa já está todo comen-
03 # Restaura arquivos deletados via erreeme
tado, acho que a única linha que precisa de
04 #
explicação é a 8, onde criamos a variável
05
Coluna. O estranho ali é aquele número
06 if [ $# -eq 0 ]
9, que na verdade indica o tamanho da
07 then
cadeia de caracteres que vou escrever na
08 echo "Uso: $0 <Nome do arquivo a ser restaurado>"
tela. Dessa forma, este programa somente
09 exit 1
conseguiria centralizar cadeias de 9 carac-
10 fi
teres, mas veja isto:
11 # Pega nome do arquivo/diretório original na última linha
12 Dir='tail -1 /tmp/$LOGNAME/$1'
$ var=Papo
13 # O grep -v exclui a última linha e recria o arquivo com o diretório
$ echo ${#var}
14 # e nome originalmente usados
4
15 grep -v $Dir /tmp/$LOGNAME/$1 > $Dir/$1
$ var="Papo de Botequim"
16 # Remove o arquivo que já estava moribundo
$ echo ${#var}
17 rm /tmp/$LOGNAME/$1
16
eu te apresentei quando falávamos do para que o \n fosse entendido como $ read -t2 -p "Digite seu nome completo: U
comando for, será que ainda acredita uma quebra de linha (new line) e não " Nom || echo 'Eita moleza!'
nisso? Vamos testar: como um literal. Sob o Bash exis- Digite seu nome completo: Eita moleza!
tem diversas opções do read que ser- $ echo $Nom
$ oIFS="$IFS" vem para facilitar a sua vida. Veja
$ IFS=: a tabela 2. O exemplo acima foi uma brincadeira,
$ read var1 var2 var3 E agora direto aos exemplos curtos pois eu só tinha 2 segundos para digitar o
Papo de Botequim para demonstrar estas opções. Para ler meu nome completo e mal tive tempo de
$ echo $var1 um campo “Matrícula”: teclar um J (aquele colado no Eita), mas
Papo de Botequim ele serviu para mostrar duas coisas:
$ echo $var2 # -n não salta linha P 1) O comando após o par de barras verti-
$ echo $var3 $ echo -n "Matricula: "; read Mat cais (o ou – or – lógico, lembra-se?) será
$ read var1 var2 var3 Matricula: 12345 executado caso a digitação não tenha
Papo:de:Botequim $ echo $Mat sido concluída no tempo estipulado;
$ echo $var1 12345 P 2) A variável Nom permaneceu vazia. Ela
Papo só receberá um valor quando o ENTER
$ echo $var2 Podemos simplificar as coisas usando for teclado.
de a opção -p:
$ echo $var3 $ read -sp “Senha: “
Botequim $ read -p "Matricula: " Mat Senha: $ echo $REPLY
$ IFS="$oIFS" Matricula: 12345 segredo :)
$ echo $Mat
Viu? eu estava furado! O read lê uma 12345 Aproveitei um erro no exemplo ante-
lista, assim como o for, separada pelos rior para mostrar um macete. Quando
caracteres da variável $IFS. Veja como E podemos ler apenas uma quantidade escrevi a primeira linha, esqueci de colo-
isso pode facilitar a sua vida: pré-determinada de caracteres: car o nome da variável que iria receber a
senha e só notei isso quando ia escrevê-
$ grep julio /etc/passwd $ read -n5 -p"CEP: " Num ; read -n3 U la. Felizmente a variável $REPLY do Bash
julio:x:500:544:Julio C. Neves - 7070:U -p- Compl contém a última seqüência de caracteres
/home/julio:/bin/bash CEP: 12345-678$ digitada – e me aproveitei disso para não
$ oIFS="$IFS" # Salva o IFS antigo. $ echo $Num perder a viagem. Teste você mesmo o que
$ IFS=: 12345 acabei de fazer.
$ grep julio /etc/passwd | read lname U $ echo $Compl O exemplo que dei, na verdade, era para
lixo uid gid coment home shell 678 mostrar que a opção -s impede que o que
$ echo -e "$lname\n$uid\n$gid\n$comentU está sendo digitado seja mostrado na tela.
\n$home\n$shell" No exemplo acima executamos duas Como no exemplo anterior, a falta do new
julio vezes o comando read: um para a pri- line fez com que o prompt de comando
500 meira parte do CEP e outra para o seu ($) permanecesse na mesma linha.
544 complemento, deste modo formatando Agora que sabemos ler da tela, vejamos
Julio C. Neves - 7070 a entrada de dados. O cifrão ($) logo como se lêem os dados dos arquivos.
/home/julio após o último algarismo digitado é
/bin/bash necessário porque o read não inclui Lendo arquivos
$ IFS="$oIFS" # Restaura o IFS por padrão um caractere new line Como eu já havia lhe dito, e você deve se
implícito, como o echo. lembrar, o while testa um comando e exe-
Como você viu, a saída do grep foi Para ler só durante um determinado cuta um bloco de instruções enquanto esse
redirecionada para o comando read, limite de tempo (também conhecido comando for bem sucedido. Ora, quando
que leu todos os campos de uma só como time out): você está lendo um arquivo para o qual
tacada. A opção -e do echo foi usada você tem permissão de leitura, o read só
será mal sucedido quando alcançar o EOF
Tabela 2: Opções do read (End Of File – Fim do Arquivo). Portanto,
podemos ler um arquivo de duas maneiras.
Opção Ação A primeira é redirecionando a entrada do
-p prompt Escreve “prompt” na tela antes de fazer a leitura arquivo para o bloco while, assim:
-n num Lê até num caracteres
while read Linha
-t seg Espera seg segundos para que a leitura seja concluída do
-s Não exibe na tela os caracteres digitados. echo $Linha
done < arquivo
A segunda é redirecionando a saída Como você viu, o script lista suas pró- que vou mostrar com um exemplo prático.
de um cat para o while, da seguinte prias linhas com um sinal de menos (-) Suponha que você queira listar um arquivo
maneira: antes e outro depois de cada uma e, no e quer que a cada dez registros essa listagem
final, exibe o conteúdo da variável $Ultimo. pare para que o operador possa ler o con-
cat arquivo | Repare, no entanto, que o conteúdo dessa teúdo da tela, e que ela só continue depois
while read Linha variável permanece vazio. Ué, será que de o operador pressionar qualquer tecla.
do a variável não foi atualizada? Foi, e isso Para não gastar papel (da Linux Magazine),
echo $Linha pode ser comprovado porque a linha echo vou fazer essa listagem na horizontal. Meu
done "-$Ultimo-" lista corretamente as linhas. arquivo (numeros) tem 30 registros com
Então por que isso aconteceu? números seqüenciais. Veja:
Cada um dos processos tem suas van- Como eu disse, o bloco de instruções
tagens e desvantagens. O primeiro é mais redirecionado pelo pipe (|) é executado $ seq 30 > numeros
rápido e não necessita de um subshell em um subshell e, lá, as variáveis são $ cat 10porpag.sh
para assisti-lo mas, em contrapartida, o atualizadas. Quando esse subshell ter- #!/bin/bash
redirecionamento fica pouco visível em mina, as atualizações das variáveis vão # Programa de teste para escrever
um bloco de instruções grande, o que para as profundezas do inferno junto com # 10 linhas e parar para ler
por vezes prejudica a visualização do ele. Repare que vou fazer uma pequena # Versão 1
código. O segundo processo traz a van- mudança no script, passando o arquivo while read Num
tagem de que, como o nome do arquivo por redirecionamento de entrada (<), e do
está antes do while, a visualização do as coisas passarão a funcionar na mais let ContLin++ # Contando...
código é mais fácil. Entretanto, o Pipe perfeita ordem: # -n para não saltar linha
(|) chama um subshell para interpretá-lo, echo -n "$Num "
tornando o processo mais lento e pesado. $ cat redirread.sh ((ContLin % 10)) > /dev/null || read
Para ilustrar o que foi dito, veja os exem- #!/bin/bash done < numeros
plos a seguir: # redirread.sh
# Exemplo de read passando o arquivo Na tentativa de fazer um programa
$ cat readpipe.sh # por um pipe. genérico criamos a variável $ContLin (na
#!/bin/bash Ultimo="(vazio)" vida real, os registros não são somente
# readpipe.sh # Passa o script ($0) para o while números seqüenciais) e, quando testamos
# Exemplo de read passando um arquivo while read Linha se o resto da divisão era zero, mandamos
# por um pipe. do a saída para /dev/null, pra que ela não
Ultimo="(vazio)" Ultimo="$Linha" apareça na tela. Mas quando fui executar
# Passa o script ($0) para o while echo "-$Ultimo-" o programa descobri a seguinte zebra:
cat $0 | while read Linha done < $0
do echo "Acabou, Último=:$Ultimo:" $ 10porpag.sh
Ultimo="$Linha" 1 2 3 4 5 6 7 8 9 10 12 13 14 15 16 17 U
echo "-$Ultimo-" Veja como ele roda perfeitamente: 18 19 20 21 23 24 25 26 27 28 29 30
done
echo "Acabou, Último=:$Ultimo:" $ redirread.sh Repare que faltou o número 11 e a lista-
-#!/bin/bash- gem não parou no read. Toda a entrada do
Vamos ver o resultado de sua execução: -# redirread.sh- loop estava redirecionada para o arquivo
-# Exemplo de read passando o arquivo numeros e a leitura foi feita em cima desse
$ readpipe.sh -# por um pipe.- arquivo, perdendo o número 11. Vamos
-#!/bin/bash- -- mostrar como deveria ficar o código para
-# readpipe.sh- -Ultimo="(vazio)"- que ele passe a funcionar a contento:
-# Exemplo de read passando um arquivo -while read Linha-
-# por um pipe.- -do- $ cat 10porpag.sh
-- -Ultimo="$Linha"- #!/bin/bash
-Ultimo="(vazio)"- -echo "-$Ultimo-"- # Programa de teste para escrever
-# Passa o script ($0) para o while- -# Passa o script ($0) para o while- # 10 linhas e parar para ler - Versão 2
-cat $0 | - -done < $0- while read Num
-while read Linha- -echo "Acabou, Último=:$Ultimo:"- do
-do- Acabou, Último=:echo "Acabou,U let ContLin++ # Contando...
-Ultimo="$Linha"- Último=:$Ultimo:": # -n para não saltar linha
-echo "-$Ultimo-"- echo -n "$Num "
-done- Bem, amigos da Rede Shell, para finali- ((ContLin % 10)) > /dev/null || read U
-echo "Acabou, Último=:$Ultimo:"- zar a aula sobre o comando read só falta < /dev/tty
Acabou, Último=:(vazio): mais um pequeno e importante macete done < numeros
Repare que agora a entrada do read foi # 10 linhas e parar para ler – Bem meu amigo, por hoje é só porque
redirecionada para /dev/tty, que nada # Versão 3 acho que você já está de saco cheio…
mais é senão o terminal corrente, expli- clear – Num tô não, pode continuar…
citando desta forma que aquela leitura while read Num – Se você não estiver eu estou… Mas já
seria feita do teclado e não do arquivo do que você está tão empolgado com o shell,
numeros. É bom realçar que isso não # Contando... vou te deixar um serviço bastante sim-
acontece somente quando usamos o redi- ((ContLin++)) ples para você melhorar a sua cdteca:
recionamento de entrada; se tivéssemos echo "$Num" Monte toda a tela com um único echo
usado o redirecionamento via pipe (|), o ((ContLin % (`tput lines` - 3))) || e depois posicione o cursor à frente de
mesmo teria ocorrido. Veja agora a exe- { cada campo para receber o valor que
cução do script: # para ler qualquer caractere será digitado pelo operador.
read -n1 -p"Tecle Algo " < /dev/tty Não se esqueçam que, em caso de
$ 10porpag.sh # limpa a tela após a leitura qualquer dúvida ou falta de companhia
1 2 3 4 5 6 7 8 9 10 clear para um chope é só mandar um e-mail
11 12 13 14 15 16 17 18 19 20 } para julio.neves@gmail.com. Vou aproveitar
21 22 23 24 25 26 27 28 29 30 done < numeros também para fazer uma propaganda:
digam aos amigos que quem estiver a
Isso está quase bom, mas ainda falta A mudança substancial feita neste exem- fim de fazer um curso “porreta” de pro-
um pouco para ficar excelente. Vamos plo é com relação à quebra de página, já gramação em shell deve mandar um e-
melhorar o exemplo para que você o que ela é feita a cada quantidade-de-linhas- mail para julio.neves@tecnohall.com.br para
reproduza e teste (mas, antes de testar, da-tela (tput lines) menos (-) três, isto informar-se. Até mais!
aumente o número de registros em nume- é, se a tela tem 25 linhas, o programa
ros ou reduza o tamanho da tela, para listará 22 registros e parará para leitura. Informações
que haja quebra de linha). No comando read também foi feita uma [1] Página oficial do Tput: http://www.cs.utah.edu/
alteração, inserido o parâmetro -n1 para dept/old/texinfo/tput/tput.html#SEC4
$ cat 10porpag.sh ler somente um caractere qualquer, não
[2] Página oficial do Bash: http://www.gnu.org/
#!/bin/bash necessariamente um ENTER, e a opção software/bash/bash.html
# Programa de teste para escrever -p para exibir uma mensagem.
Papo de Botequim
Curso de Shell Script
Parte VIII
E
aê, cara, tudo bem? posicionais ($1, $2, …, $n). Todas as regras que se aplicam
– – Tudo beleza! Eu queria te mostrar o que fi z mas já sei
que você vai querer molhar o bico primeiro, né?
à passagem de parâmetros para programas também valem para
funções, mas é muito importante realçar que os parâmetros
– Só pra te contrariar, hoje não quero. Vai, mostra logo aí passados para um programa não se confundem com aqueles
o que você fez. que são passados para suas funções. Isso significa, por exem-
– Poxa, o exercício que você passou é muito grande. Dá uma plo, que o $1 de um script é diferente do $1 de uma de suas
olhada na listagem 1 e vê como eu resolvi: funções internas.
– É, o programa tá legal, tá todo estruturadinho, mas gosta- Repare que as variáveis $Msg, $TamMsg e $Col são de uso
ria de fazer alguns poucos comentários: só para relembrar, restrito dessa rotina e, por isso, foram criadas como variáveis
as seguintes construções: [ ! $Album ] && e [ $Musica ] locais. A razão é simplesmente a economia de memória, já que
|| representam a mesma coisa, isto é: no caso da primeira, ao sair da rotina elas serão devidamente detonadas, coisa que
testamos se a variável $Album não (!) tem nada dentro, então não aconteceria se eu não tivesse usado esse artifício.
(&&)… Na segunda, testamos se $Musica tem algum dado, A linha de código que cria a variável local Msg concatena ao
senão (||)… texto recebido ($1) um parêntese, a resposta padrão ($2) em
Se você reclamou do tamanho do programa, é porque ainda caixa alta, uma barra, a outra resposta ($3) em caixa baixa
não te dei algumas dicas. Repare que a maior parte do script e fi naliza fechando o parêntese. Uso essa convenção para, ao
é para mostrar mensagens centraliza-
das na penúltima linha da tela. Repare Listagem 1: musinc5.sh
ainda que algumas mensagens pedem
01 $ cat musinc5.sh
um S ou um N como resposta e outras
02 #!/bin/bash
são só de advertência. Isso é um caso
03 # Cadastra CDs (versao 5)
típico que pede o uso de funções, que
04 #
seriam escritas somente uma vez e
05 clear
executadas em diversos pontos do
06 LinhaMesg=$((`tput lines` - 3)) # Linha onde serão mostradas as msgs para o operador
script. Vou montar duas funções para
07 TotCols=$(tput cols) # Qtd colunas da tela para enquadrar msgs
resolver esses casos e vamos incor-
08 echo “
porá-las ao seu programa para ver o
Inclusão de Músicas
resultado fi nal.
======== == =======
– Chico! Agora traz dois chopes, um
Título do Álbum:
sem colarinho, para me dar inspira-
| Este campo foi
ção. E você, de olho na listagem 2.
Faixa: < criado somente para
Como podemos ver, uma função é
| orientar o preenchimento
defi nida quando digitamos nome_da_
Nome da Música:
função () e todo o seu corpo está
Intérprete:” # Tela montada com um único echo
entre chaves ({}). Já conversamos aqui
09 while true
no boteco sobre passagem de parâ-
10 do
metros e as funções os recebem da
11 tput cup 5 38; tput el # Posiciona e limpa linha ➟
mesma forma, isto é, são parâmetros
Conselho de amigo: crie um arquivo e anexe a ele cada fun- diretório /home. Só que assim que a execução do script termi-
ção nova que você criar. Ao final de algum tempo você terá nou, o sub-shell foi para o beleléu e, com ele, todo o ambiente
uma bela biblioteca de funções que lhe poupará muito tempo criado. Agora preste atenção no exemplo abaixo e veja como a
de programação. coisa muda de figura:
09 fi
Sistemas desde 1969 e trabalha com Unix
10 IFS=”
11 :” desde 1980, quando participou do desen-
12 for ArtMus in $(cut -f2 -d^ musicas) volvimento do SOX, um sistema operacio-
13 do nal similar ao Unix produzido pela Cobra
14 echo “$ArtMus” | grep -i “^$*~” > /dev/null && echo $ArtMus | cut -f2 -d~ Computadores. Pode ser contatado no
15 done e-mail julio.neves@gmail.com
Papo de Botequim
Curso de Shell Script
Parte IX
T
á bom, já sei que você vai querer chope antes de começar, Bom, a resposta é "mais ou menos". Com estes comandos
mas tô tão a fi m de te mostrar o que fi z que já vou pedir a você escreve 90% do que precisa, porém se precisar escrever
rodada enquanto isso. Aê Chico, manda dois! O dele é sem algo formatado eles lhe darão muito trabalho. Para formatar a
colarinho pra não deixar cheiro ruim nesse bigodão… saída veremos agora uma instrução muito mais interessante,
Enquanto o chope não chega, deixa eu te lembrar o que a printf. Sua sintaxe é a seguinte:
você me pediu na edição passada: era para refazer
o programa listartista com a tela formatada e exe- Listagem 1: mandamsg.func e pergunta.func
cução em loop, de forma que ele só termine quando mandamsg.func
receber um [ENTER] sozinho como nome do artista. 01 # A função recebe somente um parâmetro
Eventuais mensagens de erro e perguntas feitas ao 02 # com a mensagem que se deseja exibir.
usuário deveriam ser mostradas na antepenúltima 03 # Para não obrigar o programador a passar
linha da tela, utilizando para isso as rotinas externas 04 # a msg entre aspas, usaremos $* (todos
mandamsg.func e pergunta.func que desenvolvemos 05 # os parâmetro, lembra?) e não $1.
06 Msg="$*"
durante nosso papo na edição passada.
07 TamMsg=${#Msg}
Primeiramente eu dei uma encolhida nas rotinas
08 Col=$(((TotCols - TamMsg) / 2)) # Centra msg na linha
mandamsg.func e pergunta.func, que ficaram como 09 tput cup $LinhaMesg $Col
na listagem 1. E na listagem 2 você tem o “grandão”, 10 read -n1 -p "$Msg "
nossa versão refeita do listaartista.
– Puxa, você chegou com a corda toda! Gostei da pergunta.func
forma como você resolveu o problema e estruturou 01 # A função recebe 3 parâmetros na seguinte ordem:
02 # $1 - Mensagem a ser mostrada na tela
o programa. Foi mais trabalhoso, mas a apresenta-
03 # $2 - Valor a ser aceito com resposta padrão
ção ficou muito legal e você explorou bastante as
04 # $3 - O outro valor aceito
opções do comando tput. Vamos testar o resultado
05 # Supondo que $1=Aceita?, $2=s e $3=n, a linha
com um álbum do Emerson, Lake & Palmer que 06 # abaixo colocaria em Msg o valor "Aceita? (S/n)"
tenho cadastrado. 07 Msg="$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)"
08 TamMsg=${#Msg}
Envenenando a escrita 09 Col=$(((TotCols - TamMsg) / 2)) # Centraliza msg na linha
Ufa! Agora você já sabe tudo sobre leitura de dados, 10 tput cup $LinhaMesg $Col
mas quanto à escrita ainda está apenas engatinhando. 11 read -n1 -p "$Msg " SN
12 [ ! $SN ] && SN=$2 # Se vazia coloca default em SN
Já sei que você vai me perguntar: “Ora, não é com o
13 SN=$(echo $SN | tr A-Z a-z) # A saída de SN será em minúscula
comando echo e com os redirecionamentos de saída
14 tput cup $LinhaMesg $Col; tput el # Apaga msg da tela
que se escreve dados?”.
Listagem 2: listaartista
printf formato [argumento...] $ cat listartista3.sh
01 #!/bin/bash
Onde formato é uma cadeia 02 # Dado um artista, mostra as suas musicas
03 # versao 3
de caracteres que contém três
04 LinhaMesg=$((`tput lines` - 3)) # Linha onde as msgs serão mostradas
tipos de objeto: caracteres 05 TotCols=$(tput cols) # Qtd de colunas na tela para enquadrar msgs
simples, caracteres para es- 06 clear
pecificação de formato (ou de 07 echo "
+–––––––––––––––––-----------------------------------+
controle) e seqüência de esca-
| Lista Todas as Músicas de um Determinado Artista |
pe no padrão da linguagem | ===== ===== == ======= == == =========== ======= |
C. argumento é a cadeia de | |
caracteres a ser impressa sob | Informe o Artista: |
+––––––––––––––––----------------------------------–-+"
o controle de formato.
08 while true
Cada um dos caracteres uti- 09 do
lizados é precedido pelo ca- 10 tput cup 5 51; tput ech 31 # ech=Erase chars (31 para não apagar barra vertical)
racter % e, logo a seguir, vem 11 read Nome
a especificação de formato de 12 if [ ! "$Nome" ] # $Nome esta vazio?
13 then
acordo com a tabela 1.
14 . pergunta.func "Deseja Sair?" s n
As seqüências de escape 15 [ $SN = n ] && continue
padrão da linguagem C são 16 break
sempre precedidas pelo ca- 17 fi
18 fgrep -iq "^$Nome~" musicas || # fgrep não interpreta ^ como expressão regular
ractere contra-barra (\). As
19 {
reconhecidas pelo comando 20 . mandamsg.func "Não existe música desse artista"
printf são as da tabela 2. 21 continue
Não acaba por aí não! Tem 22 }
23 tput cup 7 29; echo '| |'
muito mais coisa sobre essa
24 LinAtual=8
instrução, mas como esse é 25 IFS="
um assunto muito cheio de 26 :"
detalhes e, portanto, chato 27 for ArtMus in $(cut -f2 -d^ musicas) # Exclui nome do album
28 do
para explicar e pior ainda para
29 if echo "$ArtMus" | grep -iq "^$Nome~"
ler ou estudar, vamos passar 30 then
direto aos exemplos com co- 31 tput cup $LinAtual 29
mentários. Veja só: 32 echo -n '| "
33 echo $ArtMus | cut -f2 -d~
34 tput cup $LinAtual 82
$ printf "%c" "1 caracter"
35 echo '|'
1$ 36 let LinAtual++
37 if [ $LinAtual -eq $LinhaMesg ]
Errado! Só listou 1 caractere 38 then
39 . mandamsg.func "Tecle Algo para Continuar..."
e não saltou linha ao final
40 tput cup 7 0; tput ed # Apaga a tela a partir da linha 7
41 tput cup 7 29; echo '| |'
$ printf "%c\n" "1 caracter" 42 LinAtual=8
1 43 fi
44 fi
45 done
Saltou linha mas ainda não 46 tput cup $LinAtual 29; echo '| |'
listou a cadeia inteira 47 tput cup $((++LinAtual)) 29
48 read -n1 -p "+–––--Tecle Algo para Nova Consulta––––+"
49 tput cup 7 0; tput ed # Apaga a tela a partir da linha 7
$ printf "%c caractere\n" 1
50 done
1 caractere ➟
Tabela 1: Formatos de caractere (%) significa o tamanho que a cadeia terá O bc devolveu duas casas decimais e
após a execução do comando. Vamos ver o printf colocou o zero à direita. O co-
Caractere A expressão será impressa como:
c Caractere simples a seguir mais alguns exemplos. Os co- mando a seguir:
d Número no sistema decimal mandos abaixo:
e Notação científica exponencial $ printf "%o\n" 10
f Número com ponto decimal (float) $ printf "%d\n" 32 12
g O menor entre os formatos %e e %f com 32
omissão dos zeros não significativos $ printf "%10d\n" 32 Converteu o valor 10 para base octal.
o Número no sistema octal 32 Para melhorar experimente:
s Cadeia de caracteres
x Número no sistema hexadecimal
preenchem a string com espaços em $ printf "%03o\n" 27
% Imprime um %. Não há nenhum
tipo de conversão branco à esquerda (oito espaços mais dois 033
caracteres, 10 dígitos), não com zeros. Já
no comando abaixo: Assim a conversão fica com mais jeito
Opa, essa é a forma correta! O %c rece- de octal, né?. O que este aqui faz?
beu o valor 1, como queríamos: $ printf "%04d\n" 32
0032 $ printf "%s\n" Peteleca
$ a=2 Peteleca
$ printf "%c caracteres\n" $a O 04 após % significa “formate a string $ printf "%15s\n" Peteleca
2 caracteres em quatro dígitos, com zeros à esquerda Peteleca
se necessário”. No comando:
O %c recebeu o valor da variável $a. Imprime Peteleca com 15 caracteres.
$ printf "%e\n" $(echo "scale=2 ; 100/6" | bc) A cadeia de caracteres é preenchida com
$ printf "%10c caracteres\n" $a 1.666000e+01 espaços em branco à esquerda. Já no co-
2 caracteres mando:
$ printf "%10c\n" $a caracteres O padrão do %e é seis casas decimais.
2 Já no comando: $ printf "%-15sNeves\n" Peteleca
c Peteleca Neves
$ printf "%.2e\n" `echo "scale=2 ; 100/6" | bc`
Repare que, nos dois últimos exemplos, 1.67e+01 O menos (-) colocou espaços em branco
em virtude do uso do %c, só foi listado à direita de Peteleca até completar os 15
um caractere de cada cadeia de caracteres O .2 especificou duas casas decimais. caracteres pedidos. E o comando abaixo,
passada como parâmetro. O valor 10 à Observe agora: o que faz?
frente do c não significa 10 caracteres. Um
número seguindo o sinal de percentagem $ printf "%f\n" 32.3 $ printf "%.3s\n" Peteleca
32.300000 Pet
Tabela 2: Seqüências de escape
O padrão do %f é seis casas decimais. O .3 manda truncar a cadeia de ca-
Seqüência Efeito
E no comando: racteres após as três primeiras letras. E
a Soa o beep
b Volta uma posição (backspace) o comando a seguir:
f Salta para a próxima página $ printf "%.2f\n" 32.3
lógica ( form feed) 32.30 $ printf "%10.3sa\n" Peteleca
n Salta para o início da linha se- Peta Pet
guinte (line feed) O .2 especificou duas casas decimais.
r Volta para o início da linha cor-
Agora observe: Imprime a cadeia com 10 caracteres,
rente (carriage return)
truncada após os três primeiros, conca-
t Avança para a próxima marca de
tabulação $ printf "%.3f\n" `echo "scale=2 ; 100/6" | bc` tenada com o caractere a (após o s). E
33.330 esse comando a seguir, o que faz?
$ printf “EXEMPLO %x\n” 45232 trabalho, principalmente em instalações (1 minuto). A cada intervalo o Shell fará
EXEMPLO b0b0 com estrutura de diretórios em múltiplos a verificação antes de exibir o próximo
níveis. Veja o exemplo a seguir: prompt primário ($PS1). Se essa variável
Ele transformou o número 45232 para estiver sem valor ou com um valor menor
hexadecimal (b0b0), mas os zeros não $ echo $CDPATH ou igual a zero, a busca por novas men-
combinam com o resto. Experimente: .:..:~:/usr/local sagens não será efetuada.
$ pwd PATH » Caminhos que serão pesquisa-
$ printf “EXEMPLO %X\n” 45232 /home/jneves/LM dos para tentar localizar um arquivo espe-
EXEMPLO B0B0 $ cd bin cificado. Como cada script é um arquivo,
$ pwd caso use o diretório corrente (.) na sua
Assim disfarçou melhor! (repare no X /usr/local/bin variável $PATH, você não necessitará usar
maiúsculo). Pra terminar, que tal o co- o comando ./scrp para que o script scrp
mando abaixo: Como /usr/local estava na minha seja executado. Basta digitar scrp. Este
variável $CDPATH e não existia o diretório é o modo que prefiro.
$ printf “%X %XL%X\n” 49354 192 10 bin em nenhum dos seus antecessores (., PIPESTATUS » É uma variável do tipo
C0CA C0LA .. e ~), o comando cd foi executado tendo vetor (array) que contém uma lista de
como destino /usr/local/bin. valores de códigos de retorno do último
Este aí não é marketing e é bastante HISTSIZE » Limita o número de ins- pipeline executado, isto é, um array que
completo, veja só como funciona: truções que cabem dentro do arquivo de abriga cada um dos $? de cada instrução
O primeiro %X converteu 49354 em he- histórico de comandos (normalmente do último pipeline. Para entender melhor,
xadecimal, resultando em C0CA (leia-se .bash_history, mas na verdade é o que veja o exemplo a seguir:
“cê”, “zero”, “cê” e “a”). Em seguida veio está indicado na variável $HISTFILE). Seu
um espaço em branco seguido por outro valor padrão é 500. $ who
%XL. O %X converteu o 192 dando como HOSTNAME » O nome do host corrente jneves pts/0 Apr 11 16:26 (10.2.4.144)
resultado C0 que com o L fez C0L. E final- (que também pode ser obtido com o co- jneves pts/1 Apr 12 12:04 (10.2.4.144)
mente o último parâmetro %X transformou mando uname -n). $ who | grep ^botelho
o número 10 na letra A. LANG » Usada para determinar o idioma $ echo ${PIPESTATUS[*]}
Conforme vocês podem notar, a instru- falado no país (mais especificamente ca- 0 1
ção é bastante completa e complexa. Ain- tegoria do locale). Veja um exemplo:
da bem que o echo resolve quase tudo... Neste exemplo mostramos que o usu-
Acertei em cheio quando resolvi expli- $ date ário botelho não estava “logado”, em
car o printf através de exemplos, pois Thu Apr 14 11:54:13 BRT 2005 seguida executamos um pipeline que
não saberia como enumerar tantas regri- $ LANG=pt_BR date procurava por ele. Usa-se a notação [*]
nhas sem tornar a leitura enfadonha. Qui Abr 14 11:55:14 BRT 2005 em um array para listar todos os seus
elementos; dessa forma, vimos que a pri-
Principais variáveis do Shell LINENO » O número da linha do script meira instrução (who) foi bem-sucedida
O Bash possui diversas variáveis que ou função que está sendo executada. (código de retorno 0) e a seguinte (grep)
servem para dar informações sobre Seu uso principal é mostrar mensagens não (código de retorno 1).
o ambiente ou alterá-lo. São muitas e de erro juntamente com as variáveis $0 PROMPT_COMMAND » Se esta variável re-
não pretendo mostrar todas elas, mas (nome do programa) e $FUNCNAME (nome ceber o nome de um comando, toda vez
uma pequena parte pode lhe ajudar na da função em execução). que você teclar um [ENTER] sozinho no
elaboração de scripts. Veja a seguir as LOGNAME » Esta variável armazena o prompt principal ($PS1), esse comando
principais delas: nome de login do usuário . será executado. É muito útil quando você
CDPATH » Contém os caminhos que MAILCHECK » Especifica, em segundos, a precisa repetindo constantemente uma
serão pesquisados para tentar localizar freqüência com que o Shell verifica a pre- determinada instrução.
um diretório especificado. Apesar dessa sença de correspondência nos arquivos PS1 » É o prompt principal. No “Papo
variável ser pouco conhecida, seu uso indicados pela variáveis $MAILPATH ou de Botequim” usamos os padrões $ para
deve ser incentivado por poupar muito $MAIL. O tempo padrão é de 60 segundos usuário comum e # para root, mas é mui-
${cadeia/%subcad1/subcad2} Se subcad1 combina com o fim de cadeia, então é trocado por subcad2 “${Passaro/%quero/não}
Como diz o nordestino - quero não
$ echo $cadeia $ echo ${cadeia/*po/Conversa} – Agora já chega, o papo hoje foi chato
Papo de Botequim Conversa de Botequim porque teve muita decoreba, mas o que
$ echo ${cadeia%' '*} $ echo ${cadeia/????/Conversa} mais importa é você ter entendido o
Papo de Conversa de Botequim que te falei. Quando precisar, consulte
$ echo ${cadeia%%' '*} estes guardanapos onde rabisquei as
Papo Trocando todas as ocorrências de uma dicas e depois guarde-os para consultas
subcadeia por outra. O comando: futuras. Mas voltando à vaca fria: tá
Para trocar primeira ocorrência de uma na hora de tomar outro e ver o jogo do
subcadeia em uma cadeia por outra: $ echo ${cadeia//o/a} Mengão. Pra próxima vez vou te dar
Papa de Batequim moleza e só vou cobrar o seguinte: pe-
$ echo $cadeia gue a rotina pergunta.func (da qual
Papo de Botequim Ordena a troca de todos as letras o por falamos no início do nosso bate-papo
$ echo ${cadeia/de/no} a. Outro exemplo mais útil é para contar de hoje, veja a listagem 1) e otimize-a
Papo no Botequim a quantidade de arquivos existentes no para que a variável $SN receba o valor
$ echo ${cadeia/de /} diretório corrente. Observe o exemplo: padrão por expansão de parâmetros,
Papo Botequim como vimos.
$ ls | wc -l E não se esqueça: em caso de dúvidas
Preste atenção quando for usar metaca- 30 ou falta de companhia para um (ou mais)
racteres! Eles são gulosos e sempre com- chope é só mandar um e-mail para julio.
binarão com a maior possibilidade; No O wc põe um monte de espaços em neves@gmail.com. E diga para os amigos
exemplo a seguir eu queria trocar Papo de branco antes do resultado. Para tirá-los: que quem estiver a fim de fazer um curso
Botequim por Conversa de Botequim: porreta de programação em Shell deve
# QtdArqs recebe a saída do comando mandar um e-mail para julio.neves@tecnohall.
$ echo $cadeia $ QtdArqs=$(ls | wc -l) com.br para informar-se. Valeu! ■
Papo de Botequim $ echo ${QtdArqs/ * /}
$ echo ${cadeia/*o/Conversa} 30 Julio Cezar Neves é Analista de Suporte de
Sobre o autor
Papo de Botequim
Curso de Shell Script
Parte X
E
aê amigo, te dei a maior moleza na a legibilidade do código está “horrorível”, O comando eval
última aula né? Um exerciciozinho mas o desempenho, isto é, a velocidade Vou te dar um problema que eu duvido
muito simples… de execução, está ótimo. Como funções que você resolva:
– É, mas nos testes que eu fi z, e de são coisas muito pessoais, já que cada
acordo com o que você ensinou sobre um usa as suas e quase não há neces- $ var1=3
substituição de parâmetros, achei que sidade de manutenção, eu sempre opto $ var2=var1
deveria fazer algumas alterações nas fun- pelo desempenho.
ções que desenvolvemos para torná-las de Hoje vamos sair daquela chatura que Te dei essas duas variáveis e quero que
uso geral, como você disse que todas as foi o nosso último papo e voltar à lógica, você me diga como eu posso, me referindo
funções deveriam ser. Quer ver? saindo da decoreba. Mas volto a te lem- apenas à variável a var2, listar o valor de
– Claro, né, mané, se te pedi para fazer brar: tudo que eu te mostrei da última vez var1 (que, no nosso caso, é 3).
é porque estou a fi m de te ver aprender, aqui no Boteco do Chico é válido e quebra – Ah, isso é mole, mole! É só digitar
mas peraí, dá um tempo. Chico! Manda um galhão. Guarde aqueles guardanapos esse comando aqui:
dois, um sem colarinho! Vai, mostra aí que rabiscamos porque, mais cedo ou
o que você fez. mais tarde, eles lhe vão ser muito úteis. echo $`echo $var2`
– Bem, além do que você pediu, eu
reparei que o programa que chamava a Listagem 1: função pergunta.func
função teria de ter previamente defi nidas 01 # A função recebe 3 parâmetros na seguinte ordem:
a linha em que seria mostrada a mensa- 02 # $1 - Mensagem a ser mostrada na tela
gem e a quantidade de colunas. O que 03 # $2 - Valor a ser aceito com resposta padrão
fi z foi incluir duas linhas – nas quais 04 # $3 - O outro valor aceito
empreguei substituição de parâmetros 05 # Supondo que $1=Aceita?, $2=s e $3=n, a linha
– para que, caso uma dessas variáveis não 06 # abaixo colocaria em Msg o valor "Aceita? (S/n)"
07 TotCols=${TotCols:-$(tput cols)} # Se não estava definido, agora está
fosse informada, ela recebesse um valor
08 LinhaMesg=${LinhaMesg:-$(($(tput lines)-3))} # Idem
atribuído pela própria função. A linha
09 Msg="$1 (`echo $2 | tr a-z A-Z`/`echo $3 | tr A-Z a-z`)"
de mensagem é três linhas antes do fi m 10 TamMsg=${#Msg}
da tela e o total de colunas é obtido pelo 11 Col=$(((TotCols - TamMsg) / 2)) # Para centralizar Msg na linha
comando tput cols. Dê uma olhada na 12 tput cup $LinhaMesg $Col
listagem 1 e veja como ficou: 13 read -n1 -p "$Msg " SN
– Gostei, você já se antecipou ao que eu 14 SN=${SN:-$2} # Se vazia coloca o padrão em SN
ia pedir. Só pra gente encerrar esse papo 15 SN=$(echo $SN | tr A-Z a-z) # A saída de SN será em minúsculas
16 tput cup $LinhaMesg $Col; tput el # Apaga Msg da tela
de substituição de parâmetros, repare que
Repare que eu coloquei o echo $var2 entre crases (`), porque $ var2=ls
dessa forma ele terá prioridade de execução e resultará em var1. $ $var2
E echo $var1 produzirá 3… 10porpag1.sh alo2.sh incusu logado
– Ah, é? Então execute para ver se está correto. 10porpag2.sh ArqDoDOS.txt1 listamusica logaute.sh
10porpag3.sh confuso listartista mandamsg.func
$ echo $`echo $var2` alo1.sh contpal.sh listartista3 monbg.sh
$var1
Agora vamos colocar em var2 o seguinte: ls $var1; e em
– Ué! Que foi que aconteceu? O meu raciocínio me parecia var1 vamos colocar l*, vejamos o resultado:
bastante lógico…
– O seu raciocínio realmente foi lógico, o problema é que você $ var2='ls $var1'
esqueceu de uma das primeiras coisas de que te falei aqui no $ var1='l*'
Boteco e que vou repetir. O Shell usa a seguinte ordem para $ $var2
resolver uma linha de comando: ls: $var1: No such file or directory
P Resolve os redirecionamentos; $ eval $var2
P Substitui as variáveis pelos seus valores; listamusica listartista listartista3 logado logaute.sh
P Resolve e substitui os meta caracteres;
P Passa a linha já toda esmiuçada para execução. Novamente, no tempo de substituição das variáveis, $var1
Dessa forma, quando o interpretador chegou na fase de re- ainda não havia se apresentado ao Shell para ser resolvida.
solução de variáveis, que como eu disse é anterior à execução, Assim, só nos resta executar o comando eval para dar as duas
a única variável existente era var2 e por isso a tua solução passadas necessárias.
produziu como saída $var1. O comando echo identificou isso Uma vez um colega da excelente lista de discussão groups.yahoo.com/
como uma cadeia de caracteres e não como uma variável. group/shell-script colocou uma dúvida: queria fazer um menu
Problemas desse tipo são relativamente freqüentes e seriam que numerasse e listasse todos os arquivos com extensão .sh e,
insolúveis caso não existisse a instrução eval, cuja sintaxe é quando o operador escolhesse uma opção, o programa corres-
eval cmd, onde cmd é uma linha de comando qualquer, que pondente fosse executado. Veja minha proposta na listagem 2:
você poderia inclusive executar direto no prompt do terminal.
Quando você põe o eval na frente, no entanto, o que ocorre é Listagem 2: fazmenu.sh
que o Shell trata cmd como um parâmetro do eval e, em seguida, 01 #!/bin/bash
o eval executa a linha recebida, submetendo-a ao Shell. Ou 02 #
seja, na prática cmd é analisado duas vezes. Dessa forma, se 03 # Lista que enumera os programas com extensão .sh no
executássemos o comando que você propôs colocando o eval 04 # diretório corrente e executa o escolhido pelo operador
05 #
na frente, teríamos a saída esperada. Veja:
06 clear; i=1
07 printf "%11s\t%s\n\n" Opção Programa
$ eval echo $`echo $var2` 08 CASE='case $opt in'
3 09 for arq in *.sh
10 do
Esse exemplo também poderia ter sido feito de outra maneira. 11 printf "\t%03d\t%s\n" $i $arq
12 CASE="$CASE
Dá só uma olhada:
13 "$(printf "%03d)\t %s;;" $i $arq)
14 i=$((i+1))
$ eval echo \$$var2 15 done
3 16 CASE="$CASE
17 *) . erro;;
Na primeira passada a contrabarra (\) seria retirada e $var2 18 esac"
19 read -n3 -p "Informe a opção desejada: " opt
seria resolvido produzindo var1. Na segunda passada teria so-
20 echo
brado echo $var1, que produziria o resultado esperado. Agora 21 eval "$CASE"
vou colocar um comando dentro de var2 e executar:
Parece complicado porque usei muitos dos por) processos em execução. Vamos, "limpar a área" ao seu término. Se seu
printf para formatação da tela, mas na de agora em diante, dar uma olhadinha encerramento ocorrer de forma prevista,
verdade é bastante simples: o primei- nos sinais enviados aos processos e mais ou seja, se tiver um término normal, é
ro printf foi colocado para imprimir à frente vamos dar uma passada rápida muito fácil fazer essa limpeza; porém,
o cabeçalho e logo em seguida come- pelos sinais gerados pelos processos. se o seu programa tiver um fim brusco,
cei a montar dinamicamente a variável Para mandar um sinal a um processo, muita coisa ruim pode ocorrer:
$CASE, na qual ao final será feito um eval usamos normalmente o comando kill, P É possível que em um determinado es-
para execução do programa escolhido. cuja sintaxe é: paço de tempo, o seu computador esteja
Repare no entanto que dentro do loop cheio de arquivos de trabalho inúteis
do for existem dois printf: o primeiro $ kill -sig PID P Seu processador poderá ficar atolado
serve para formatar a tela e o segundo de processos zombies e defuncts gera-
para montar o case (se antes do coman- Onde PID é o identificador do proces- dos por processos filhos que perderam
do read você colocar uma linha echo so (Process Identification ou Process ID). os pais e estão “órfãos”;
"$CASE", verá que o comando case mon- Além do comando kill, algumas seqüên- P É necessário liberar sockets abertos para
tado dentro da variável está todo inden- cias de teclas também podem gerar sinais. não deixar os clientes congelados;
tado. Frescura, né?:). Na saída do for, foi A tabela 1 mostra os sinais mais impor- P Seus bancos de dados poderão ficar
adicionada uma linha à variável $CASE tantes para monitorarmos: corrompidos porque sistemas gerencia-
para, no caso de uma escolha inválida, Além desses, existe o famigerado si- dores de bancos de dados necessitam
ser executada uma função externa para nal -9 ou SIGKILL que, para o processo de um tempo para gravar seus buffers
exibir mensagens de erro. Vamos execu- que o está recebendo, equivale a meter em disco (commit).
tar o script para ver a saída gerada: o dedo no botão de desligar do compu- Enfim, existem mil razões para não usar
tador – o que é altamente indesejável, um kill com o sinal -9 e para monitorar o
$ fazmenu.sh já que muitos programas necessitam encerramento anormal de programas.
Opcao Programa
Listagem 3: Nova versão do fazmenu.sh
001 10porpag1.sh
01 #!/bin/bash
002 10porpag2.sh
02 #
003 10porpag3.sh 03 # Lista enumerando os programas com extensão .sh no
004 alo1.sh 04 # diretório corrente; executa o escolhido pelo operador
005 alo2.sh 05 #
006 contpal.sh 06 clear; i=1
007 fazmenu.sh 07 printf "%11s\t%s\n\n" Opção Programa
008 logaute.sh 08 CASE='case $opt in'
009 monbg.sh 09 for arq in *.sh
010 readpipe.sh 10 do
011 redirread.sh 11 printf "\t%03d\t%s\n" $i $arq
Informe a opção desejada: 12 CASE="$CASE
13 "$(printf "%03d)\t %s;;" $i $arq)
Seria interessante incluir uma opção 14 i=$((i+1))
para terminar o programa e, para isso, 15 done
seria necessária a inclusão de uma linha 16 printf "\t%d\t%s\n\n" 999 "Fim do programa" # Linha incluída
após o loop de montagem da tela e a alte- 17 CASE="$CASE
Se você ignorar um sinal, todos os sub- Para terminar esse assunto, abra um cadeiadeopcoes deve ser abc. Se você
shells irão ignorá-lo. Portanto, se você console gráfico e escreva no prompt de desejar que uma opção seja seguida por
especificar qual ação deve ser tomada comando o seguinte: um argumento, ponha um sinal de dois
quando receber um sinal, todos os sub- pontos (:) depois da letra, como em a:bc.
shells irão tomar a mesma ação quando $ trap "echo Mudou o tamanho da janela" 28 Isso diz ao getopts que a opção -a tem a
receberem esse sinal. Ou seja, os sinais forma -a argumento. Normalmente um
são automaticamente exportados. Para o Em seguida, pegue o mouse e arraste-o ou mais espaços em branco separam o
sinal mostrado (sinal 2), isso significa que de forma a variar o tamanho da janela parâmetro da opção; no entanto, getopts
os sub-shells serão encerrados. Suponha corrente. Surpreso? É o Shell orientado também manipula parâmetros que vêm
que você execute o comando trap "" 2 e a eventos… Mais unzinho, porque não colados à opção como em -aargumento.
então execute um sub-shell, que tornará a consigo resistir. Escreva isto: cadeiadeopcoes não pode conter um si-
executar outro script como um sub-shell. nal de interrogação (?).
Se for gerado um sinal de interrupção, $ trap "echo já era" 17 O nome constante da linha de sintaxe
este não terá efeito nem sobre o Shell acima define uma variável que receberá,
principal nem sobre os sub-shell por ele Em seguida digite: a cada vez que o comando getopts for
chamados, já que todos eles ignorarão executado, o próximo dos parâmetros
o sinal. $ sleep 3 & posicionais e o colocará na variável nome.
Em korn shell (ksh) não existe a opção getopts coloca uma interrogação (?) na
-s do comando read para ler uma senha. Você acabou de criar um sub-shell que variável definida em nome se achar uma
O que costumamos fazer é usar usar o irá dormir durante três segundos em opção não definida em cadeiadeopcoes
comando stty com a opção -echo, que background. Ao fim desse tempo, você ou se não achar o argumento esperado
inibe a escrita na tela até que se encon- receberá a mensagem “já era”, porque o para uma determinada opção.
tre um stty echo para restaurar essa sinal 17 é emitido a cada vez em que um Como já sabemos, cada opção passada
escrita. Então, se estivéssemos usando o sub-shell termina a sua execução. Para por uma linha de comandos tem um ín-
interpretador ksh, a leitura da senha teria devolver esses sinais ao seu comporta- dice numérico; assim, a primeira opção
que ser feita da seguinte forma: mento padrão, digite: trap 17 28. estará contida em $1, a segunda em $2 e
Muito legal esse comando, né? Se você assim por diante. Quando o getopts obtém
echo -n "Senha: " descobrir algum material bacana sobre uma opção, ele armazena o índice do
stty -echo uso de sinais, por favor me informe por próximo parâmetro a ser processado na
read Senha email, porque é muito rara a literatura variável OPTIND.
stty echo sobre o assunto. Quando uma opção tem um argumento
associado (indicado pelo : na cadeiade-
O problema com esse tipo de constru- Comando getopts opcoes), getopts armazena o argumento
ção é que, caso o operador não soubes- O comando getopts recupera as opções e na variável OPTARG. Se uma opção não
se a senha, ele provavelmente teclaria seus argumentos de uma lista de parâ- possuir argumento ou se o argumento
[CTRL]+[C] ou um [CTRL]+[\] durante metros de acordo com a sintaxe POSIX.2, esperado não for encontrado, a variável
a instrução read para descontinuar o pro- isto é, letras (ou números) após um sinal OPTARG será "apagada" (com unset). O co-
grama e, caso agisse dessa forma, o seu de menos (-) seguidas ou não de um mando encerra sua execução quando:
terminal estaria sem echo. Para evitar que argumento; no caso de somente letras P Encontra um parâmetro que não come-
isso aconteça, o melhor a fazer é: (ou números), elas podem ser agrupa- ça com um hífen (-).
das. Você deve usar esse comando para P O parâmetro especial -- indica o fim
echo -n "Senha: " "fatiar" opções e argumentos passados das opções.
trap "stty echo para o seu script. P Quando encontra um erro (por exemplo,
exit" 2 3 A sintaxe é getopts cadeiadeopcoes uma opção não reconhecida).
stty -echo nome. A cadeiadeopcoes deve explicitar O exemplo da listagem 4 é meramente
read Senha uma cadeia de caracteres com todas as didático, servindo para mostrar, em um
stty echo opções reconhecidas pelo script; assim, pequeno fragmento de código, o uso ple-
trap 2 3 se ele reconhece as opções -a -b e –c, no do comando.
Papo de botequim
Curso de Shell Script
E
aí rapaz, tudo bom? – Não estou nem aí para a aparência, o que eu queria é que
– Beleza. Você se lembra de que da última vez você me você exercitasse o que aprendemos. Me dá a listagem 1 pra eu
pediu para fazer um programa que imprimisse dinami- ver o que você fez.
camente, no centro da tela, a quantidade de linhas e colunas – Perfeito! Que se dane a aparência, depois vou te ensinar
de um terminal sempre que o tamanho da janela variasse? uns macetes para melhorá-la. O que vale é que o programa está
Pois é, eu até que fi z, mas mesmo depois de quebrar muito a funcionando e está bem enxuto.
cabeça a aparência não ficou igual. – Pôxa, e eu que perdi o maior tempo tentando descobrir
como aumentar a fonte…
Listagem 1: tamtela.sh – Deixe isso para lá! Hoje vamos ver umas coisas
01 #!/bin/bash bastante interessantes e úteis.
02 #
03 # Coloca no centro da tela, em video reverso, Dando nomes aos canos
04 # a quantidade de colunas e linhas Um outro tipo de pipe é o named pipe, que também
05 # quando o tamanho da tela eh alterado. é chamado de FIFO. FIFO é um acrônimo de First In
06 # First Out que se refere à propriedade em que a ordem
07 trap Muda 28 # 28 = sinal gerado pela mudanca no tamanho
dos bytes entrando no pipe é a mesma que a da saída.
08 # da tela e Muda eh a funcao que fara isso.
O name em named pipe é, na verdade, o nome de um
09
10 Bold=$(tput bold) # Modo de enfase arquivo. Os arquivos tipo named pipes são exibidos
11 Rev=$(tput rev) # Modo de video reverso pelo comando ls como qualquer outro, com poucas
12 Norm=$(tput sgr0) # Restaura a tela ao padrao default diferenças, veja:
13
14 Muda () $ ls -l pipe1
15 { prw-r-r-- 1 julio dipao 0 Jan 22 23:11 pipe1|
16 clear
17 Cols=$(tput cols)
o p na coluna mais à esquerda indica que fifo1
18 Lins=$(tput lines)
19 tput cup $(($Lins / 2)) $(((Cols - 7) / 2)) # Centro da tela
é um named pipe. O resto dos bits de controle de
20 echo $Bold$Rev$Cols X $Lins$Norm permissões, quem pode ler ou gravar o pipe, funcio-
21 } nam como um arquivo normal. Nos sistemas mais
22 modernos uma barra vertical (|), ou pipe, no fi m do
23 clear nome do arquivo é outra dica e, nos sistemas LINUX,
24 read -n1 -p "Mude o tamanho da tela ou tecle algo para terminar " onde o ls pode exibir cores, o nome do arquivo é
escrito em vermelho por padrão.
Nos sistemas mais antigos, os named pipes são criados pelo Sincronização de processos
utilitário mknod, normalmente situado no diretório /etc. Nos Suponha que você dispare paralelamente dois programas (pro-
sistemas mais modernos, a mesma tarefa é feita pelo mkfifo, cessos), chamados programa1 e programa2, cujos diagramas de
que recebe um ou mais nomes como argumento e cria pipes blocos de suas rotinas são como mostrado na tabela 1. Os dois
com esses nomes. Por exemplo, para criar um named pipe com processos são disparados em paralelo e, no bloco 1 do programa1,
o nome pipe1, digite: as três classificações são disparadas da seguinte maneira:
Tabela 1
Programa1 Programa2
Bloco 1 Rotina de classificação de três grandes arquivos Rotina de abertura e geração de menus
Bloco 2 Acertos finais e encerramento Impressão dos dados classificados pelo programa 1
$ ls -l >(cat) #!/bin/bash
l-wx–– 1 jneves jneves 64 Aug 27 12:26 /dev/fd/63 -> pipe:[7050] LIST="" # Criada no shell principal
ls | while read FILE # Inicio do subshell
É… Realmente é um named pipe. Você deve estar pensando do
que isto é uma maluquice de nerd, né? Então suponha que você LIST="$FILE $LIST" # Alterada dentro do subshell
tenha dois diretórios, chamados dir e dir.bkp, e deseja saber done # Fim do subshell
se os dois são iguais. Basta comparar o conteúdo dos diretórios echo $LIST
com o comando cmp:
No início deste exemplo eu disse que ele era meramente
$ cmp <(cat dir/*) <(cat dir.bkp/*) || echo backup furado didático porque existem formas melhores de fazer a mesma
tarefa. Veja só estas duas:
ou, melhor ainda:
$ ls | ln
$ cmp <(cat dir/*) <(cat dir.bkp/*) >/dev/null || echo backup furado
ou então, usando a própria substituição de processos:
Este é um exemplo meramente didático, mas são tantos os
comandos que produzem mais de uma linha de saída que ele $ cat -n <(ls)
serve como guia para outros. Eu quero gerar uma listagem dos
meus arquivos, numerando-os, e ao final mostrar o total de Um último exemplo: você deseja comparar arq1 e arq2 usando
arquivos no diretório corrente: o comando comm, mas esse comando necessita que os arquivos
estejam classificados. Então a melhor forma de proceder é:
while read arq
do $ comm <(sort arq1) <(sort arq2)
((i++)) # assim nao eh necessario inicializar i
echo "$i: $arq" Essa forma evita que você faça as seguintes operações:
done < <(ls)
echo "No diretorio corrente (`pwd`) existem $i arquivos" $ sort arq1 > /tmp/sort1
$ sort arq2 > /tmp/sort2
Tá legal, eu sei que existem outras formas de executar a $ comm /tmp/sort1 /tmp/sort2
mesma tarefa. Usando o comando while, a forma mais comum $ rm -f /tmp/sort1 /tmp/sort2
de resolver esse problema seria:
Pessoal, o nosso papo de botequim chegou ao fim. Curti muito
ls | while read arq e recebi diversos elogios pelo trabalho desenvolvido ao longo
do de doze meses e, o melhor de tudo, fiz muitas amizades e tomei
((i++)) # assim nao eh necessario inicializar i muitos chopes de graça com os leitores que encontrei pelos
echo "$i: $arq" congressos e palestras que ando fazendo pelo nosso querido
done Brasil. Me despeço de todos mandando um grande abraço aos
echo "No diretorio corrente (`pwd`) existem $i arquivos" barbudos e beijos às meninas e agradecendo os mais de 100
emails que recebi, todos elogiosos e devidamente respondidos.
Ao executar o script, tudo parece estar bem, porém no coman- À saúde de todos nós: Tim, Tim.
do echo após o done, você verá que o valor de $i foi perdido. - Chico, fecha a minha conta porque vou pra casa! ■
Isso deve-se ao fato desta variável estar sendo incrementada em
Julio Cezar Neves é Analista de Suporte de Sistemas desde 1969 e
Sobre o autor