Introducao ao Perl
1,869 acessos
Uma introdução ao Perl, uma linguagem de script presente em muitos sites da Internet, muito poderosa, multiplataforma e de rápido desenvolvimento
O Perl é uma das linguagens mais usadas hoje em dia. PERL significa “Practical Extraction and Report Language” (linguagem prática de extração e relatório). Ela foi criada por Larry Wall para facilitar a manipulação de textos suprindo as necessidades que o shell, awk e sed não conseguiam resolver. Perl é a ferramenta para fazer o seu trabalho, de forma rápida e eficiente.
Apesar de ter sido originalmente escrita para manipulação de texto, o fato de ter o código aberto e estar licenciada nos termos da GPL, permitiu que se expandisse para as mais diversas áreas. Com Perl é possível escrever ferramentas para administração de sistema, servidores, daemons, CGIs, programar para X usando toolkits como TK, Gtk+ e QT, escrever aplicativos Gnome e KDE, usar bibliotecas em C, interfaceamento com banco de dados, entre outras coisas.
Uma das forças do Perl é a sua comunidade, que assim como a do Linux colabora com o seu desenvolvimento e uso. O maior exemplo é o CPAN (www.perl.com/CPAN/), Comprehensive Perl Active Network, que é uma coleção de módulos e bibliotecas que permitem desde codificar um cliente compatível com o ICQ até manipular todo o funcionamento do servidor de web Apache. Antes de tentar reinventar a roda, certifique-se de que alguém já não o tenha feito.
O objetivo deste artigo é introduzi-lo ao Perl, começando pelo básico da linguagem até escrever uma pequena ferramenta de administração de sistema. Não é necessário conhecer Perl, mas um conhecimento básico de qualquer linguagem o ajudará a entender melhor.
Tipos de variáveis
A maioria dos termos mais comuns será mantida em inglês uma vez que será assim que você os achará na maior parte da literatura sobre o assunto.
Os tipos de variáveis no Perl são: scalar, array, hash e filehandle.
$scalar
O scalar (representado pelo $) é o tipo de variável mais usada. Podendo representar:
- um número (ex: 1, -4, 10.897, -12e4),
- uma string (ex: “Gooolll!!!”),
- ou uma referência (mais sobre referências depois).
Assim:
$a = 4; # Número 4 $b = "Erro número "; # uma String
Como o Perl diferencia um número de uma string? Depende do contexto.
Assim:
print $b . $a; #O '.' concatena duas strings
resultando:
Erro número 4
e:
print $b + $a; #O '+' adiciona dois números
resultando:
4
No primeiro caso, o número 4 em $a é convertido para
a string “4″. No segundo, a string “Erro número” em $b é convertida para o número 0, já que não é possível transformá-la em outro valor. No entanto, se a string representar um número válido como “-3.14″, será convertida para o número -3.14, se o contexto pedir um número.
@array
Uma array (ou matriz) é uma lista de scalares. Para criar uma array (representada por @):
@a = (1, 2, "mar",'); #Uma lista de três elementos #(scalares), 2 números e 1 string
Os parênteses têm um significado especial. Eles
colocam os scalares em um contexto de lista. No caso:
@a = (2,');
(2) é uma lista de um elemento, 2. Cada elemento de uma array pode ser acessado diretamente na forma $array[índice], em que índice é a posição do elemento na lista. O primeiro elemento tem índice 0.
Analise a situação:
$a = 33; #Um scalar
@a = ("trinta e três",'); #Uma array de um elemento print $a;
print $a[0];
O que será impresso no primeiro print? E no segundo?
Resposta: 33 e "trinta e três".
Porque o $a e $a[0] estão em "espaços"
diferentes dentro do Perl. Assim, você pode ter um scalar, uma array,
um hash, um filehandle e uma função com o mesmo nome. E uma não
interfere com a outra.
Um recurso muito útil com os arrays é o slice
(fatia).
O slice separa um pedaço de uma array.
@a = (1, 5, 4, "carro", "moto",');
#Uma array de 5 elementos
@b = @a[2..4];
#@b contém 4, "carro" e "moto"
Uma função muito usada quando se trabalha com
arrays é o sort. O sort ordena alfabeticamente uma lista. No exemplo
anterior:
@a = sort(1, 5, 4, "carro", "moto",');
#Uma array de 5 elementos
@b = @a[2..4];
#@b contém 5, "carro" e "moto"
%hash
O hash(representado por %) é um caso especial de array.
O hash é uma array cujo índice é uma string. Assim:
%hash = (,');
#criada um hash vazio
$hash{'banana'} = "R$ 1,05";
#Item banana em hash com valor
#"R$ 1,05"
$hash{"maçã"} = "R$ 0,40";
#Idem para maçã
$hash{laranja} = "R$ 0,65";
#também válido, as aspas são
#opcionais
Assim como a array, as variáveis
$a, $a[0] e $a{0} são todas diferentes e independentes.
Um hash é na verdade uma array especial. Isso permite
algumas coisas interessantes como:
@a = ("banana", 12, "uva", 40,');
#Uma array
%frutas = @a;
#O conteúdo de @a foi convertido para
#uma array
print $frutas{uva};
#vai mostrar "40".
Filehandle
O filehandle é a forma que o Perl usa para se comunicar
com o mundo externo. Não existe um símbolo especial para ele,
por isso usa-se uma palavra em letras maiúsculas para evitar conflitos
com palavras reservadas.
Um filehandle pode ser a leitura de entrada, saída para
a tela (STDIN, STDOUT), a leitura ou escrita de um arquivo ou mesmo um socket
de rede (como uma conexão TCP/IP).
Funções
As funções (ou subrotinas) no Perl são
definidas pela declaração sub:
sub minha_primeira_funcao{
#código da função vem aqui
#....
}
Em que os { } definem um bloco que
contém a subrotina. No Perl, não se definem
os parâmetros das funções diretamente
como em outras linguagens.
Ao invés disso, o Perl define um array @_ que contém
uma lista com os argumentos passados para a função.
Assim:
minha_funcao(1234, $dado_a, $ar[6], $fruta{melao},');
#Chama-se a funcao
E na definição da função:
sub minha_funcao{
$meu_dado_a = @[1];
$minha_frunta = @[3];
}
Ou, mais comum:
sub minha_funcao{
($senha, $meu_dado_a, $param, $minha_fruta) = @_;
#...
}
Cada item da array (@_) é copiado diretamente para o
item correspondente na primeira lista.
Para retornar um dado da função, usa-se o comando
return.
Referências
O conceito de referência é um pouco mais complicado
de entender para quem está começando e/ou tem pouca experiência
com programação. Uma referência "aponta" para
outra variável. A referência contém a localização
de outra variável. Nada melhor do que um exemplo para ajudar a entender:
$a = "Oi"; #Um scalar normal
$b = $a; #O $b é uma referência para o $a
O `' antes de uma variável serve para "extrair"
sua referência. Assim:
print $$b;
mostrará:
Oi
Pode-se analisar como o scalar ($) referenciado por $b. Praticamente
qualquer coisa pode ser referenciada: scalares($), arrays(@), hashes(%), filehandles
ou mesmo funções(&). Assim:
@array = ( 13, "carro", $a,');
#Uma array de 3 elementos
$b = @array;
#$b é uma referência para
#@array
Assim, @$b é a array(@) referenciada por $b. Cuidado
para não usar um tipo de variável diferente da referida pela referência,
no caso usar um $ ($$b) ao invés de um @ (@$b). Isso causará um
alerta ou mesmo erro na execução do programa.
Pode não parecer óbvio o uso de referências,
mas imagine uma array com 1MB de tamanho. Se você simplesmente copiá-la
com:
@new_array = @old_array;
você terá uma cópia idêntica de @old_array.
Se @old_array tem 1MB, @new_array usará outro 1MB. Quando você
passa parâmetros para uma função, os parâmetros são
copiados para a array @_, a mesma situação ocorre. Se, ao invés
de passar parâmetro por parâmetro, for passada uma referência,
economiza-se memória e ganha-se velocidade:
@array_gigante = (,');
#Uma array
#gigante
funcao(@array_gigante,');
#passa uma referência da array
sub funcao{
$refarray = $_[1];
#Cópia da referência
print @$refarray;
#Usando a array normalmente
print $$refarray[2];
#Ou o terceiro item
}
Exemplo da vida real
Nada melhor do que um exemplo útil para ver tudo funcionando
junto. Vamos fazer um analisador para o arquivo de log de acesso de um servidor
web.
Existe um formato padrão para log de servidor de acesso.
Esse formato é:
endereço ident user [data] "request" status bytes
Endereço: Endereço IP (mais comum) ou nome
da máquina do cliente.
Ident: Resposta do ident no cliente. Normalmente não
usado (-).
User: O nome do usuário, caso ele tenha se autenticado
antes.
Data: A data e a hora do acesso.
Request: A linha de requisição enviada pelo
cliente.
Status: O status respondido pelo servidor.
Bytes: A quantidade de bytes transferidos.
Ex:
200.220.50.122 - gvtit [24/May/2000:08:19:04 -0300] "GET /it/index.html HTTP/1.0" 200 774
O que queremos:
1 - As páginas mais acessadas
2 - Os endereços(usuários) que mais acessam
o servidor
3 - A quantidade total de bytes transferidos
4 - A quantidade de bytes transferidos por página
mais acessada.
5 - A quantidade de bytes transferidos por clientes (endereços).
Não nos interessa os campos ident, user e data. Interessam-nos
apenas as entradas com status 200 (accepted).
Outros tipos de resposta serão ignorados.
Começando o programa:
#!/usr/bin/perl -w
use strict;
my $file = $ARGV[0]; #arquivo a ser usado
A primeira linha especifica que programa executará esse
script.
O -w especifica que o Perl deve ser mais rigoroso com os alertas,
evidenciando potenciais erros.
O "use strict" força o Perl a apenas aceitar
variáveis locais, declaradas com o uso do "my". Isso ajuda
a evitar que erros de digitação possam comprometer o programa.
O "my" usado na declaração das variáveis
as forçam a ser locais. Nesse caso, as variáveis declaradas fora
de uma função não existirão dentro de uma função
e variáveis declaradas dentro de uma função deixarão
de existir quando a função termina.
Assim, se existir uma variável $a fora de uma função
e um $a dentro, elas serão independentes.
A array @ARGV é uma variável especial. Ela contém
os argumentos que foram passados pela linha de comando. Assim, estamos considerando
que o primeiro argumento é o nome do arquivo a ser usado, contendo os
logs.
Continuando:
my $linha;
open (LOG, "<$file",');
#abre-se o arquivo para leitura
while ($linha = ){
#loop em cada linha
#aqui elas serão processadas
}
#fim do loop
O open abre o arquivo para leitura (<) usando o filehandle
LOG para designar o acesso a esse arquivo. A expressão
$linha = , lê uma linha do arquivo. Os <>
são responsáveis por isso. Quando o arquivo terminar será
devolvido o valor undef (indefinido) que é interpretado como falso pelo
while, encerrando assim o loop.
Dentro do loop while:
if ( $linha =~ /^(S+)s+S+s+S+s+[.*]s+"(.*)"s+(S+)s+(S+)$/ ){
$ip = $1;
$full_request = $2;
$status = $3;
$bytes = $4;
Agora um pouco de expressões regulares. Expressões
regulares são um dos recursos mais poderosos do Perl. Uma expressão
regular tenta "casar" seu conteúdo com uma variável.
No caso:
$linha =~ /^(S+)s+S+s+S+s+[.*]s+"(.*)"s+(S+)s+(S+)$/
O sinal "=~" tenta casar o conteúdo de $linha
com a expressão regular (entre //)
Vamos desmontar a expressão regular(ER):
/ #Especifica que é uma ER
^ #Deve começar pelo primeiro caracter de $linha
$ #Deve terminar exatamente no último caracter de
$linha
s #Um caracter de espaço (espaço ou tab)
S #Um caracter diferente de espaço
. #Um caracter qualquer
+ #Significa um ou mais caracteres iguais ao precedente
* # Nenhum ou mais caracteres iguais ao precedente
() #Gravar o conteúdo dentro dos parênteses
(veja adiante)
" #O caracter `"`
[ #O caracter `[`. `[` e `]`
tem significado especial em ER
Neste caso estamos checando se a $linha contém uma linha
de log válida. Caso não contenha, capturaremos o erro em um else
depois do bloco.
Também aproveitamos para capturar as informações
que nos interessam com o uso dos (). Serão criadas
automaticamente as variáveis $1, $2, $3 e $4 correspondendo a cada
(). Veja figura 1.
Esta parte é bem direta. %contagem_ip usa o IP do log
como chave (índice), %contagem_request usa o arquivo solicitado
e o mesmo ocorre com a soma dos bytes.
Ao terminar o loop do while, teremos todos os dados processados.
Fechamos o filehandle e podemos começar a ordenação
dos dados. Veja figura 2.
keys é uma função especial para
hashes. Ela gera uma array apenas com os índices
(chaves) do hash.
sort como vimos antes, ordena uma array alfabeticamente.
No entanto, podemos mudar a maneira de como ele ordena.
Para isso especificamos uma função dentro de um BLOCO no sort
com os valores $a e $b. $a e $b são dois elementos da array que está
sendo ordenada. Essa função tem que retornar -1 se $a < $b,
0 se $a == $b ou 1 se $a > $b. A função que faz isso é
esta:
sub ordena_hash_numerica{
my ($a, $b, $hashref) = @_; #parâmetros de entrada
my $c = $$hashref{$a} <=> $$hashref{$b};
return $c;
}
No nosso caso $a e $b são chaves do hash %contagem_request
e o que queremos é ordenar o valor dos hashes. Para podermos comparar
o conteúdo do hash, tivemos que passar uma referência do hash.
O operador <=> faz a tarefa que precisamos. Ele compara
os valores à direita e à esquerda numericamente e retorna -1,
0 ou 1 correspondentemente.
O comando reverse é usado para reverter a ordem de uma
array ou lista. Assim teremos os acessos da ordem do maior para o menor, que
é a desejada. Depois de termos as chaves ordenadas por ordem de acesso,
pegamos as dez primeiras que nos interessam.
Para visualizá-las:
print "Páginas mais acessadas:
# acessos bytes Páginas
";
$count = 0;
foreach $request_url (@top_ten){
$count++; #adiciona 1 ao $count
print "$count $contagem_request{$request_url} ";
print "$bytes_request{$request_url} $request_url
";
}
Finalizando um loop especial é o foreach. Ele executa
o BLOCO uma vez para cada elemento da array @top_ten. No entanto, a cada interação,
$request_url recebe um elemento de @top_ten. Para completar, esse processo de
ordenação e visualização tem de ser repetido para
cada item (hash) que queremos mostrar.
Veja a listagem completa na figura 3.
Note as alterações finais no programa. A primeira
foi a remoção do open do arquivo e o uso do <>. O loop usando
como parâmetro o <> é um caso especial no Perl. Nesse caso,
o Perl irá abrir para leitura todos os arquivos especificados na linha
de comando (ex: loganaliser log log.1 log.2). Caso nenhum arquivo tenha sido
especificado, a leitura será feita da entrada padrão (STDIN).
Isso possibilita, entre outras coisas, o uso de "pipes" no programa
(ex. cat arq | loganaliser).
Outra alteração é a criação
da função ordena_e_mostra, que recebe duas referências de
hashes como parâmetros. Isso possibilita que ela possa ser usada genericamente.
Note também que retiramos as arrays usadas e colocamos tudo na mesma
linha. O slice [0..9] depois do sort e reverse, na função ordena_e_mostra,
funciona por uma simples razão: uma lista especificada entre parênteses
é um array sem a variável (sem nome e sem o `@`).
Antes de mais nada, o Perl é a ferramenta para que você
tenha o seu trabalho resolvido. Com esse exemplo é possível perceber
o seu poder. Mas não para por aí. Existem muitos programas feitos
em Perl, como clientes de e-mail e até mesmo um servidor Radius, o radiator,
essencial em um provedor de internet.
para saber mais
www.perl.com
No seu computador: man perl
Livros:
Learning Perl - Schwartz & Christiansen -
Ed. O'Reilly
Programming Perl - Wall - Ed. O'Reilly
Figura 1
if ($status == 200){
#Extrai a URL:
$full_request =~ /S+s+(.*)s+S+/;
$request = $1;
#Garante que o valor é numérico
#Caso o usuário aborte, é colocado "-" no lugar
if ($bytes eq "-"){
$bytes = 0;
}
$contagem_ip {$ip}++; #Faz a contagem de
$contagem_request {$request}++; #acessos
$bytes_ip {$ip} += $bytes; #E a quantidade de bytes
$bytes_request {$request} += $bytes; #transferidos
$total += $bytes; #O total
}else{ #Aqui contabilizamos o número
$nao_200++; #de páginas com status diferente de 200
}
}else{ #E o número de erros
$erros++; #Erro são as linhas que não "casam"
} #com a Expressão Regular
} #Fim do Loop While
close(LOG,'); #Fecha o LOG
Figura 2
@keys = keys %contagem_request; #As chaves índices) do hash
@sorted_keys = reverse sort{
ordena_hash_numerica($a, $b, \%contagem_request,');
}@keys; #ordena @keys numericamente
@top_ten = @sorted_keys [0..9]; #Os 10 primeiros elementos
Figura 3
#!/usr/bin/perl -w
use strict;
# declaração das variáveis
my ($ip, $full_request, $status, $bytes,');
my ($request, $total, $nao_200, $erros,');
my (%contagem_ip, %contagem_request, %bytes_ip, %bytes_request,');
while (<>){ #loop em cada linha
if ( /^(S+)s+S+s+S+s+[.*]s+"(.*)"s+(S+)s+(S+)$/ ){
$ip = $1;
$full_request = $2;
$status = $3;
$bytes = $4;
if ($3 == 200){
#Extrai a URL:
$full_request =~ /S+s+(.*)s+S+/;
$request = $1;
#Garante que o valor é numérico
#Caso o usuário aborte, é colocado "-" no lugar
if ($bytes eq "-"){
$bytes = 0;
}
$contagem_ip {$ip}++; #Faz a contagem de
$contagem_request {$request}++; #acessos
$bytes_ip {$ip} += $bytes; #E o a quantidade de bytes
$bytes_request {$request} += $bytes; #transferidos
$total += $bytes; #O total
}else{ #Aqui contabilizamos o número
$nao_200++; #de páginas com status diferente de 200
}
}else{ #E o número de erros
$erros++; #Erro são as linhas que não "casam"
} #com a Expressão Regular
} #Fim do Loop While
#As 10 páginas (arquivos) mais acessadas
print "Páginas mais acessadas:
# acessos bytes Páginas
";
ordena_e_mostra(\%contagem_request, \%bytes_request,');
#Os 10 clientes (usuários) que mais acessam
#Não considera proxies.
print "
Clientes que mais acessam:
# acessos bytes Cliente
";
ordena_e_mostra(\%contagem_ip, \%bytes_ip,');
print "
Dados Finais:
";
print "Erros: $erros
";
print "Não 200: $nao_200
";
print "Total: $total
";
#ordena e mostra os dados
sub ordena_e_mostra{
my ($ref_a, $ref_b)=@_;
my $count = 0;
foreach $item ((reverse sort {$$ref_a {$a} <=> $$ref_a {$b}} keys %$ref) [0..9]){
$count++; #adiciona 1 ao $count
print "$count "; #Mostra os dados
print "$$ref_a {$item} "; #O item principal
print "$$ref_b {$item} "; #Item secundário
print "$item
"; #O nome do item
}
}