Você está na página 1de 5

Papo de Botequim

LINUX USER

Curso de Shell Script

Papo de
botequim III

Um chopinho, um aperitivo e o papo continua. Desta vez vamos aprender


alguns comandos de manipulao de cadeias de caracteres, que sero muito
teis na hora de incrementar nossa CDteca. POR JULIO CEZAR NEVES

aron! traga dois chopes por


favor que hoje eu vou ter que
falar muito. Primeiro quero
mostrar uns programinhas simples
de usar e muito teis, como o cut, que
usado para cortar um determinado
pedao de um arquivo. A sintaxe e
alguns exemplos de uso podem ser vistos no Quadro 1:
Como d para ver, existem quatro
sintaxes distintas: na primeira (-c 1-5)
especifiquei uma faixa, na segunda
(-c -6) especifiquei todo o texto at
uma posio, na terceira (-c 4-) tudo de
uma determinada posio em diante e
na quarta (-c 1,3,5,7,9), s as posies
determinadas. A ltima possibilidade
(-c -3,5,8-) foi s para mostrar que podemos misturar tudo.
Mas no pense que acabou por a!
Como voc deve ter percebido, esta
forma de cut muito til para lidar com
arquivos com campos de tamanho xo,
mas atualmente o que mais existe so
arquivos com campos de tamanho varivel, onde cada campo termina com um
delimitador. Vamos dar uma olhada no
arquivo musicas que comeamos a preparar na ltima vez que viemos aqui no
botequim. Veja o Quadro 2.

Ento, recapitulando, o layout


do arquivo o seguinte: nome do
lbum^intrprete1~nome da msica1:...:
intrpreten~nome da msican, isto , o
nome do lbum ser separado por um
circunexo (^) do resto do registro, que
formado por diversos grupos compostos pelo intrprete de cada msica do
CD e a respectiva msica interpretada.
Estes grupos so separados entre si por
dois-pontos (:) e o intrprete ser separado do nome da msica por um til (~).
Ento, para pegar os dados referentes
a todas as segundas msicas do arquivo
musicas, devemos digitar:
$ cut -f2 -d: musicas
Artista2~Musica2
Artista4~Musica4
Artista6~Musica5
Artista8~Musica8@10_L:

Ou seja, cortamos o segundo campo


z(-f de eld, campo em ingls) delimitado (-d) por dois-pontos (:). Mas, se
quisermos somente os intrpretes, devemos digitar:
$ cut -f2 -d: musicas | cut U
-f1 -d~

Artista2
Artista4
Artista6
Artista8

Para entender melhor isso, vamos analisar a primeira linha de musicas:


$ head -1 musicas
album 1^Artista1~Musica1: U
Artista2~Musica2

Ento observe o que foi feito:


album 1^Artista1~Musica1: U
Artista2~Musica2

Desta forma, no primeiro cut o primeiro


campo do delimitador (-d) dois-pontos (:) album 1^Artista1~Musica1 e o
segundo, que o que nos interessa,
Artista2~Musica2. Vamos ento ver o
que aconteceu no segundo cut:
Artista2~Musica2

Agora, primeiro campo do delimitador


(-d) til (~), que o que nos interessa,
Artista2 e o segundo Musica2. Se
o raciocnio que fizemos para a pri-

www.linuxmagazine.com.br

Outubro 2004

85

LINUX USER

Papo de Botequim

Quadro 1 O comando cut


A sintaxe do cut : cut -c PosIni-PosFim
[arquivo], onde PosIni a posio inicial, e
PosFim a posio nal. Veja os exemplos:
$ cat numeros
1234567890
0987654321
1234554321
9876556789
$ cut -c1-5 numeros
12345
09876
12345
98765
$ cut -c-6 numeros
123456
098765
123455
987655
$ cut -c4- numeros
4567890
7654321
4554321
6556789
$ cut -c1,3,5,7,9 numeros
13579
08642
13542
97568
$ cut -c -3,5,8- numeros
1235890
0986321
1235321
9875789

