Feeds:
Posts
Comentários

Posts Tagged ‘cut’

Papo de Botequim – Parte 3

– Garçon, traga dois chopes por favor que hoje eu vou ter que falar muito.

Trabalhando com cadeias

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

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

Primeiro quero te mostrar, de forma eminentemente prática uma instrução simples de usar e muito útil: o comando cut, Esta instrução é usada para cortar um determinado pedaço de um arquivo e tem duas formas distintas de uso

O comando cut com a opção -c

Com esta opção, o comando tem a seguinte sintaxe:
cut -c PosIni-PosFim [arquivo]

Onde:
PosIni = Posição inicial
PosFim = Posição final

$ 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

Como dá para ver, no duro mesmo existem quatro sintaxes distintas: na primeira (-c 1-5), eu especifiquei uma faixa, na segunda (-c -6), especifiquei tudo até uma posição, na terceira (-c 4-) de uma determinada posição em diante e na quarta (-c 1,3,5,7,9), determinadas posições. A última (-c -3,5,8-) foi só para mostrar que podemos misturar tudo.

O comando cut com a opção -f

Mas não pense você que acabou por aí! Como você deve ter percebido esta forma de cut é útil para arquivos com campos de tamanho fixo, mas atualmente o que mais existe são arquivos com campos de tamanho variáveis, onde cada campo termina com um delimitador. Vamos dar uma olhada no arquivo musicas que começamos a preparar no nosso papo na última vez que viemos aqui no botequim.

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

Então, recapitulando, o seu “leiaute” é o seguinte:
nome do album^interprete1~nome da musica1:...:interpreten~nome da musican

isto é, o nome do álbum será separado por um circunflexo (^) do resto do registro, que é formado por diversos grupos compostos pelo intérprete de cada música do CD e a respectiva música interpretada. Estes grupos são separados entre si por dois-pontos (:) e internamente, o nome do intérprete será separado por um til (~) do nome da música.

Então para pegarmos os dados referentes a todas as segundas músicas do arquivo musicas, devemos fazer:

$ cut -f2 -d: musicas
Artista2~Musica2
Artista4~Musica4
Artista6~Musica5
Artista8~Musica8

Ou seja, cortamos o segundo campo (-f de field em inglês) delimitado (-d) por dois-pontos (:). Mas, se quisermos somente os intérpretes, devemos fazer:

$ cut -f2 -d: musicas | cut -f1 -d~
Artista2
Artista4
Artista6
Artista8

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

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

Então observe o que foi feito:

Delimitador do primeiro cut (:)

album 1^Artista1~Musica1:Artista2~Musica2

Desta forma, no primeiro cut, o primeiro campo do delimitador (-d) dois-pontos (:) é album 1^Artista1~Musica1 e o segundo, que é o que nos interessa, é Artista2~Musica2.

Vamos então ver o que aconteceu no segundo cut:

Novo delimitador (~)

Artista2~Musica2

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

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

Se tem cut tem paste

Como já era de se esperar, o comando paste serve para colar, só que aqui no Shell o que ele cola são arquivos. Só para começar a entendê-lo, vamos fazer assim::

    paste arq1 arq2

Desta forma ele mandará para a saída padrão (stdout) cada um dos registros de arq1 ao lado dos registros de arq2 correspondentes e caso nenhum delimitador seja especificado, ele usará por default o <TAB>.

O paste é um comando pouco usado por sua sintaxe ser pouco conhecida. Vamos brincar com 2 arquivos criados da seguinte forma:

$ seq 10 > inteiros
$ seq 2 2 10 > pares

Para ver o conteúdo dos arquivos criados, vamos usar o paste na sua forma careta que mostramos acima:

$ paste inteiros pares
1 2
2 4
3 6
4 8
5 10
6
7
8
9
10

Quem está em pé, deita

Agora vamos transformar a coluna do pares em linha:

$ paste -s pares
2 4 6 8 10

Usando separadores

Como já foi dito, o separador default do paste é o <TAB>, mas isso pode ser alterado com a opção -d. Então para calcular a soma do conteúdo de pares primeiramente faríamos:

$ paste -s -d’+’ pares # também poderia ser -sd’+’
2+4+6+8+10

e depois passaríamos esta linha para a calculadora (bc) e então ficaria:

$ paste -sd’+’ pares | bc
30

Assim sendo, para calcular o fatorial do número contido em $Num, basta:

$ seq $Num | paste -sd’*’ | bc

Com o comando paste você também pode montar formatações exóticas como esta a seguir:

$ ls | paste -s -d’ttn’
arq1 arq2 arq3
arq4 arq5 arq6

O que aconteceu foi o seguinte: foi especificado para o comando paste que ele transformaria linhas em colunas (pela opção -s) e que os seus separadores (é…! Ele aceita mais de um, mas somente um após cada coluna criada pelo comando) seriam uma <TAB>, outra <TAB> e um <ENTER>, gerando desta forma a saída tabulada em 3 colunas.

Agora que você já entendeu isto, veja como fazer a mesma coisa, porém de forma mais fácil e menos bizarra e tosca, usando o mesmo comando mas com a seguinte sintaxe:

$ ls | paste – – –
arq1 arq2 arq3
arq4 arq5 arq6

E isto acontece porque se ao invés de especificarmos os arquivos colocarmos o sinal de menos (-), o comando paste os substitui pela saída ou entrada padrão conforme o caso. No exemplo anterior os dados foram mandados para a saída padrão (stdout), porque o pipe (|) estava desviando a saída do ls para a entrada padrão (stdin) do paste, mas veja o exemplo a seguir:

$ cat arq1
predisposição
privilegiado
profissional

$ cat arq2
encher
mário
motor

$ cut -c-3 arq1 | paste -d “” – arq2
preencher
primário
promotor

Neste caso, o cut devolveu as três primeiras letras de cada registro de arq1, o paste foi montado para não ter separador (-d"") e receber a entrada padrão (desviada pelo pipe) no traço (-) gerando a saída juntamente com arq2.

O Comando tr

Outro comando muito interessante é o tr que serve para substituir, comprimir ou remover caracteres. Sua sintaxe segue o seguinte padrão:

    tr [opções] cadeia1 [cadeia2]

O comando tr copia o texto da entrada padrão (stdin), troca as ocorrência dos caracteres de cadeia1 pelo seu correspondente na cadeia2 ou troca múltiplas ocorrências dos caracteres de cadeia1 por somente um caracter, ou ainda remove caracteres da cadeia1.

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

Principais Opções do comando tr
Opção Significado
-s Comprime n ocorrências de cadeia1 em apenas uma
-d Remove os caracteres de cadeia1

Trocando caracteres com tr

Primeiro vou te dar um exemplo bem bobo:

$ echo bobo | tr o a
baba

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

Suponha que em um determinado ponto do meu script eu peça ao operador para teclar s ou n (sim ou não), e guardo sua resposta na variável $Resp. Ora o conteúdo de $Resp pode estar com letra maiúscula ou minúscula, e desta forma eu teria que fazer diversos testes para saber se a resposta dada foi S, s, N ou n. Então o melhor é fazer:

$ Resp=$(echo $Resp | tr SN sn)

e após este comando eu teria certeza que o conteúdo de $Resp seria um s ou um n.

Se o meu arquivo ArqEnt está todo escrito com letras maiúsculas e desejo passá-las para minúsculas eu faço:

$ tr A-Z a-z < ArqEnt > /tmp/$$
$ mv -f /tmp/$$ ArqEnt

