torradeira.net



Milhares de dicas e tutoriais de informatica!

Flower

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

www.perl.org

www.perl.com/CPAN/

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
}
}
Compartilhe:
  • Twitter
  • del.icio.us
  • Google Bookmarks
  • email
  • Live
  • Rec6
  • Print
  • StumbleUpon

Comente! Sua participação é importante.