Quadro 2 O arquivo
musicas
$ cat musicas
album 1^Artista1~Musica1:U
Artista2~Musica2
album 2^Artista3~Musica3:U
Artista4~Musica4
album 3^Artista5~Musica5:U
Artista6~Musica5
album 4^Artista7~Musica7:U
Artista8~Musica8

meira linha for aplicado ao restante do


arquivo, chegaremos resposta anteriormente dada. Outro comando muito
interessante o tr que serve para substituir, comprimir ou remover caracteres.
Sua sintaxe segue o seguinte padro:
tr [opes] cadeia1 [cadeia2]

O comando copia o texto da entrada


padro (stdin), troca as ocorrncia dos
caracteres de cadeia1 pelo seu correspondente na cadeia2 ou troca mltiplas
ocorrncias dos caracteres de cadeia1
por somente um caracter, ou ainda
caracteres da cadeia1. As principais
opes do comando so mostradas na
Tabela 1.
Primeiro veja um exemplo bem bobo:
$ echo bobo | tr o a
baba

Isto , troquei todas as ocorrncias da


letra o pela letra a. Suponha que em
determinado ponto do meu script eu
pea ao operador para digitar s ou n
(sim ou no), e guardo sua resposta
na varivel $Resp. Ora, o contedo de
$Resp pode conter letras maisculas
ou minsculas, e desta forma eu teria
que fazer diversos testes para saber se
a resposta dada foi S, s, N ou n. Ento o
melhor fazer:
$ Resp=$(echo $Resp | tr SN sn)

e aps este comando eu teria certeza


de que o contedo de $Resp seria um
s ou um n. Se o meu arquivo ArqEnt
est todo em letras maisculas e desejo
pass-las para minsculas eu fao:
$ tr A-Z a-z < ArqEnt > / tmp/$$
$ mv -f /tmp/$$ ArqEnt

Note que neste caso usei a notao AZ para no escrever ABCDYZ. Outro
tipo de notao que pode ser usada so
as escape sequences (como eu traduziria? Seqncias de escape? Meio sem
sentido, n? Mas v l) que tambm
so reconhecidas por outros comandos
e tambm na linguagem C, e cujo signicado voc ver na Tabela 2:
Deixa eu te contar um causo: um
aluno que estava danado comigo resolveu complicar minha vida e como res-

86

Outubro 2004

www.linuxmagazine.com.br

posta a um exerccio prtico, valendo


nota, que passei ele me entregou um
script com todos os comandos separados por ponto-e-vrgula (lembre-se
que o ponto-e-vrgula serve para separar diversos comandos em uma mesma
linha). Vou dar um exemplo simplicado, e idiota, de um script assim:
$ cat confuso
echo leia Programao Shell U
Linux do Julio Cezar Neves U
> livro;cat livro;pwd;ls;rm U
-f livro2>/dev/null;cd ~

Eu executei o programa e ele funcionou:


$ confuso
leia Programao Shell Linux U
do Julio Cezar Neves
/home/jneves/LM
confuso livro musexc musicas
musinc muslist numeros

Mas nota de prova coisa sria (e nota


de dlar mais ainda) ento, para
entender o que o aluno havia feito, o
chamei e em sua frente digitei:
$ tr ; \n < confuso
echo leia Programao Shell U
Linux do Julio Cezar Neves
pwd
cd ~
ls -l
rm -f lixo 2>/dev/null

O cara cou muito desapontado, porque


em dois ou trs segundos eu des z a
gozao que ele perdeu horas para fazer.
Mas preste ateno! Se eu estivesse em
uma mquina Unix, eu teria digitado:
$ tr ; \012 < confuso

Agora veja a diferena entre o resultado


de um comando date executado hoje e
outro executado h duas semanas:
Sun Sep 19 14:59:54
Sun Sep 5 10:12:33

2004
2004

Notou o espao extra aps o Sep na