Note que neste caso usei a notação A-Z para não escrever ABCD...YZ. Outro tipo de notação que pode ser usada são as escape sequences (prefiro escrever no bom e velho português, mas nesse caso como eu traduziria? Seqüências de escape? Meio sem sentido, né? Mas vá lá…) que também são reconhecidas por outros comandos e também na linguagem C, e cujo significado você verá a seguir:

Escape Sequences
Seqüência Significado Octal
t Tabulação 11
n Nova linha 12
v Tabulação Vertical 13
f Nova Página 14
r Início da linha <^M> 15
\ Uma barra invertida 134

Removendo caracteres com tr

Então deixa eu te contar um “causo”: um aluno que estava danado comigo, resolveu complicar a minha vida e em um exercício prático valendo nota que passei para ser feito no computador, me entregou o script com todos os comandos separados por ponto-e-vírgula (lembra que eu disse que o ponto-e-vírgula servia para separar diversos comandos em uma mesma linha?).

Vou dar um exemplo simplificado e idiota de uma “tripa” assim:

$ cat confuso
echo leia Programação Shell Linux do Julio Cezar Neves > livro;cat livro;pwd;ls;rm -f lixo 2>/dev/null;cd ~

Eu executava o programa e ele funcionava:

$ confuso
leia Programação Shell Linux do Julio Cezar Neves
/home/jneves/LM
confuso livro musexc musicas musinc muslist numeros

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

$ tr “;” “n” < confuso
echo leia Programação Shell Linux do Julio Cezar Neves
pwd
ls
rm -f lixo 2>/dev/null

cd ~

O cara ficou muito desapontado, porque em 2 ou 3 segundos eu desfiz a gozação que ele perdera horas para fazer.
Mas preste atenção! Se eu estivesse em uma máquina com Unix, eu teria feito:

$ tr “;” “12” < confuso

Xpremendo com tr

Agora veja a diferença entre os dois comandos date: o que fiz hoje e outro que foi executado há duas semanas:

$ date # Hoje
Sun Sep 19 14:59:54 2004

$ date # Há duas semanas
Sun Sep 5 10:12:33 2004

Para pegar a hora eu deveria fazer:

$ date | cut -f 4 -d ‘ ‘
14:59:54

Mas duas semanas antes ocorreria o seguinte:

$ date | cut -f 4 -d ‘ ‘
5

Mas observe porque:

$ date # Há duas semanas
Sun Sep 5 10:12:33 2004

Como você pode notar, existem 2 caracteres em branco antes do 5 (dia), o que estraga tudo porque o terceiro pedaço está vazio e o quarto é o dia (5). Então o ideal seria comprimir os espaços em brancos sucessivos em somente um espaço para poder tratar as duas cadeias resultantes do comando date da mesma forma, e isso se faz assim:

$ date | tr -s ” “a
Sun Sep 5 10:12:33 2004

Como você pode ver não existem mais os dois espaços, então agora eu poderia cortar:

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

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

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

E agora eu quero te dar duas dicas:

Pinguim com placa de dica Dica #1 – A opção -v do cat mostra os caracteres de controle invisíveis, com a notação ^L, onde ^ é a tecla control e L é a respectiva letra. A opção -e mostra o final da linha como um cifrão ($).
Pinguim com placa de dica Dica #2 – Isto ocorre porque no formato DOS (ou rwin), o fim dos registros é formado por um carriage-return (r) e um line-feed (n). No Linux porém o final do registro tem somente o line-feed.

Vamos então 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 opção -d do tr remove o caractere especificado de todo o arquivo. Desta forma eu removi os caracteres indesejados salvando em um arquivo de trabalho e posteriormente renomeei-o para a sua designação original.
Obs: No Unix eu deveria fazer:

$ tr -d ’15’ < ArqDoDOS.txt > /tmp/$$
Pinguim com placa de atenção Isto aconteceu porque o ftp foi feito do modo binário (ou image), isto é, sem a interpretação do texto. Se antes da transmissão do arquivo tivesse sido estipulada a opção ascii do ftp, isto não teria ocorrido.

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

– Pois é, ainda não te falei quase nada sobre programação em Shell, ainda tem muita coisa para aprender, mas com o que aprendeu, já dá para resolver muitos problemas, desde que você adquira o “modo Shell de pensar”. Você seria capaz de fazer um script para me dizer quais são as pessoas que estão “logadas” há mais de um dia no seu servidor?

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

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

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

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

E veja também o date:

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

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

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

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

$ LANG=pt_BR date
Seg Set 20 10:47:19 BRT 2004

Desta forma passando a saída do comando date para português.

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

$ Data=$(date | cut -c 5-10)

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

$ echo $Data
Sep 20

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

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

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

– Claro, é a opção -v

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

$ who | grep -v “$Data”
jneves pts/1 Sep 18 13:40

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

$ who | grep -v “$Data” | cut -f1 -d ‘ ‘
jneves

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

Comandos Condicionais

Veja as linhas de comando a seguir:

$ ls musicas
musicas

$ echo $?
0

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

$ echo $?
1

$ who | grep jneves
jneves pts/1 Sep 18 13:40 (10.2.4.144)

$ echo $?
0

$ who | grep juliana
$ echo $?

1

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

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

O Comando if

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

    if cmd
    then
        cmd1
        cmd2
        cmdn
    else
        cmd3
        cmd4
        cmdm
    fi

ou seja: caso comando cmd tenha sido executado com sucesso, os comandos do bloco do then (cmd1, cmd2 e cmdn) serão executados, caso contrário, os comandos executados serão os do bloco opcional do else (cmd3, cmd4 e cmdm), terminando com um fi.

Vamos ver na prática como isso funciona usando um scriptizinho que serve para incluir usuários no /etc/passwd:

$ cat incusu
#!/bin/bash
# Versão 1
if grep ^$1 /etc/passwd
then
echo Usuario ‘$1’ já existe
else
if useradd $1
then
echo Usuário ‘$1’ incluído em /etc/passwd
else
echo “Problemas no cadastramento. Você é root?”
fi
fi

Repare que o if está testando direto o comando grep e esta é a sua finalidade. Caso o if seja bem sucedido, ou seja, o usuário (cujo nome está em $1) foi encontrado em /etc/passwd, os comandos do bloco do then serão executados (neste exemplo é somente o echo) e caso contrário, as instruções do bloco do else é que serão executadas, quando um novo if testa se o comando useradd foi executado a contento, criando o registro do usuário em /etc/passwd, ou não quando dará a mensagem de erro.
Vejamos sua execução, primeiramente passando um usuário já cadastrado:

$ incusu jneves
jneves:x:54002:1001:Julio Neves:/home/jneves:/bin/bash
Usuario ‘jneves’ ja existe

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

$ cat incusu
#!/bin/bash
# Versão 2
if grep ^$1 /etc/passwd > /dev/null # ou: if grep -q ^$1 /etc/passwd
then
echo Usuario ‘$1’ já existe
else
if useradd $1
then
echo Usuário ‘$1’ incluído em /etc/passwd
else
echo “Problemas no cadastramento. Você é root?”
fi
fi

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

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

Epa, aquele erro não era para acontecer! Para evitar que isso aconteça devemos mandar também a saída de erro (strerr, lembra?) do useradd para /dev/null, ficando na versão final assim:

