Revista Do Linux  
EDIÇÃO DO MÊS
  Perl
  Estudo de Caso
  Delphi para Linux
  Entrevista
  CD do mês
  Programas Matadores
  Interfaces Gráficas
  Hardware
  Segurança
  Expressões Regulares

Perl
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 = (<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. 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:\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
  }
}
 

A Revista do Linux é editada pela Conectiva S/A
Todos os Direitos Reservados.

Política de Privacidade