segunda linha? Para pegar a hora eu
deveria digitar:
$ date | cut -f 4 -d
14:59:54

Papo de Botequim

Mas h duas semanas ocorreria o


seguinte:
$ date | cut -f 4 -d
5

Isto porque existem 2 caracteres em


branco antes do 5 (dia). Ento o ideal
seria transformar os espaos em branco
consecutivos em somente um espao
para poder tratar os dois resultados do
comando date da mesma forma, e isso
se faz assim:
$ date | tr -s
Sun Sep 5 10:12:33 2004

E agora eu posso cortar:


$ date | tr -s | cut -f 4 U
-d
10:12:33

Olha s como o Shell est quebrando o


galho. Veja o contedo de um arquivo
baixado de uma mquina Windows:
$ cat -ve ArqDoDOS.txt
Este arquivo^M$
foi gerado pelo^M$
DOS/Win e foi^M$
baixado por um^M$
ftp mal feito.^M$

Dica: a opo -v do cat mostra os caracteres de controle invisveis, com a notao ^L, onde ^ a tecla Control e L
a respectiva letra. A opo -e mostra o
nal da linha como um cifro ($).
Isto ocorre porque no DOS o m dos
registros indicado por um Carriage
Return (\r Retorno de Carro, CR) e
um Line Feed (\f Avano de Linha, ou
LF). No Linux porm o nal do registro indicado somente pelo Line Feed.
Vamos limpar este arquivo:
$ tr -d \r < ArqDoDOS.txt > /tmp/$$
$ mv -f /tmp/$$ ArqDoDOS.txt

Agora vamos ver o que aconteceu:


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

Bem a opo -d do tr remove do arquivo


todas as ocorrncias do caractere especificado. Desta forma eu removi os
caracteres indesejados, salvei o texto
em um arquivo temporrio e posteriormente renomeei-o para o nome original.
Uma observao: em um sistema Unix
eu deveria digitar:

LINUX USER

E veja tambm o date:


$ date
Mon Sep 20 10:47:19 BRT 2004

Repare que o ms e o dia esto no


mesmo formato em ambos os comandos. Ora, se em algum registro do who
eu no encontrar a data de hoje, sinal
$ tr -d \015 < ArqDoDOS.U
que o usurio est logado h mais
txt > /tmp/$$
de um dia, j que ele no pode ter se
logado amanh Ento vamos guarUma dica: o problema com os termina- dar o pedao que importa da data de
dores de linha (CR/LF) s aconteceu
hoje para depois procur-la na sada do
porque a transferncia do arquivo foi
comando who:
feita no modo binrio (ou image), Se
antes da transmisso do arquivo tivesse
$ Data=$(date | cut -f 2-3 U
sido estipulada a opo ascii do ftp, isto
-d )
no teria ocorrido.
Olha, depois desta dica t comeando a
Eu usei a construo $(...), para priogostar deste tal de shell, mas ainda tem
rizar a execuo dos comandos antes
muita coisa que no consigo fazer.
de atribuir a sua sada varivel Data.
Pois , ainda no te falei quase nada
Vamos ver se funcionou:
sobre programao em shell, ainda
tem muita coisa para aprender, mas
$ echo $Data
com o que aprendeu, j d para resolSep 20
ver muitos problemas, desde que voc
adquira o modo shell de pensar. Voc
Beleza! Agora, o que temos que fazer
seria capaz de fazer um script que diga
procurar no comando who os registros
quais pessoas esto logadas h mais
que no possuem esta data.
de um dia no seu servidor?
Claro que no! Para isso seria necess- Ah! Eu acho que estou entendendo!
rio eu conhecer os comandos condicioVoc falou em procurar e me ocorreu o
nais que voc ainda no me explicou
comando grep, estou certo?
como funcionam. - Deixa eu tentar Certssimo! S que eu tenho que usar o
mudar um pouco a sua lgica e trazgrep com aquela opo que ele s lista
la para o modo shell de pensar, mas
os registros nos quais ele no enconantes melhor tomarmos um chope.
trou a cadeia. Voc se lembra que
Agora que j molhei a palavra, vamos
opo essa?
resolver o problema que te propus. Veja Claro, a -v
como funciona o comando who:
Isso! T cando bom! Vamos ver:
$ who
jneves
1
rtorres
0
rlegaria
1
lcarlos
3