$ cat incusu
#!/bin/bash
# Versão 3
if grep ^$1 /etc/passwd > /dev/null
then
echo Usuario ‘$1’ já existe
else
if useradd $1 2> /dev/null
then
echo Usuário ‘$1’ incluído em /etc/passwd
else
echo “Problemas no cadastramento. Você é root?”
fi
fi

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

$ incusu botelho
Usuário ‘botelho’ incluido em /etc/passwd

E novamente:

$ incusu botelho
Usuário ‘botelho’ já existe

Lembra que eu falei que ao longo dos nossos papos e chopes os nossos programas iriam se aprimorando? Então vejamos agora como poderíamos melhorar o nosso programa para incluir músicas:

$ cat musinc
#!/bin/bash
# Cadastra CDs (versao 3)
#
if grep “^$1$” musicas > /dev/null
then
echo Este álbum já está cadastrado
else
echo $1 >> musicas
sort musicas -o musicas
fi

Como você viu, é uma pequena evolução da versão anterior, assim, antes de incluir um registro (que pela versão anterior poderia ser duplicado), testamos se o registro começava (^) e terminava ($) igual ao parâmetro passado ($1). O uso do circunflexo (^) no início da cadeia e cifrão ($) no fim, são para testar se o parâmetro passado (o álbum e seus dados) são exatamente iguais a algum registro anteriormente cadastrado e não somente igual a um pedaço de algum dos registros.
Vamos executá-lo passando um álbum já cadastrado:

$ musinc “album 4^Artista7~Musica7:Artista8~Musica8”
Este álbum já está cadastrado

E agora um não cadastrado:

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

– Como você viu, o programa melhorou um pouquinho, mas ainda não está pronto. À medida que eu for te ensinando a programar em shell, nossa CDteca irá ficando cada vez melhor.

– Entendi tudo que você me explicou, mas ainda não sei como fazer um if para testar condições, ou seja o uso normal do comando.

– Cara, para isso existe o comando test, ele é que testa condições. O comando if testa o comando test. Mas isso está meio confuso e como já falei muito, estou precisando de uns chopes para molhar a palavra. Vamos parando por aqui e na próxima vez te explico direitinho o uso do test e de diversas outras sintaxes do if.

– Falou! Acho bom mesmo porque eu também já tô ficando zonzo e assim tenho tempo para praticar esse monte de coisas que você me falou hoje.

– Para fixar o que você aprendeu, tente fazer um scriptizinho para informar se um determinado usuário, que será passado como parâmetro esta logado (arghh!) ou não.

– Aê Chico, mais dois chopes por favor…

Vou aproveitar também para mandar o meu jabá: diga para os amigos que quem estiver afim de fazer um curso porreta de programação em Shell que mande um e-mail para a nossa gerencia de treinamento para informar-se.

Qualquer dúvida ou falta de companhia para um chope ou até para falar mal dos políticos é só mandar um e-mail para mim.

Valeu!

Read Full Post »

Tira Gosto

Shell em pedaços pequenos e gostosos


Em construção para sempre


Esta página, apesar de estar no escopo do Papo de Botequim, nunca foi publicada na Linux Magazine. Trata-se de artigos que escrevi para outras mídias, dicas úteis que li pela internet afora (e neste caso com os devidos créditos), contribuições deste pessoal do Software Livre, maravilhoso e sempre pronto a ajudar e da imperdível Lista de Shell Script



Passando parâmetros com xargs

Existe um comando, cuja função primordial é construir listas de parâmetros e passá-la para a execução de outros programas ou instruções. Este comando é o xargs e deve ser usado da seguinte maneira:

    xargs [comando [argumento inicial]]

xargs combina o argumento inicial com os argumentos recebidos da entrada padrão, de forma a executar o comando especificado uma ou mais vezes.

Exemplo:

Vamos procurar em todos os arquivos abaixo de um determinado diretório uma cadeia de caracteres usando o comando find com a opção -type f para pesquisar somente os arquivos normais, desprezando diretórios, arquivos especiais, arquivos de ligações, etc, e vamos torná-la mais genérica recebendo o nome do diretório inicial e a cadeia a ser pesquisada como parâmetros. Para isso fazemos:

$ cat grepr
#
# Grep recursivo
# Pesquisa a cadeia de caracteres definida em $2 a partir do diretorio $1
#
find $1 -type f -print|xargs grep -l “$2”

Na execução deste script procuramos, a partir do diretório definido na variável $1, todos os arquivos que continham a cadeia definida na variável $2.

Exatamente a mesma coisa poderia ser feito se a linha do programa fosse a seguinte:

    find $1 -type f -exec grep -l "$2" {} \;

O primeiro processo tem duas grandes desvantagens sobre o anterior:

  • A primeira é bastante visível: o tempo de execução deste método é muito superior ao daquele, isso porque o grep será feito em cada arquivo que lhe for passado pelo find, um-a-um, ao passo que com o xargs, será passada toda, ou na pior das hipóteses, a maior parte possível, da lista de arquivos gerada pelo find;
  • Dependendo da quantidade de arquivos encontrados que atendem ao find, poderemos ganhar aquela famosa e fatídica mensagem de erro Too many arguments indicando um estouro da pilha de execução do grep. Como foi dito no item anterior, se usarmos o xargs ele passará para o grep a maior quantidade de parâmetros possível, suficiente para não causar este erro, e caso necessário executará o grep mais de uma vez.
Pinguim com placa de atenção Aê pessoal do linux que usa o ls colorido que nem porta de tinturaria: nos exemplos a seguir que envolvem esta instrução, você devem usar a opção --color=none, senão existem grandes chances dos resultados não ocorrerem como o esperado.

Vamos agora analisar um exemplo que é mais ou menos o inverso deste que acabamos de ver. Desta vez, vamos fazer um script para remover todos os arquivos do diretório corrente, pertencentes a um determinado usuário.

A primeira idéia que surge é, como no caso anterior, usar um comando find, da seguinte maneira:

    find . -user cara -exec rm -f {} \;

Quase estaria certo, o problema é que desta forma você removeria não só os arquivos do cara no diretório corrente, mas também de todos os outros subdiretórios “pendurados” neste. Vejamos então como fazer:

    ls -l | grep " cara " | cut -c55- | xargs rm

Desta forma, o grep selecionou os arquivos que continham a cadeia cara no diretório corrente listado pelo ls -l. O comando cut pegou somente o nome dos arquivos, passando-os para a remoção pelo rm usando o comando xargs como ponte

O xargs é também uma excelente ferramenta de criação de one-liners (scripts de somente uma linha). Veja este para listar todos os donos de arquivos (inclusive seus links) “pendurados” no diretório /bin e seus subdiretórios.

$ find /bin -type f -follow | \
xargs ls -al | tr -s ‘ ‘ | cut -f3 -d’ ‘ | sort -u

Muitas vezes o /bin é um link (se não me engano, no Solaris o é) e a opção -follows obriga o find a seguir o link. O comando xargs alimenta o ls -al e a seqüência de comandos seguinte é para pegar somente o 3º campo (dono) e classificá-lo devolvendo somente uma vez cada dono (opção -u do comando sort, que equivale ao comando uniq).

Opções do xargs

Você pode usar as opções do xargs para construir comandos extremamente poderosos.

Opção -i

Para exemplificar isso e começar a entender as principais opções desta instrução, vamos supor que temos que remover todos as arquivos com extensão .txt sob o diretório corrente e apresentar os seus nomes na tela. Veja o que podemos fazer:

$ find . -type f -name “*.txt” | \
xargs -i bash -c “echo removendo {}; rm {}”

