Os tipos de variáveis no Perl são: scalar, array,
hash e filehandle.
O scalar (representado pelo $) é o tipo de variável
mais usada. Podendo representar:
Como o Perl diferencia um número de uma string? Depende
do contexto.
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.
Uma array (ou matriz) é uma lista de scalares. Para criar
uma array (representada por @):
Os parênteses têm um significado especial. Eles
colocam os scalares em um contexto de lista. No caso:
(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.
$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 = (<MUITOS PARÂMETROS>);
#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 = <LOG>){
#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 = <LOG>, 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:\n\n#\tacessos\tbytes\tPáginas\n";
$count = 0;
foreach $request_url (@top_ten){
$count++; #adiciona 1 ao $count
print "$count\t$contagem_request{$request_url}\t";
print "$bytes_request{$request_url}\t$request_url\n";
}
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.
<b>para saber mais</b>
www.perl.com
www.perl.org
www.perl.com/CPAN/
No seu computador: man perl
Livros:
Learning Perl Schwartz & Christiansen
Ed. OReilly
Programming Perl Wall Ed. OReilly
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:\n\n#\tacessos\tbytes\tPáginas\n";
ordena_e_mostra(\%contagem_request, \%bytes_request);
#Os 10 clientes (usuários) que mais acessam
#Não considera proxies.
print "\nClientes que mais acessam:\n\n#\tacessos\tbytes\tCliente\n";
ordena_e_mostra(\%contagem_ip, \%bytes_ip);
print "\nDados Finais:\n";
print "Erros: $erros\n";
print "Não 200: $nao_200\n";
print "Total: $total\n";
#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\t"; #Mostra os dados
print "$$ref_a {$item}\t"; #O item principal
print "$$ref_b {$item}\t"; #Item secundário
print "$item\n"; #O nome do item
}
}