pts/ U
Sep 18
pts/ U
Sep 20
pts/ U
Sep 20
pts/ U
Sep 20

$ who | grep -v $Data


jneves
pts/ U
1
Sep 18 13:40

13:40
07:01

Se eu quisesse um pouco mais de perfumaria eu faria assim:

08:19
$

who | grep -v $Data |U


cut -f1 -d
jneves

10:01

Tabela 1 O comando tr

Opo

Signicado

-s

Comprime n ocorrncias de
cadeia1 em apenas uma

-d

Remove os caracteres de cadeia1

Viu? No foi necessrio usar comando


condicional, at porque o nosso
comando condicional, o famoso if, no
testa condio, mas sim instrues.
Mas antes veja isso:

www.linuxmagazine.com.br

Outubro 2004

87

LINUX USER

Papo de Botequim

Tabela 2

$ ls musicas
musicas
$ echo $?
0
$ ls ArqInexistente
ls: ArqInexistente: No such U
le or directory
$ echo $?
1
$ who | grep jneves
jneves
pts/1
Sep 18 U
13:40 (10.2.4.144)
$ echo $?
0
$ who | grep juliana
$ echo $?
1

Seqncia
\t
\n
\v
\f
\r
\\

O que que esse $? faz a? Algo comeado por cifro ($) parece ser uma varivel, certo? Sim uma varivel que
contm o cdigo de retorno da ltima
instruo executada. Posso te garantir que se esta instruo foi bem sucedida, $? ter o valor zero, caso contrrio
seu valor ser diferente de zero. O que
nosso comando condicional (if) faz
testar esta varivel. Ento vamos ver a
sua sintaxe:
if cmd
then
cmd1
cmd2
cmdn
else
cmd3
cmd4
cmdm

Ou seja, caso comando cmd tenha sido


executado com sucesso, os comandos
do bloco do then (cmd1, cmd2 e cmdn)
sero executados, caso contrrio, os
comandos do bloco opcional do else
(cmd3, cmd4 e cmdm) sero executados.
O bloco do if terminando com um .
Vamos ver na prtica como isso funciona, usando um script que inclui usurios no arquivo /etc/passwd:
$ cat incusu
#!/bin/bash
# Verso 1
if grep ^$1 /etc/passwd
then
echo Usuario \$1\U

Signicado
Tabulao
Nova linha
<ENTER>
Tabulao
Vertical
Nova
Pgina
Incio da
linha <^M>
Uma barra
invertida

Outubro 2004

\013
\014
\015
\0134

j existe
else
if useradd $1
then
echo Usurio \$1\ U
includo em /etc/passwd
else
echo Problemas no U
cadastramento. Voc root?

Repare que o if est testando direto o


comando grep e esta a sua nalidade.
Caso o if seja bem sucedido, ou seja, o
usurio (cujo nome est em $1) foi
encontrado em /etc/passwd, os comandos do bloco do then sero executados
(neste exemplo, apenas o echo). Caso
contrrio, as instrues do bloco do else
sero executadas, quando um novo if
testa se o comando useradd foi executado a contento, criando o registro do
usurio em /etc/passwd, ou exibindo
uma mensagem de erro, caso contrrio.
Executar o programa e passe como
parmetro um usurio j cadastrado:
$ incusu jneves
jneves:x:54002:1001:Julio Neves: U
/home/jneves:/bin/ U
bash
Usuario jneves ja existe

No exemplo dado, surgiu uma linha


indesejada, ela a sada do comando
grep. Para evitar que isso acontea,
devemos desviar a sada para /dev/null.
O programa ca assim:
$ cat incusu
#!/bin/bash
# Verso 2
if grep ^$1 /etc/passwd > U