A opção -i do xargs troca pares de chaves ({}) pela cadeia que está recebendo pelo pipe (|). Então neste caso as chaves ({}) serão trocadas pelos nomes dos arquivos que satifaçam ao comando find.

Opção -n

Olha só a brincadeira que vamos fazer com o xargs:

$ ls | xargs echo > arq.ls
$ cat arq.ls
arq.ls arq1 arq2 arq3

$ cat arq.ls | xargs -n1
arq.ls
arq1
arq2
arq3

Quando mandamos a saída do ls para o arquivo usando o xargs, comprovamos o que foi dito anteriormente, isto é, o xargs manda tudo que é possível (o suficiente para não gerar um estouro de pilha) de uma só vez. Em seguida, usamos a opção -n 1 para listar um por vez. Só para dar certeza veja o exemplo a seguir, quando listaremos dois em cada linha:

$ cat arq.ls | xargs -n 2
arq.ls arq1
arq2 arq3

Mas a linha acima poderia (e deveria) ser escrita sem o uso de pipe (|), da seguinte forma:

$ xargs -n 2 < arq.ls

Opção -p

Outra opção legal do xargs é a -p, na qual o sistema pergunta se você realmente deseja executar o comando. Digamos que em um diretório você tenha arquivos com a extensão .bug e .ok, os .bug estão com problemas que após corrigidos são salvos como .ok. Dá uma olhadinha na listagem deste diretório:

$ ls dir
arq1.bug
arq1.ok
arq2.bug
arq2.ok

arq9.bug
arq9.ok

Para comparar os arquivos bons com os defeituosos, fazemos:

$ ls | xargs -p -n2 diff -c
diff -c arq1.bug arq1.ok ?…y
….
diff -c arq9.bug arq9.ok ?…y

Opção -t

Para finalizar, o xargs também tem a opção -t, onde vai mostrando as instruções que montou antes de executá-las. Gosto muito desta opção para ajudar a depurar o comando que foi montado.

Resumo

Então podemos resumir o comando de acordo com a tabela a seguir:

Opção Ação
-i Substitui o par de chaves ({}) pelas cadeias recebidas
-nNum Manda o máximo de parâmetros recebidos, até o máximo de Num para o comando a ser executado
-lNum Manda o máximo de linhas recebidas, até o máximo de Num para o comando a ser executado
-p Mostra a linha de comando montada e pergunta se deseja executá-la
-t Mostra a linha de comando montada antes de executá-la

Here Strings

Acabei de dar um treinamento no Instituto de Pesquisas Eldorado, onde peguei uma turma de 12 alunos de altíssima qualidade. Devido ao preparo do pessoal, o nível do curso foi tão bom que cheguei a abordar alguns conceitos pouco usados como o redirecionamento por here strings. Então pensei: se é uma coisa tão útil, porque não mostrá-la a todos? Ai vai …

Primeiro um programador com complexo de inferioridade criou o redirecionamento de entrada e representou-o com um sinal de menor (<) para representar seus sentimento. Em seguida, outro sentindo-se pior ainda, criou o here document representando-o por dois sinais de menor (<<) porque sua fossa era maior. O terceiro, pensou: “estes dois não sabem o que é estar por baixo”… Então criou o here strings representado por três sinais de menor (<<<).

Brincadeiras a parte, o here strings é utilíssimo e, não sei porque, é um perfeito desconhecido. Na pouquíssima literatura que há sobre o tema, nota-se que o here strings é freqüentemente citado como uma variante do here document, teoria com a qual discordo pois sua aplicabilidade é totalmente diferente daquela. Sua sintaxe é simples:

    $ comando <<< $cadeia

Onde cadeia é expandida e alimenta a entrada primária (stdin) de comando.

Como sempre, vamos direto aos exemplos dos dois usos mais comuns para que vocês próprios tirem suas conclusões.

  • Uso #1. Substituindo a famigerada construção echo "cadeia" | comando, que força um fork, criando um subshell e onerando o tempo de execução.

Exemplos:

$ a=”1 2 3″
$ cut -f 2 -d ‘ ‘ <<< $a # Normalmente faz-se: echo $a | cut -f 2 -d ‘ ‘
2

$ echo $NomeArq
Meus Documentos
# Arrrghhh!
$ tr “A-Z ” “a-z_” <<< $NomeArq # Substituindo o echo $NomeArq | tr “A-Z ” “a-z_”
meus_documentos

$ bc <<<“3 * 2”
6

$ bc <<<“scale = 4; 22 / 7”
3.1428

Para mostrar a melhoria no desempenho, vamos fazer um loop de 500 vezes usando o exemplo dados para o comando tr: Veja agora esta seqüência de comandos com medidas de tempo:

$ time for ((i=1; i<= 500; i++)); { tr “A-Z ” “a-z_” <<< $NomeArq >/dev/null; }

real 0m3.508s
user 0m2.400s
sys 0m1.012s

$ time for ((i=1; i<= 500; i++)); { echo $NomeArq | tr “A-Z ” “a-z_” >/dev/null; }

real 0m4.144s
user 0m2.684s
sys 0m1.392s

Veja agora esta seqüência de comandos com medidas de tempo:

$ time for ((i=1;i<=100;i++)); { who | cat > /dev/null; }

real 0m1.435s
user 0m1.000s
sys 0m0.380s

$ time for ((i=1;i<=100;i++)); { cat <(who) > /dev/null; }

real 0m1.552s
user 0m1.052s
sys 0m0.448s

$ time for ((i=1;i<=100;i++)); { cat <<< $(who) > /dev/null; }

real 0m1.514s
user 0m1.056s
sys 0m0.412s

Observando este quadro você verá que no primeiro usamos a forma convencional, no segundo usamos um named pipe temporário para executar uma substituição de processos e no terceiro usamos here strings. Notará também que ao contrário do exemplo anterior, aqui o uso de here strings não foi o mais veloz. Mas repare bem que neste último caso o comando who está sendo executado em um subshell e isso onerou o processo como um todo.

Vejamos uma forma rápida de inserir uma linha como cabeçalho de um arquivo:

$ cat num
1 2
3 4
5 6
7 8
9 10

$ cat – num <<< “Impares Pares”
Impares Pares
1 2
3 4
5 6
7 8
9 10
  • Uso #2. Outra forma legal de usar o here strings é casando-o com um comando read, não perdendo de vista o que aprendemos sobre IFS (veja aqui, na explicação do comando for). O comando cat com as opções -vet mostra o <ENTER> como $, o <TAB> como ^I e os outros caracteres de controle com a notação ^L onde L é uma letra qualquer. Vejamos então o conteúdo de uma variável e depois vamos ler cada um de seus campos:

Exemplos:

$ echo “$Linha”
Leonardo Mello (21)3313-1329

$ cat -vet <<< “$Linha”
Leonardo Mello^I(21)3313-1329$
# Os separadores sao branco e <TAB> (^I)
$ read Nom SNom Tel <<< “$Linha”
$ echo “${Nom}_$S{Nom}_$Tel” # Vamos ver se ele leu cada um dos campos
Leonardo_Mello_(21)3313-1329
# Leu porque os separadores casavam com o IFS

Também podemos ler direto para um vetor (array) veja:

$ echo $Frutas
Pera:Uva:Maçã

