Escolar Documentos
Profissional Documentos
Cultura Documentos
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 satisfaç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
-t Mostra a linha de comando montada antes de executá-la
-i Substitui o par de chaves ({}) pelas cadeias recebidas
Manda o máximo de parâmetros recebidos, até o máximo de Num
-nNum
para o comando a ser executado
Manda o máximo de linhas recebidas, até o máximo de Num para o
-lNum
comando a ser executado
Mostra a linha de comando montada e pergunta se deseja executá-la
-p
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 ...
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
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
CL=
until [[ $CL == 0[1-8] || $CL == [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
Vetores associativos
A partir do Bash 4.0, passou a existir o vetor associativo. Chama-se vetor
associativo, aqueles cujos índices são alfabéticos. As regras que valem para os
vetores inteiros, valem também para os associativos, porém antes de valorar estes
últimos, é obrigatório declará-los.
Exemplo:
$ declare -A Animais # Obrigatório para vetor associativo
$ Animais[cavalo]=doméstico
$ Animais[zebra]=selvagem
$ Animais[gato]=doméstico
$ Animais[tigre]=selvagem
É impossível gerar todos os elementos de uma só vez, como nos vetores inteiros.
Assim sendo, não funciona a sintaxe:
Animais=([cavalo]=doméstico [zebra]=selvagem [gato]=doméstico
[tigre]=selvagem)
$ echo ${Animais[@]}
doméstico selvagem doméstico selvagem
$ echo ${!Animais[@]}
gato zebra cavalo tigre
Repare que os valores não são ordenados, ficam armazenados na ordem que são
criados, diferentemente dos vetores inteiros que ficam em ordem numérica.
Supondo que esse vetor tivesse centenas de elementos, para listar separadamente
os domésticos dos selvagens, poderíamos fazer um script assim:
$ cat animal.sh
#!/bin/bash
# Separa animais selvagens e domésticos
declare -A Animais
Animais[cavalo]=doméstico # Criando vetor para teste
Animais[zebra]=selvagem # Criando vetor para teste
Animais[gato]=doméstico # Criando vetor para teste
Animais[tigre]=selvagem # Criando vetor para teste
Animais[urso pardo]=selvagem # Criando vetor para teste
for Animal in "${!Animais[@]}" # Percorrendo vetor pelo índice
do
if [[ "${Animais[$Animal]}" == selvagem ]]
then
Sel=("${Sel[@]}" "$Animal") # Gerando vetor p/ selvagens
else
Dom=("${Dom[@]}" "$Animal") # Gerando vetor p/ domésticos
fi
done
# Operador condicional, usado para descobrir qual
#+ vetor tem mais elementos. Veja detalhes na seção
#+ O interpretador aritmético do Shell
Maior=$[${#Dom[@]}>${#Sel[@]}?${#Dom[@]}:${#Sel[@]}]
clear
tput bold; printf "%-15s%-15s\n" Domésticos Selvagens; tput sgr0
for ((i=0; i<$Maior; i++))
{
tput cup $[1+i] 0; echo ${Dom[i]}
tput cup $[1+i] 14; echo ${Sel[i]}
}
Gostaria de chamar a sua atenção para um detalhe: neste script me referi a um
elemento de vetor associativo empregando ${Animais[$Animal]} ao passo que me
referi a um elemento de um vetor inteiro usando ${Sel[i]}. Ou seja, quando
usamos uma variável como índice de um vetor inteiro, não precisamos prefixá-la
com um cifrão ($), ao passo que no vetor associativo, o cifrão ( $) é obrigatório.
Expansão de chaves
O Bash 4.0 incorporou duas novas formas de fazer expansão de chaves:
Sequência numérica com preenchimento de zeros à esquerda;
Possibilidade de usarmos um incremento (passo) em uma sequência
numérica.
Exemplos:
$ echo {0010..0019} # Preenchendo com 2 zeros à esquerda
0010 0011 0012 0013 0014 0015 0016 0017 0018 0019
$ echo {05..15} # Preenchendo com zeros à esquerda
05 06 07 08 09 10 11 12 13 14 15
$ echo {-5..19..3} # Incrementando de 3 em 3 (Passo 3)
-5 -2 1 4 7 10 13 16 19
$ echo {000..100..10} # Preenchendo com zeros e passo 10
000 010 020 030 040 050 060 070 080 090 100