88

Octal
\011
\012

www.linuxmagazine.com.br

/dev/null
then
echo Usuario \$1\ j U
existe
else
if useradd $1
then
echo Usurio \$1\ U
includo em /etc/passwd
else
echo Problemas no U
cadastramento. Voc root?

Vamos test-lo como um usurio normal :


$ incusu ZeNinguem
./incusu[6]: useradd: not found
Problemas no cadastramento. U
Voc root?

Aquela mensagem de erro no deveria


aparecer! Para evitar isso, devemos
redirecionar a sada de erro (stderr) do
comando useradd para /dev/null. A verso nal ca assim:
$ cat incusu
#!/bin/bash
# Verso 3
if grep ^$1 /etc/passwd > U
/dev/null
then
echo Usuario \$1\ j U
existe
else
if useradd $1 2> /dev/null
then
echo Usurio \$1\ U
includo em /etc/passwd
else
echo Problemas no U
cadastramento. Voc root?

Depois disso, vejamos o comportamento


do programa, se executado pelo root:
$ incusu botelho
Usurio botelho incluido em U
/etc/passwd

E novamente:
$ incusu botelho
Usurio botelho j existe

Papo de Botequim

Lembra que eu falei que ao longo dos


nossos papos e chopes os nossos programas iriam se aprimorando? Ento
vejamos agora como podemos melhorar
o nosso programa para incluir msicas
na "CDTeca":
$ cat musinc
#!/bin/bash
# Cadastra CDs (versao 3)
#
if grep ^$1$ musicas > U
/dev/null
then
echo Este lbum j est U
cadastrado
else
echo $1 >> musicas
sort musicas -o musicas

Como voc viu, uma pequena evoluo em relao verso anterior. Antes
de incluir um registro (que na verso
anterior poderia ser duplicado), testamos se o registro comea (^) e termina
($) de forma idntica ao parmetro
lbum passado ($1). O circunexo (^)

no incio da cadeia e cifro ($) no m,


servem para testar se o parmetro (o
lbum e seus dados) exatamente igual
a algum registro j existente. Vamos
executar nosso programa novamente,
mas desta vez passamos como parmetro um lbum j cadastrado, pra ver o
que acontece:
$ musinc album 4^Artista7~ U
Musica7:Artista8~Musica8
Este lbum j est cadastrado

E agora um no cadastrado:
$ musinc album 5^Artista9~ U
Musica9:Artista10~Musica10
$ cat musicas
album 1^Artista1~Musica1: U
Artista2~Musica2
album 2^Artista3~Musica3: U
Artista4~Musica4
album 3^Artista5~Musica5: U
Artista6~Musica5
album 4^Artista7~Musica7: U
Artista8~Musica8
album 5^Artista9~Musica9: U
Artista10~Musica10

LINUX USER

Como voc viu, o programa melhorou um pouquinho, mas ainda no est


pronto. medida que eu te ensinar a
programar em shell, nossa CDteca vai
car cada vez melhor.
Entendi tudo que voc me explicou,
mas ainda no sei como fazer um if
para testar condies, ou seja o uso
normal do comando.
Para isso existe o comando test, que
testa condies. O comando if testa
o comando test. Como j falei muito,
preciso de uns chopes para molhar a
palavra. Vamos parar por aqui e na
prxima vez te explico direitinho o
uso do test e de diversas outras sintaxes do if.
Falou! Acho bom mesmo porque eu
tambm j t cando zonzo e assim
tenho tempo para praticar esse monte
de coisas que voc me falou hoje.
Para xar o que voc aprendeu, tente
fazer um scriptizinho para informar se
um determinado usurio, cujo nome
ser passado como parmetro, est
logado no sistema ou no.
A Chico! traz mais dois chopes pra
mim por favor

www.linuxmagazine.com.br

Outubro 2004

89

Você também pode gostar