$ IFS=:
$ echo $Frutas
Pera Uva Maçã
# Sem as aspas o shell mostra o IFS como branco
$ echo “$Frutas”
Pera:Uva:Maçã
# Ahhh, agora sim!
$ read -a aFrutas <<< “$Frutas” # A opção -a do read, lê para um vetor
$ for i in 0 1 2
> do
> echo ${aFrutas[$i]} # Imprimindo cada elemento do vetor
> done
Pera
Uva
Maçã

Rotatório Peczenyj

Estava, como faço todo dia, dando um lida nos e-mails da “Lista de Shell Script” , quando vi uma “descoberta” totalmente inusitada do Tiago Barcellos Peczenyj. Quando resolvi montar esta coletânea de dicas, me lembrei disso e pedi-lhe para me encaminhar aquele e-mail novamente. O texto a a seguir é o e-mail que ele me mandou, só inseri o último exemplo e tirei as abreviaturas.

Julio descobri uma forma para o Shell criar combinaçãoes fazendo rotação com os elementos estipulados. Podemos gerar todos os binários de 0000 a 1111 da seguinte forma:

$ A={0,1}
$ eval echo $A$A$A$A
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111

Uma aplicação pratica que vejo é para combinar valores diferentes sem ter que encadear loops nem usar o seq

$ A={`seq -s , -f “_%g” 3`}
$ eval echo -e $A$A$A |tr ‘ _’ ‘\n ‘ | grep -vE ‘.+?(\b[0-9]+\b).+?\1’
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

Neste caso eu combinei os números de 1 a 3 eliminando repetições com o grep. Usei um tr ‘podre’ para melhor tratar os dados, saltando linha.

O grep é simples como você pode notar, eu vejo se uma determinada parte da combinação (.+?(\b[0-9]+\b).+?) existe em outra parte (\1), se existe eu não deixo imprimir por causa da opção -v, assim

    1 1 2
    1 2 1
    1 1 1

não serão impressos. Agora vai o meu exemplo: o one-liner a seguir gerará todas as permissões possíveis (em octal) para o arquivo arq (o exemplo foi interrompido porque existem 512 combinações de permissões possíveis).

$ A={`seq -s , 0 7`}
$ eval echo -e $A$A$A | tr ‘ ‘ ‘\n’ | xargs -i bash -c “chmod {} arq; ls -l arq”
———- 1 julio julio 100 2006-11-27 11:50 arq
———x 1 julio julio 100 2006-11-27 11:50 arq
——–w- 1 julio julio 100 2006-11-27 11:50 arq
——–wx 1 julio julio 100 2006-11-27 11:50 arq
——-r– 1 julio julio 100 2006-11-27 11:50 arq
——-r-x 1 julio julio 100 2006-11-27 11:50 arq
——-rw- 1 julio julio 100 2006-11-27 11:50 arq
——-rwx 1 julio julio 100 2006-11-27 11:50 arq
. . . . . . . . . . . . . . . . . .
-rwxrwxrw- 1 julio julio 100 2006-11-27 11:50 arq
-rwxrwxrwx 1 julio julio 100 2006-11-27 11:50 arq

Vamos ver este exemplo passo-a-passo para entendê-lo:

$ echo $A
{0,1,2,3,4,5,6,7}

$ eval echo -e $A$A$A
000 001 002 003 004 005 006 007 010 … … 767 770 771 772 773 774 775 776 777

$ eval echo -e $A$A$A | tr ‘ ‘ ‘\n’ # O tr trocará cada espaco em branco por um <ENTER>
000
001
002
003
. . .
774
775
776
777

A seguir o xargs (clique para dicas do xargs) executa o comando bash -c (que serve para executar uma linha de comandos) que por sua vez executa o chmod e o ls -l para mostrar que as permissões estão sendo alteradas.

Aritmética em Shell

Antigamente usávamos o comando expr para fazer operações aritméticas e muita gente ainda usa, pois é compatível com quaquer ambiente.

Exemplo:

$ expr 7 \* 5 / 3 # 7 vezes 5 = 35 dividido por 3 = 11
14

Neste artigo porém, vamos ver outras formas não tanto conhecidas, porém mais simples de usar, mais elaboradas e com precisão maior.

O uso do bc

Uma forma bacana de fazer cálculos em Shell – usada normalmente quando a expressão aritmética é mais complexa, ou quando é necessário trabalharmos com casas decimais – é usar a instrução calculadora do UNIX/LINUX. O bc. Veja como: Exemplo:

$ echo “(2 + 3) * 5” | bc # Parênteses usados para dar precedência
25

Para trabalhar com números reais (números não necessariamente inteiros), especifique a precisão (quantidade de decimais) com a opção scale do comando bc. Assim vejamos o penúltimo exemplo:

$ echo “scale=2; 7*5/3” | bc
11.66

Outros exemplos:

$ echo “scale=3; 33.333*3” | bc
99.999

$ num=5
$ echo “scale=2; ((3 + 2) * $num + 4) / 3” | bc
9.66

Obviamente todos os exemplos acima no caso de linux, poderiam (e deveriam) ser escritos usando Here Strings. Veja os últimos como ficariam:

$ bc <<< “scale=3; 33.333*3”
99.999

$ num=5
$ bc <<< “scale=2; ((3 + 2) * $num + 4) / 3”
9.66

