Este documento fornece instruções sobre como manipular cadeias de caracteres usando comandos do shell script no Linux. Ele explica como usar cut para extrair partes de arquivos, tr para substituir caracteres e grep para procurar padrões em arquivos. O autor também discute como resolver problemas com arquivos de texto contendo caracteres indesejáveis e como formatar a saída de comandos como date.
Apostila teoria musical fundamental i - Alberto Damián
Curso de Shell Script - Comandos cut e tr
1. ����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
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 manipulação de cadeias de caracteres, que serão muito
úteis na hora de incrementar nossa “CDteca”. POR JULIO CEZAR NEVES
G arçon! 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
Então, recapitulando, o layout
do arquivo é o seguinte: nome do
álbum^intérprete1~nome da música1:...:
intérpreten~nome da músican, isto é, o
nome do álbum será separado por um
Artista2
Artista4
Artista6
Artista8
é 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
��������������������������������������������������������������������������������
2. ���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim
Quadro 1 – O comando cut meira linha for aplicado ao restante do
arquivo, chegaremos à resposta ante-
posta a um exercício prático, valendo
nota, que passei ele me entregou um
riormente dada. Outro comando muito script com todos os comandos sepa-
A sintaxe do cut é: cut -c PosIni-PosFim
interessante é o tr que serve para subs- rados por ponto-e-vírgula (lembre-se
[arquivo], onde PosIni é a posição inicial, e
tituir, comprimir ou remover caracteres. que o ponto-e-vírgula serve para sepa-
PosFim a posição final. Veja os exemplos:
Sua sintaxe segue o seguinte padrão: rar diversos comandos em uma mesma
linha). Vou dar um exemplo simplifi-
$ cat numeros
tr [opções] cadeia1 [cadeia2] cado, e idiota, de um script assim:
1234567890
0987654321
O comando copia o texto da entrada $ cat confuso
1234554321
padrão (stdin), troca as ocorrência dos echo leia Programação Shell U
9876556789
caracteres de cadeia1 pelo seu corres- Linux do Julio Cezar Neves U
pondente na cadeia2 ou troca múltiplas > livro;cat livro;pwd;ls;rm U
$ cut -c1-5 numeros
ocorrências dos caracteres de cadeia1 -f livro2>/dev/null;cd ~
12345
por somente um caracter, ou ainda
09876
caracteres da cadeia1. As principais Eu executei o programa e ele funcionou:
12345
opções do comando são mostradas na
98765
Tabela 1. $ confuso
Primeiro veja um exemplo bem bobo: leia Programação Shell Linux U
$ cut -c-6 numeros
do Julio Cezar Neves
123456
$ echo bobo | tr o a /home/jneves/LM
098765
baba confuso livro musexc musicas
123455
musinc muslist numeros
987655
Isto é, troquei todas as ocorrências da
letra o pela letra a. Suponha que em Mas nota de prova é coisa séria (e nota
$ cut -c4- numeros
determinado ponto do meu script eu de dólar é mais ainda) então, para
4567890
peça ao operador para digitar s ou n entender o que o aluno havia feito, o
7654321
(sim ou não), e guardo sua resposta chamei e em sua frente digitei:
4554321
na variável $Resp. Ora, o conteúdo de
6556789
$Resp pode conter letras maiúsculas $ tr ”;” ”n” < confuso
ou minúsculas, e desta forma eu teria echo leia Programação Shell U
$ cut -c1,3,5,7,9 numeros
que fazer diversos testes para saber se Linux do Julio Cezar Neves
13579
a resposta dada foi S, s, N ou n. Então o pwd
08642
melhor é fazer: cd ~
13542
ls -l
97568
$ Resp=$(echo $Resp | tr SN sn) rm -f lixo 2>/dev/null
$ cut -c -3,5,8- numeros
e após este comando eu teria certeza O cara ficou muito desapontado, porque
1235890
de que o conteúdo de $Resp seria um em dois ou três segundos eu desfi z a
0986321
s ou um n. Se o meu arquivo ArqEnt gozação que ele perdeu horas para fazer.
1235321
está todo em letras maiúsculas e desejo Mas preste atenção! Se eu estivesse em
9875789
passá-las para minúsculas eu faço: uma máquina Unix, eu teria digitado:
$ 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
��������������������������������������������������������������������������������
3. ����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
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
��������������������������������������������������������������������������������
4. ���������������������������������������������������
����������������������������������������������������������������������������������������������������������������������������������������������������
LINUX USER Papo de Botequim
$ ls musicas Tabela 2 /dev/null
musicas Seqüência Significado Octal then
$ echo $? t Tabulação 011 echo Usuario ’$1’ já U
0 n Nova linha 012 existe
$ ls ArqInexistente <ENTER> else
ls: ArqInexistente: No such U v Tabulação 013 if useradd $1
file or directory Vertical then
$ echo $? f Nova 014 echo Usuário ’$1’ U
1 Página incluído em /etc/passwd
$ who | grep jneves r Início da 015 else
jneves pts/1 Sep 18 U linha <^M> echo ”Problemas no U
Uma barra 0134
13:40 (10.2.4.144) cadastramento. Você é root?”
invertida
$ echo $? fi
0 fi
$ who | grep juliana já existe
$ echo $? else Vamos testá-lo como um usuário normal :
1 if useradd $1
then $ incusu ZeNinguem
O que é que esse $? faz aí? Algo come- echo Usuário ’$1’ U ./incusu[6]: useradd: not found
çado por cifrão ($) parece ser uma vari- incluído em /etc/passwd Problemas no cadastramento. U
ável, certo? Sim é uma variável que else Você é root?
contém o código de retorno da última echo ”Problemas no U
instrução executada. Posso te garan- cadastramento. Você é root?” Aquela mensagem de erro não deveria
tir que se esta instrução foi bem suce- fi aparecer! Para evitar isso, devemos
dida, $? terá o valor zero, caso contrário fi redirecionar a saída de erro (stderr) do
seu valor será diferente de zero. O que comando useradd para /dev/null. A ver-
nosso comando condicional (if) faz é Repare que o if está testando direto o são fi nal fica assim:
testar esta variável. Então vamos ver a comando grep e esta é a sua fi nalidade.
sua sintaxe: Caso o if seja bem sucedido, ou seja, o $ cat incusu
usuário (cujo nome está em $1) foi #!/bin/bash
if cmd encontrado em /etc/passwd, os coman- # Versão 3
then dos do bloco do then serão executados if grep ^$1 /etc/passwd > U
cmd1 (neste exemplo, apenas o echo). Caso /dev/null
cmd2 contrário, as instruções do bloco do else then
cmdn serão executadas, quando um novo if echo Usuario ’$1’ já U
else testa se o comando useradd foi execu- existe
cmd3 tado a contento, criando o registro do else
cmd4 usuário em /etc/passwd, ou exibindo if useradd $1 2> /dev/null
cmdm uma mensagem de erro, caso contrário. then
fi Executar o programa e passe como echo Usuário ’$1’ U
parâmetro um usuário já cadastrado: incluído em /etc/passwd
Ou seja, caso comando cmd tenha sido else
executado com sucesso, os comandos $ incusu jneves echo ”Problemas no U
do bloco do then (cmd1, cmd2 e cmdn) jneves:x:54002:1001:Julio Neves: U cadastramento. Você é root?”
serão executados, caso contrário, os /home/jneves:/bin/ U fi
comandos do bloco opcional do else bash fi
(cmd3, cmd4 e cmdm) serão executados. Usuario ’jneves’ ja existe
O bloco do if é terminando com um fi. Depois disso, vejamos o comportamento
Vamos ver na prática como isso fun- No exemplo dado, surgiu uma linha do programa, se executado pelo root:
ciona, usando um script que inclui usu- indesejada, ela é a saída do comando
ários no arquivo /etc/passwd: grep. Para evitar que isso aconteça, $ incusu botelho
devemos desviar a saída para /dev/null. Usuário ’botelho’ incluido em U
$ cat incusu O programa fica assim: /etc/passwd
#!/bin/bash
# Versão 1 $ cat incusu E novamente:
if grep ^$1 /etc/passwd #!/bin/bash
then # Versão 2 $ incusu botelho
echo Usuario ’$1’U if grep ^$1 /etc/passwd > U Usuário ’botelho’ já existe
�����������������������������
88 Outubro 2004 www.linuxmagazine.com.br
��������������������������������������������������������������������������������
5. ����������������������������������������������������
�����������������������������������������������������������������������������������������������������������������������������������������������������
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
��������������������������������������������������������������������������������