Uma vez apareceu na lista (excelente por sinal) de Shell script no Yahoo (http://br.groups.yahoo.com/group/shell-script/) um cara com a seguinte dúvida: “eu tenho um arquivo cujos campos estão separados por e o terceiro deles possui números. Como posso calcular a soma de todos os números desta coluna do arquivo?”

Mandei a seguinte resposta:

$ echo $(cut -f3 num | tr ‘\n’ +)0 | bc
20.1

Vamos por partes para entender melhor e primeiramente vamos ver como era o arquivo que fiz para teste:

$ cat num
a b 3.2
a z 4.5
w e 9.6
q w 2.8

Como pode-se ver, está dentro do padrão do problema, onde eu tenho como terceiro campo números reais. Vamos ver o que faria a primeira parte da linha de comandos, onde eu transformo os caracteres (new-line) em um sinal de mais (+):

$ cut -f3 num | tr ‘\n’ +
3.2+4.5+9.6+2.8+

Se eu mandasse desse jeito para o bc, ele me devolveria um erro por causa daquele sinal de mais (+) solto no final do texto. A minha saída foi colocar um zero no final, pois somando zero o resultado não se alterará. Vamos ver então como ficou:

$ echo $(cut -f3 num | tr -s ‘\n’ +)0
3.2+4.5+9.6+2.8+0

Isso é o que se costuma chamar one-liner, isto é, códigos que seriam complicados em outras linguagens (normalmente seria necessário criar contadores e fazer um loop de leitura somando o terceiro campo ao contador) e em Shell são escritos em uma única linha.

Há também gente que chama isso de método KISS, que é o acrônimo de Keep It Simple Stupid. smile

Mas o potencial de uso desta calculadora não se encerra aí, existem diversas facilidades por ela propiciadas. Veja só este exemplo:

$ echo “obase=16; 11579594” | bc
B0B0CA

$ echo “ibase=16; B0B0CA” | bc # B, zero, B, zero, C, e A
11579594

Nestes exemplos vimos como fazer mudanças de base de numeração com o uso do bc. Na primeira explicitamos a base de saída (obase) como 16 (hexadecimal) e na segunda, dissemos que a base da entrada (ibase) era 10 (decimal).

Outras formas de trabalhar com inteiros

Outra forma muito legal de fazer cálculos é usar a notação $((exp aritmética)). É bom ficar atento, porém, ao fato desta sintaxe não ser universalizada. O Bourne Shell (sh), por exemplo, não a reconhece.

Exemplo:

Usando o mesmo exemplo que já havíamos usado:

$ echo $(((2+3)*5)) # Os parênteses mais internos priorizaram o 2+3
25

Agora olha só esta maluquice:

$ tres=3
$ echo $(((2+tres)*5)) # Variável tres não precedida pelo $
25

$ echo $(((2+$tres)*5)) # Variável tres precedida pelo $
25

Ué!! Não é o cifrão ($) precedente que caracteriza uma variável? Sim, porém em todos os sabores UNIX que testei, sob bash ou ksh, ambas as formas de construção produzem uma boa aritmética.

Preste a atenção nesta seqüência:

$ unset i # $i mooorreu!
$ echo $((i++))
0

$ echo $i
1

$ echo $((++i))
2

$ echo $i
2

Repare que apesar da variável não estar definida, pois foi feito um unset nela, nenhum dos comandos acusou erro, porque, como estamos usando construções aritméticas, sempre que uma variável não existe, é inicializada com zero (0).

Repare que o i++ produziu zero (0). Isto ocorre porque este tipo de construção chama-se pós-incrementação, isto é, primeiramente o comando é executado e só então a variável é incrementada. No caso do ++i, foi feita uma pré-incrementação: primeiro incrementou e somente após o comando foi executado.

Também são válidos:

$ echo $((i+=3))
5

$ echo $i
5

$ echo $((i*=3))
15

$ echo $i
15

$ echo $((i%=2))
1

$ echo $i
1

Estas três operações seriam o mesmo que:

    i=$((i+3))
    i=$((i*3))
    i=$((i%2))

E isto seria válido para todos os operadores aritméticos o que em resumo produziria a tabela a seguir:

Expansão Aritmética
Expressão Resultado
id++ id-- pós-incremento e pós-decremento de variáveis
++id -–id pré-incremento e pré-decremento de variáveis
** exponenciação
* / % multiplicação, divisão, resto da divisão
+ - adição, subtração
<= >= < > comparação
== != igualdade, desigualdade
&& E lógico
|| OU lógico

Mas o auge desta forma de construção com duplo parênteses é o seguinte:

$ echo $var
50

$ var=$((var>40 ? var-40 : var+40))
$ echo $var
10

$ var=$((var>40 ? var-40 : var+40))
$ echo $var
50

Este tipo de construção deve ser lido da seguinte forma: caso a variável var seja maior que 40 (var>40), então (?) faça var igual a var menos 40 (var-40), senão (:) faça var igual a var mais 40 (var+40). O que quis dizer é que os caracteres ponto-de-interrogação (?) e dois-pontos (:) fazem o papel de “então” e “senão”, servindo desta forma para montar uma operação aritmética condicional.

Da mesma forma que usamos a expressão $((...)) para fazer operações aritméticas, também poderíamos usar a intrínseca (built-in) let ou construção do tipo $[...].

Os operadores são os mesmos para estas três formas de construção, o que varia um pouco é a operação aritmética condicional com o uso do let. Vejamos como seria:

$ echo $var
50

$ let var=’var>40 ? var-40 : var+40′
$ echo $var
10

$ let var=’var>40 ? var-40 : var+40′
$ echo $var
50

Baseando

Se você quiser trabalhar com bases diferentes da decimal, basta usar o formato:

    base#numero

Onde base é um número decimal entre 2 e 64 representando o sistema de numeração, e numero é um número no sistema numérico definido por base. Se base# for omitida, então 10 é assumida como default. Os algarismos maiores que 9 são representados por letras minúsculas, maiúsculas, @ e _, nesta ordem.

Se base for menor ou igual a 36, maiúsculas ou minúsculas podem ser usadas indiferentemente para definir algarismos maiores que 9 (não está mal escrito, os algarismos do sistema hexadecimal, por exemplo, variam entre 0 (zero) e F). Vejamos como isso funciona:

$ echo $[2#11]
3

$ echo $((16#a))
10

$ echo $((16#A))
10

$ echo $((2#11 + 16#a))
13

$ echo $[64#a]
10

$ echo $[64#A]
36

$ echo $((64#@))
62

$ echo $((64#_))
63

Nestes exemplos usei as notações $((...)) e $[...] indistintamente, para demonstrar que ambas funcionam.

Funciona também uma mudança automática para a base decimal, desde que você esteja usando a convenção numérica do C, isto é, em 0xNN, o NN será tratado como um hexadecimal e em 0NN, o NN será visto como um octal. Veja o exemplo:

Exemplo

$ echo $((10)) # decimal
10

$ echo $((010)) # octal
8

$ echo $((0x10)) # hexadecimal
16

$ echo $((10+010+0x10)) # Decimal + octal + hexadecimal
64

Ah, já ia me esquecendo! As expressões aritméticas com os formatos $((...)), $[...] e com o comando let usam os mesmos operadores usados na instrução expr, além dos operadores unários (++, --, +=, *=, ...) e condicionais que acabamos de ver.

Testes usando expressões regulares

No Papo de Botequim 004, nós falamos tudo sobre comandos condicionais, mas faltou um que não existia àquela época. Neste mesmo Papo de Botequim, na seção E tome de test nós chegamos a falar de uma construção do tipo:

    [[ Expressao ]] && cmd

Onde o comando cmd será executado caso a expressão condicional Expressao seja verdadeira. Disse ainda que Expressao poderia ser estipulada de acordo com as regras de Geração de Nome de Arquivos (File Name Generation). A partir do bash versão 3, foi incorporado a esta forma de teste um operador representado por =~, cuja finalidade é fazer comparações com Expressões Regulares.

Exemplo:

$ echo $BASH_VERSION # Conferindo se a versão do Bash é igual ou superior a 3.0.0
3.2.17(15)-release

$ Cargo=Senador
$ [[ $Cargo =~ ^(Governa|Sena|Verea)dora?$ ]] && echo É político
É político

$ Cargo=Senadora
$ [[ $Cargo =~ ^(Governa|Sena|Verea)dora?$ ]] && echo É político
É político

$ Cargo=Diretor
$ [[ $Cargo =~ ^(Governa|Sena|Verea)dora?$ ]] && echo É político
$

Vamos dar uma esmiuçada na Expressão Regular ^(Governa|Sena|Verea)dora?$: ela casa com tudo que começa (^) por Governa, ou (|) Sena, ou (|) Verea, seguido de dor e seguido de um a opcional (?). O cifrão ($) serve para marcar o fim. Em outras palavras esta Expressão Regular casa com Governador, Senador, Vereador, Governadora, Senadora e Vereadora.

Colorindo a tela

Como você já havia visto no Papo de Botequim 007, o comando tput serve para fazer quase tudo referente a formatação de tela, mas o que não foi dito é que com ele também pode-se usar cores de frente (dos caracteres) e de fundo. Existem também outras formas de fazer o mesmo, acho porém, esta que veremos agora, mais intuitiva (ou menos “desintuitiva”). A tabela a seguir mostra os comandos para especificarmos os padrões de cores de frente (foreground) ou de fundo (background):

Obtendo cores com o comando tput
Comando Efeito
tput setaf n Especifica n como a cor de frente (foreground)
tput setab n Especifica n como a cor de fundo (background)

Bem, agora você já sabe como especificar o par de cores, mas ainda não sabe as cores. A tabela a seguir mostra os valores que o n (da tabela anterior) deve assumir para cada cor:

Valores das cores com o comando tput
Valor Cor
0 Preto
1 Vermelho
2 Verde
3 Marrom
4 Azul
5 Púrpura
6 Ciano
7 Cinza claro

Neste ponto você já pode começar a brincar com as cores.

– Mas peraí, ainda são muito poucas!

– É, tem toda razão… O problema é que ainda não lhe disse que se você colocar o terminal em modo de ênfase (tput bold), estas cores geram outras oito. Vamos montar então a tabela definitiva de cores:

Valores das cores com o comando tput
Valor Cor Cor após tput bold
0 Preto Cinza escuro
1 Vermelho Vermelho claro
2 Verde Verde claro
3 Marron Amarelo
4 Azul Azul Brilhante
5 Púrpura Rosa
6 Ciano Ciano claro
7 Cinza claro Branco

Exemplo

Como exemplo, vejamos um script que mudará a cor de sua tela de acordo com sua preferência.

$ cat mudacor.sh
#!/bin/bash
tput sgr0
clear

# Carregando as 8 cores básicas para um vetor
Cores=(Preto Vermelho Verde Marrom Azul Púrpura Ciano “Cinza claro”)

# Listando o menu de cores
echo ”
Opc Cor
= ===”
# A linha a seguir significa: para i começando de 1;
#+ enquanto i menor ou igual ao tamanho do vetor Cores;
#+ incremente o valor de i de 1 em 1
for ((i=1; i<=${#Cores[@]}; i++))
{
printf “%02d %s\n” $i “${Cores[i-1]}”
}

CL=
until [[ $CL == 0[1-8] || $CL == [1-8] ]]
do
read -p ”
Escolha a cor da letra: ” CL
done

# Para quem tem bash a partir da versao 3.2
#+ o test do until acima poderia ser feito
#+ usando-se Expressoes Regulares. Veja como:
#+ until [[ $CL =~ 0?[1-8] ]]
#+ do
#+ read -p ”
#+ Escolha a cor da letra: ” CL
#+ done

CF=
until [[ $CF == 0[1-8] || $CF == [1-8] ]]
do
read -p ”
Escolha a cor de fundo: ” CF
done

let CL– ; let CF– # Porque as cores variam de zero a sete
tput setaf $CL
tput setab $CF
clear

Ganhando o jogo com mais coringas

Estava eu lendo meus e-mails quando recebo um do Tiago enviado para a lista de Shell Script (já falei da lista e do Tiago no Rotatório Peczenyj). A seguir o conteúdo do e-mail:

Não sei se é conhecimento de todos mas o shell possui, alem do globbing normal (a expansão *, ? e [a-z] de nomes de arquivos e diretórios), um globbing extendido.

Acho que, em alguns casos, podera ser BEM util, eliminando um pipe para um grep por exemplo.

São eles:

    ?(padrao)

Casa zero ou uma ocorrência de um determinado padrao

    *(padrao)

Casa zero ou mais ocorrências de um determinado padrao

    +(padrao)

Casa uma ou mais ocorrências de um determinado padrao

    @(padrao)

Casa com exatamente uma ocorrência de um determinado padrao

    !(padrao)

Casa com qualquer coisa, exceto com padrao Para poder utilizá-lo precisa executar o shopt conforme o exemplo abaixo:

$ shopt -s extglob
$ ls
file filename filenamename fileutils

$ ls file?(name)
file filename

$ ls file*(name)
file filename filenamename

$ ls file+(name)
filename filenamename

$ ls file@(name)
filename

$ ls file!(name) # divertido esse
file filenamename fileutils

$ ls file+(name|utils)
filename filenamename fileutils

$ ls file@(name|utils) # “lembra” um {name,utils}
filename fileutils

Usando o awk para pesquisar por equivalência

Aí vai mais uma que o Tiago mandou para a lista de Shell Script do Yahoo (já falei da lista e do Tiago no Rotatório Peczenyj e no Ganhando o jogo com mais coringa)

Quem ja não passou por isso: Procurar uma palavra, porém uma letra acentuada, ou não, atrapalhou a busca?

Não descobri como fazer o grep ou sed aceitarem algo semelhante, mas o gawk aceita classes de equivalência!

Melhor explicar com um exemplo, onde vou listar o número da linha e a ocorrência casada:

$ cat dados
éco
eco
èco
êco
ëco
eço

$ awk ‘/^eco/{print NR,$1}’ dados
2 eco

$ awk ‘/^e[[=c=]]o/{print NR,$1}’ dados
2 eco
6 eço

$ awk ‘/^[[=e=]]co/{print NR,$1}’ dados
1 éco
2 eco
3 èco
4 êco
5 ëco

Ou seja, usar [=X=] permite que a expressão encontre a letra X estando acentuada ou não (é sensivel à localização corrente!).

A sintaxe é parecida com a das classes POSIX, trocando os dois-pontos (:) antes e depois da classe por sinais de igual (=).

Achei curioso e deve servir para algum caso semelhante ao descrito.

find – Procurando arquivo por características

Se você está como o Mr. Magoo, procurando em vão um arquivo, use o comando find que serve para procurar arquivos não só pelo nome, como por diversas outras características. Sua sintaxe é a seguinte:

    find [caminho ...] expressão [ação]

Parâmetros:

caminho Caminhos de diretório a partir do qual (porque ele é recursivo, sempre tentará entrar pelos subdiretórios “pendurados” neste) irá procurar pelos arquivos;
expressão Define quais critérios de pesquisa. Pode ser uma combinação entre vários tipos de procura;
ação Define que ação executar com os arquivos que atender aos critérios de pesquisa definidos por expressão.

Os principais critérios de pesquisa definidos por expressão são:

-name Procura arquivos que tenham o nome especificado. Aqui podem ser usados metacaracteres ou caracteres curingas, porém estes caracteres deverão estar entre aspas, apóstrofos ou imediatamente precedidos por uma contrabarra isso porque quem tem de expandir os coringas é o find. Se fosse o Shell que os expandisse, isto seria feito somente com relação ao diretório corrente, o que jogaria por terra a característica recursiva do find;
-user Procura arquivos que tenham usuário como dono;
-group Procura arquivos que tenham grupo como grupo dono;
-type c Procura por arquivos que tenham o tipo c, correspondente à letra do tipo do arquivo. Os tipos aceitos estão na tabela a seguir:
  Valores de c Tipo de arquivo procurado
  b Arquivo especial acessado a bloco
  c Arquivo especial acessado a caractere
  d Diretório
  p Named pipe (FIFO)
  f Arquivo normal
  l Link simbólico
  s Socket
-size ±n[unid] Procura por arquivos que usam mais (+n) de n unidades unid de espaço ou a menos (-n) de n unidades unid de espaço.
  Unidades Valor
  b Bloco de 512 bytes (valor default)
  c Caracteres
  k Kilobytes (1024 bytes)
  w Palavras (2 bytes)
-atime ±d Procura por arquivos que foram acessados há mais (+d) de d dias ou a menos (-d) de d dias;
-ctime ±d Procura por arquivos cujo status mudou há mais (+d) de d dias ou a menos (-d) de d dias;
-mtime ±d Procura por arquivos cujos dados foram modificados há mais (+d) de d dias ou a menos (-d) de d dias;

Para usar mais de um critério de pesquisa, faça:
expressão1 expressão2
ou
expressão1 –a expressão2
para atender aos critérios especificados por expressão1 e expressão2;
expressão1 –o expressão2
para atender aos critérios especificados por expressão1 ou expressão2.

As principais ações definidas para ação são:

-print Esta opção faz com que os arquivos encontrados sejam exibidos na tela. Esta é a opção default no Linux. Nos outros sabores Unix que conheço, se nenhuma ação for especificada, ocorrerá um erro;
-exec cmd {} \; Executa o comando cmd. O escopo de comando é considerado encerrado quando um ponto-e-vírgula (;) é encontrado. A cadeia {} é substituída pelo nome de cada arquivo que satisfaz ao critério de pesquisa e a linha assim formada é executada. Assim como foi dito para a opção –name, o ponto-e-vírgula (;) deve ser precedido por uma contrabarra (\), ou deve estar entre aspas ou apóstrofos;
-ok cmd {} \; O mesmo que o anterior porém pergunta se pode executar a instrução cmd sobre cada arquivo que atende ao critério de pesquisa;
-printf formato Permite que se escolha os campos que serão listados e formata a saída de acordo com o especificado em formato.

Exemplos:

Para listar na tela (-print) todos os arquivos, a partir do diretório corrente, terminados por .sh, faça:

$ find . -name \*.sh Ação não especificada –print é default
./undelete.sh
./ntod.sh
estes quatro primeiros arquivos foram
./dton.sh
encontrados no diretório corrente.
./graph.sh
./tstsh/cotafs.sh
./tstsh/data.sh
Estes quatro foram encontrados no
./tstsh/velha.sh
diretório tstsh, sob o diretório corrente
./tstsh/charascii.sh

Preciso abrir espaço em um determinado file system com muita urgência, então vou remover arquivos com mais de um megabyte e cujo último acesso foi há mais de 60 dias. Para isso, vou para este file system e faço:

$ find . –type f –size +1000000c –atime +60 –exec rm {} \;

Repare que no exemplo acima usei três critérios de pesquisa, a saber:

-type f Todos os arquivos regulares (normais)
-size +1000000c Tamanho maior do que 1000000 de caracteres (+1000000c)
-atime +60 Último acesso há mais de 60 (+60) dias.

Repare ainda que entre estes três critérios foi usado o conector e, isto é, arquivos regulares e maiores que 1MByte e sem acesso há mais de 60 dias.

Para listar todos os arquivos do disco terminados por .sh ou .txt, faria:

$ find / -name \*.sh –o –name \*.txt –print

Neste exemplo devemos salientar além das contrabarras (\) antes dos asteriscos (*), o uso do –o para uma ou outra extensão e que o diretório inicial era o raiz (/); assim sendo, esta pesquisa deu-se no disco inteiro (o que freqüentemente é bastante demorado).

Com o printf é possível formatar a saída do comando find e especificar os dados desejados. A formatação do printf é muito semelhante à do mesmo comando na linguagem C e interpreta caracteres de formatação precedidos por um símbolo de percentual (%). Vejamos seus efeitos sobre a formatação:

Caractere Significado
%f Nome do arquivo (caminho completo não aparece)
%F Indica a qual tipo de file system o arquivo pertence
%g Grupo ao qual o arquivo pertence
%G Grupo ao qual o arquivo pertence (GID- Numérico)
%h Caminho completo do arquivo (tudo menos o nome)
%i Número do inode do arquivo (em decimal)
%m Permissão do arquivo (em octal)
%p Nome do arquivo
%s Tamanho do arquivo
%u Nome de usuário (username) do dono do arquivo
%U Número do usuário (UID) do dono do arquivo

Também é possível formatar datas e horas obedecendo às tabelas a seguir:

Caractere Significado
%a Data do último acesso
%c Data de criação
%t Data de alteração

Os três caracteres anteriores produzem uma data semelhante ao do comando date.

Veja um exemplo:

$ find . -name “.b*” -printf ‘%t %p\n’
Mon Nov 29 11:18:51 2004 ./.bash_logout
Tue Nov 1 09:44:16 2005 ./.bash_profile
Tue Nov 1 09:45:28 2005 ./.bashrc
Fri Dec 23 20:32:31 2005 ./.bash_history

Nesse exemplo, o %p foi o responsável por colocar os nomes dos arquivos. Caso fosse omitido, somente as datas seriam listadas. Observe ainda que ao final foi colocado um /n. Sem ele não haveria salto de linha e a listagem anterior seria uma grande tripa.

Essas datas também podem ser formatadas, para isso basta passar as letras da tabela anterior para maiúsculas (%A, %C e %T) e usar um dos formatadores das duas tabelas a seguir:

Tabela de formatação de tempo
Caractere Significado
H Hora (00..23)
I Hora (01..12)
k Hora (0..23)
l Hora (1..12)
M Minuto (00..59)
p AM or PM
r Horário de 12 horas (hh:mm:ss) seguido de AM ou PM
S Segundos (00 … 61)
T Horário de 24-horas (hh:mm:ss)
Z Fuso horário (na Cidade Maravilhosa BRST)
Tabela de formatação de datas
Caractere Significado
a Dia da semana abreviado (Dom…Sab)
A Dia da semana por extenso (Domingo…Sábado)
b Nome do mês abreviado (Jan…Dez)
B Dia do mês por extenso (Janeiro…Dezembro)
c Data e hora completa (Fri Dec 23 15:21:41 2005)
d Dia do mês (01…31)
D Data no formato mm/dd/aa
h Idêntico a b
j Dia seqüencial do ano (001…366)
m Mês (01…12)
U Semana seqüencial do ano. Domingo como 1º dia da semana (00…53)
w Dia seqüencial da semana (0..6)
W Semana seqüencial do ano. Segunda-feira como 1º dia da semana (00…53)
x Representação da data no formato do país (definido por $LC_ALL)
y Ano com 2 dígitos (00…99)
Y Ano com 4 dígitos

Para melhorar a situação, vejamos uns exemplos; porém, vejamos primeiro quais são os arquivos do diretório corrente que começam por .b:

$ ls -la .b*
-rw——- 1 d276707 ssup 21419 Dec 26 17:35 .bash_history
-rw-r–r– 1 d276707 ssup 24 Nov 29 2004 .bash_logout
-rw-r–r– 1 d276707 ssup 194 Nov 1 09:44 .bash_profile
-rw-r–r– 1 d276707 ssup 142 Nov 1 09:45 .bashrc

Para listar esses arquivos em ordem de tamanho, podemos fazer:

$ find . -name “.b*” -printf ‘%s\t%p\n’ | sort -n
24 ./.bash_logout
142 ./.bashrc
194 ./.bash_profile
21419 ./.bash_history

No exemplo que acabamos de ver, o \t foi substituído por um na saída de forma a tornar a listagem mais legível. Para listar os mesmos arquivos classificados por data e hora da última alteração:

$ find . -name “.b*” -printf ‘%TY-%Tm-%Td %TH:%TM:%TS %p\n’ | sort
2004-11-29 11:18:51 ./.bash_logout
2005-11-01 09:44:16 ./.bash_profile
2005-11-01 09:45:28 ./.bashrc
2005-12-26 17:35:13 ./.bash_history

Vou aproveitar também para mandar o meu jabá: diga para os amigos que quem estiver afim de fazer um curso porreta de programação em Shell que mande um e-mail para a nossa gerencia de treinamento para informar-se.

Qualquer dúvida ou falta de companhia para um chope ou até para falar mal dos políticos é só mandar um e-mail para mim.

Valeu!

Read Full Post »