Uma introdução ao Perl

Alceu Junior
Publicado em 02/03/2007

r6 - 02 Mar 2007 - AlceuJunior

Uma introdução ao Perl

Introdução à linguagem de programação Perl.

Neste artigo o leitor terá uma introdução à linguagem de programação Perl. Assume-se que o leitor já tenha algum conhecimento básico sobre programação (como uso de variáveis, fluxo de execução de um programa e um pouco de lógica de programação) e de como utilizar o shell de um sistema operacional UNIX, como o Linux. Conhecer um pouco de shell script também ajudará bastante.

Ao invés de começar a explicar os detalhes da linguagem antes de fazer qualquer coisa mais útil que uma calculadora, o leitor vai desenvolver durante o artigo um programa simples enquanto o pode aprender gradativamente o básico da linguagem.

O programa fará algo simples: procurará por arquivos MP3 num local indicado (ou pré-definido como o diretório atual de execução do programa, caso nenhum seja especificado) e listará todas as informações gravadas no cabeçalho dos mesmos (assumindo, por exemplo, que os arquivos já foram criados com essas informações obtidas, por exemplo, de um servidor CDDB).

* Características
* Um breve histórico
* Providenciando o ambiente de programação mínimo
* Os requisitos do programa
* Molhando os pés
* Conclusão
* Bibliografia
* Obras impressas
* Notas

Características

Se você já programou em shell script, logo vai ter a agradável surpresa de descobrir que com pouco tempo de aprendizado em Perl você já poderá substituir alguns de seus programas com muito mais elegância, facilidade e, na maioria dos casos, maior performance. Na distribuição padrão do Perl até existe um programa(1), por exemplo, que converte automaticamente programas feitos em Sed para código equivalente em Perl.

Perl é muito utilizada também como glue language (linguagem de cola), exatamente como um shell script, dada a facilidade para executar outros programas e capturar a saída gerada pelos mesmos, isso sem considerar a possibilidade razoável de encontrar substitutos para esses programas em código Perl pronto para usar no repositório central de módulos conhecido como CPAN.

Você encontra essa linguagem instalada por padrão em qualquer sistema clone do UNIX de respeito, incluindo o Linux. Mas Perl não se limita a apenas esses sistemas, sendo ela multiplaforma: isso gera possibilidades muito interessantes, como criar programas que possam rodar em sistemas operacionais diferentes sem reescrever nenhum código. Hoje ela está disponível para sistemas como Microsoft Windows (várias versões), MAC OS, BeOS e muitos outros.

Um dos lemas principais do Perl é &147;existe mais de uma maneira de fazer&148;(2): a expressividade da linguagem é tão grande que algumas pessoas conseguem escrever poemas utilizando código funcional em Perl (entenda por código funcional algo que é executado e gera alguma saída, sem erros).

Perl foi desenvolvida inicialmente utilizando um paradigma procedural mas é plenamente possível utilizar orientação à objetos também (utilizando todos os recursos desse paradigma). Se o programador preferir, ainda é possível ter programas procedurais instanciando objetos sem o menor problema.

Outros recursos disponíveis como gerar documentação online diretamente no código do programa (perldoc) em diversos formatos, suporte a expressões regulares (tão completo que Perl é uma referência sobre esse assunto) e a capacidade de integrar código Perl com outras linguagens como C, C++ e Java.

Perl oferece amplo suporte ao desenvolvedor, incluindo um poderoso debugger em linha de comando e módulos para medir performance (Benchmark e Devel::DProf).

Um breve histórico

Perl foi criada em 1987 pelo então programador de sistemas Larry Wall. Nessa época ele recebeu como tarefa gerar relatórios de um sistema de controle em máquinas UNIX e sua primeira tentativa foi utilizar awk para fazer essa tarefa. Quando ele concluiu que awk não era tão flexível quanto ele gostaria, ele arregaçou as mangas e criou essa fantástica linguagem, liberando-a pouco tempo depois como software livre.

PERL é um acrônimo e significa Pratical Extraction Report Language (Linguagem Prática de Extração e Relatórios) e era utilizada exatamente para isso: extrair informações e gerar relatórios. Após sua liberação e depois de algumas contribuições ela passou a ser bem mais do que isso. Hoje, Perl é uma linguagem completa e é utilizada nos mais diversos campos como suporte a gerenciamento de sistemas, processamento de gráficos, acesso à banco de dados, aplicações web e gráficas e engenharia genética.

A linguagem passou a ficar mais conhecida ainda com o advento de aplicações dinâmicas na web, onde ganhou o apelido de &147;cola da Internet&148;, sendo muito utilizada para criar CGI's(3).

Providenciando o ambiente de programação mínimo

Se você utiliza o Linux como sistema operacional é muito provável que Perl já esteja instalado. Para confirmar isso, abra um terminal e execute

 $ perl -v

No website oficial do Perl em http://www.perl.org você irá encontrar, além do código fonte para compilação, links para diversas opções de Perl pré-compilado para diversos sistemas operacionais (para sistemas Microsoft Windows, por exemplo, procure em http://www.activestate.com). Para sistemas Linux, no entanto, o mais simples é utilizar o sistema de pacotes da distribuição para cuidar dessa instalação. Para o Debian GNU Linux, por exemplo, é muito simples resolver esse problema. Basta executar na linha de comando um

 $ apt-get install perl

e aguardar o sistema de pacotes instalar e configurar tudo para você.

Se você repetir o comando para verificar a versão novamente, verá algo parecido com a saída abaixo:

 C:\> perl -v

 This is perl, v5.8.8 built for MSWin32-x86-multi-thread
 (with 33 registered patches, see perl -V for more detail)

 Copyright 1987-2006, Larry Wall

 Binary build 819 [267479] provided by ActiveState http://www.ActiveState.com
 Built Aug 29 2006 12:42:41

 Perl may be copied only under the terms of either the Artistic License or the
 GNU General Public License, which may be found in the Perl 5 source kit.

 Complete documentation for Perl, including FAQ lists, should be found on
 this system using "man perl" or "perldoc perl".  If you have access to the
 Internet, point your browser at http://www.perl.org/, the Perl index.t Page.

Além do Perl instalado, você irá precisar de um editor de textos simples da sua preferência. Se você conhece e utiliza o Vim, recomendo fortemente que instale o plugin perl-support.vim do mesmo.

Os requisitos do programa

Para simplificar o aprendizado o programa usará um paradigma procedural. O fluxo de processamento que desejamos está ilustrado no diagrama abaixo:

* Fluxo de execução do programa:

fluxo.png

Molhando os pés

Nosso primeiro passo será entender que Perl não é uma linguagem compilada, no sentido do programador ter que gerar código de máquina antes de executar o programa, mas é interpretada pelo Perl e depois executada (bem, na verdade não é exatamente assim, mas isso é tudo que você precisa saber no momento).

Tendo isso em mente, temos duas formas principais de executar um programa em Perl no Linux:

1.executando o interpretador Perl na linha de comando, passando como parâmetro um arquivo contendo código para ser executado, desta forma:

 $ perl nome-do-arquivo

2.dar permissão de execução ao arquivo e adicionar o código abaixo no início do mesmo:

 #!/usr/bin/perl

Perceba que a segunda forma não é código Perl. Chamada comumente de shebang_(4) essa linha serve para que o _shell procure o interpretador a ser utilizado no caminho completo passado após o sinal de exclamação. Essa forma é simples e evita repetir digitações na linha de comando. Se o Perl estiver instalado em um diretório diferente, você terá que modificar essa linha com o caminho correto.

Edite um arquivo com o nome descritor.pl. A extensão no Linux é desnecessária, mas o leitor terá que usá-la, por exemplo, caso deseje rodar esse programa no MS Windows. Inclua então as seguintes linhas no arquivo:

 #!/usr/bin/perl
 use warnings;
 use strict;

A primeira linha você já sabe do que se trata, então vamos dar uma olhada nas duas seguintes.

A palavra reservada use é uma instrução que carrega módulos no seu programa. Módulos são as unidades básicas de reaproveitamento básico de código Perl: pense neles como bibliotecas. Estes módulos são procurados em diretórios pré-definidos durante a compilação do código fonte do Perl e estes diretórios são armazenados em forma de lista numa variável de ambiente chamada @INC. Se você estiver particularmente curioso, pode executar o comando abaixo num terminal:

 perl -e 'map { print $_, "\n" } @INC'

Será então impressa uma lista dos diretórios sendo usados como repositórios.

Voltando a instrução use, ela espera como argumento o nome do módulo a ser carregado. Os módulos warnings e strict são &147;pragmas&148;, módulos que alteram o comportamento normal do interpretador Perl. Esses dois módulos ajudam muito durante o desenvolvimento do programa, lhe avisando de possíveis erros (warnings) que não farão o programa abortar mas podem causar erros de lógica ou então abortar o programa quando se tenta utilizar variáveis não declaradas previamente (entre outras coisas). Por padrão, não se declarava variáveis em Perl: uma variável passa a existir a partir do momento que você atribui um valor a ela. Isso pode causar algumas confusões, como declarar uma variável e mais tarde digitar errado o nome de uma variável e tentar utilizá-la: se o programa usar warnings o programador ainda receberá um aviso de que está usando uma variável indefinida; caso contrário, não perceberá o erro! A pragma strict já aborta o programa nesses casos, forçando o programador a arrumar o erro.

Voltando ao exemplo, vamos adicionar mais uma linha para exemplificar isso:

 print "Welcome to MP3 descriptor - $version\n";

O programa agora já pode executar alguma coisa, imprimindo uma mensagem de boas vindas. O comando print cuida disso, imprimindo os parâmetros passados para ele. Esse comando aceita muitas opções, como selecionar o destino da impressão que pode ser a tela, um arquivo ou um soquete de rede. Devido a sua herança UNIX, Perl usa o conceito de saída padrão (STDOUT), saída de erro padrão (STDERR) e entrada padrão (STDIN). Como o padrão é imprimir coisas na tela (STDOUT), print permite que você omita a saída desejada para a impressão.

Se você já programou em shell script (ou outras linguagens com recurso semelhante, como Javascript) deve ter notado o uso das aspas duplas nessa linha de código. Aspas duplas em Perl significam que o interpretador deve retornar o texto que está entre as aspas, interpolando quaisquer variáveis que estejam ali, concatenando seus valores com o restante do texto. No caso, as variáveis ali presentes são $version e o caracter de quebra de linha &147;\n&148;. Ignore por enquanto o caracter engraçado no nome da variável, já vou falar sobre ele.

Se você ainda não deu permissão de execução ao descritor.pl, faça isso agora:

 jackal@valhalla:~$ chmod 700 descritor.pl

Se você executar o programa do jeito que está, receberá um erro:

 jackal@valhalla:~$ ./descritor.pl
 Global symbol "$version" requires explicit package name at ./descritor.pl line 5.
 Execution of ./descritor.pl aborted due to compilation errors.

O erro aconteceu porque symbol se refere a uma variável não declarada chamada $version. Esse erro foi gerado pela pragma strict e para eliminar o problema, basta modificar um pouco o programa. Para evitar repetir linhas, apenas a parte modificada será mostrada, em destaque:

 use strict;
 # versão do sistema
 my $version;
 print "Welcome to MP3 descriptor - version $version\n";

Na primeira nova linha o leitor vai se deparar com um típico comentário: o caractere de sustenido (#) diz ao interpretador Perl para ignorar tudo o que vier após esse caracter até a próxima quebra de linha. Perl não possui caracteres de comentário multilinhas, como C e C++.

Executar o programa novamente faz a pragma warnings entrar em ação:

 jackal@valhalla:~$ ./descritor.pl
 Use of uninitialized value in concatenation (.) or string at ./descritor.pl line 7.
 Welcome to MP3 descriptor -
 jackal@valhalla:~$

Esse erro aconteceu porque a variável $version, apesar de declarada corretamente, não tem valor nenhum e a pragma tratou de avisar sobre isso quando o interpretador tentou fazer a concatenação do texto com o valor de $version. O aviso será evitado se for atribuído um valor inicial à variável (o que é uma boa prática de programação, de qualquer forma). Para evitar repetir linhas, apenas a parte modificada será mostrada, em destaque:

 use strict;

 # versão do sistema
 my $version = 1;

 print "Welcome to MP3 descriptor - $version\n";

Executando novamente, teremos:

 jackal@valhalla:~$ ./descritor.pl
 Welcome to MP3 descriptor - 1
 jackal@valhalla:~$

A palavra reservada my diz ao Perl que você está definindo uma variável dentro do escopo atual, que para o descritor.pl significa o arquivo todo. Seguindo em frente você vai reparar o caracter engraçado &147;$&148; seguido de um conjunto de caracteres que, espera-se, definam um nome inteligível para a variável. O caracter de cifrão identifica o tipo de variável mais básico de Perl chamada scalar. Uma variável scalar (escalar, em português) pode armazenar caracteres ou números e o interpretador do Perl vai utilizar esses valores da forma mais conveniente possível, seja para gerar saída, realizar uma operação matemática ou concatenar números e caracteres sem necessidade alguma de conversão de tipos. Utilizando então o caractere de igual (=) atribuímos à $version o valor 1. Se fosse desejável que $version tivesse um valor alfanumérico como &147;bacana número 1&148;, basta utilizar aspas simples (que não tentam interpolar variáveis) ou aspas duplas.

Escalares podem armazenar outros tipos de literais numéricas. Veja uns exemplos:

 # ponto flutuante
 my $exemplo1 = 234.56;
 # notação científica
 my $exemplo2 = 32.04e12;
 # um inteiro bem grande, sublinhado para facilitar a leitura
 my $exemplo3 = 36_152_695;
 # declaração de um octal (basta usar zero no início)
 my $exemplo4 = 0377;
 # declaração de um hexadecimal (como o octal, mas usando "0x")
 my $exemplo5 = 0xffff;
 # declaração de um binário (como octal, mas usando "0b")
 my $exemplo6 = 0b1100_0000;

Além de caracteres e números, uma variável escalar pode armazenas referências. Referências é um assunto um pouco complexo (e extenso) para ser abordado neste artigo, mas basta você saber no momento que referências são utilizadas, em geral, para criar estruturas de dados multidimensionais em Perl.

O leitor atento já deve ter percebido que cada instrução escrita termina em ponto e vírgula (&147;;&148;): Perl ignora espaços, tabulações e quebras de linha mas o programador precisar terminar instruções com &147;;&148;. Existem algumas excessões, mas no geral você pode adotar isso sem maiores preocupações.

Agora o programa descritor.pl já é executado sem erros, gera alguma saída e ainda utiliza algumas boas práticas de programação em Perl. Mas ainda não atende sua proposta: obter informações de arquivos MP3. Os requisitos do programa dizem que ele deve procurar por arquivos MP3 num diretório dado como parâmetro ou ainda tentar no diretório corrente daonde o programa foi executado. O primeiro passo é modificar o programa novamente para receber esse parâmetro, incluindo mais código. Para evitar repetir linhas, apenas a parte modificada será mostrada, em destaque:

 print "Welcome to MP3 descriptor - version $version\n";

 my $location = $ARGV[0];
 print "Will try to find program at $location\n" if ( defined($location) );

As duas últimas linhas do programa incluem a declaração de uma nova variável e da impressão de uma mensagem de acordo com um teste condicional. Na penúltima linha, observe que $location recebe o valor de um outro tipo de variável em Perl: um array. Um array nada mais é do que uma lista de valores, que você acessa indicando um indíce numérico para recuperar o valor desejado.

Você também utiliza caracteres engraçados para manipular listas, como mostrado no código acima. Para declarar ou se referir a uma lista como um todo, você faz desta forma:

 my @list_of_mp3;

Para atribuir valores, você deve passar uma lista de valores dentro de parênteses, sendo que cada valor deve ser separado por vírgulas. Se estiver usando strings, você tem que se lembrar de utilizar aspas (simples ou dupla):

 my @list_of_mp3 = ('rock.mp3','blues.mp3','samba.mp3');

Agora se você estiver se sentido particularmente preguiçoso para digitar todas essas aspas e vírgulas, a função qw pode lhe ajudar com isso:

 my @list_of_mp3 = qw(rock.mp3 blues.mp3 samba.mp3);

Executar perldoc -f qw vai lhe dar mais detalhes sobre essa função;

Repare no entanto que em lugar nenhum foi declarada o array @ARGV: essa é uma variável especial do Perl, tal qual @INC, que por sinal também é uma lista. @ARGV mantêm registrado todas os parâmetros recebidos durante a chamada para execução de um programa ou de uma função. Normalmente você não precisa se preocupar muito com isso porque Perl tem o cuidado de passar para sempre o que você quer, desde que você não tenha intenção de acessar valores em @ARGV num escopo diferente do atual que está trabalhando. No caso do programa, basta pegar a primeira opção fornecida na linha de comando, se houver alguma.

Existem muitos outros usos e truques com arrays, como o uso de fatias. Mas isso fica para outro artigo.

A última linha faz um teste para verificar se o parâmetro foi informado ou não usando as instruções if e defined. A instrução if tem várias formas de execução além dessas que utilizamos. Para aprender sobre todas essas opções, utilize a documentação online do Perl para fazer isso:

 jackal@valhalla:~$ perldoc perlintro
 jackal@valhalla:~$ perldoc perlop

Em ambos os casos, procure as seções de operadores condicionais. Quanto à instrução defined, ela testa se a variável escalar (scalar) passada como parâmetro possui um valor definido ou não. Ela não funciona com listas (arrays).

Agora o programa deve tratar de procurar arquivos no diretório corrente caso não receba parâmetros na sua execução. Para evitar repetir linhas, apenas a parte modificada será mostrada, em destaque:

 use strict;
 use Cwd;

 # versão do sistema
 my $version = 1;
 print "Welcome to MP3 descriptor - $version\n";

 my $location;

 if (@ARGV) {

     $location = $ARGV[0];
     print "Will try to find files at $location\n";

 } else {

     $location = getcwd;
     print "Will try to find files at current directory ($location)\n";

 }

Observe como foi testada @ARGV: isso, no linguajar de Perl, é chamado de uso em contexto escalar: quando você usa uma array dessa forma ele não retorna uma lista, mas sim o número de elementos que ele armazena. Se o programa recebeu alguma coisa (com sorte a localização do diretório aonde estão os arquivos mp3) então @ARGV retornará o valor 1 (ou mais) o que torna a declaração verdadeira e possibilita a execução do código seguinte.

Adicionamos também mais um módulo ao programa: o Cwd fornece uma função chamada getcwd que quando executada retorna o diretório atual daonde o programa foi executado. O uso dessa função permite que o descritor.pl tente encontrar arquivos mp3 no diretório atual de execução.

Observe a diferença ao executar o programa de formas diferentes:

 jackal@valhalla:~$ ./descritor.pl /index.t/jackal/musicas
 Welcome to MP3 descriptor - version 1
 Will try to find files at /index.t/jackal/musicas

E agora sem parâmetro algum:

 jackal@valhalla:~$ ./descritor.pl
 Welcome to MP3 descriptor - version 1
 Will try to find files at current directory (/index.t/jackal)

Agora é hora de procurar esses arquivos mp3. Para evitar repetição de código no programa o ideal seria criar uma função para fazer isso. Funções em Perl são chamadas de subrotinas e a declaração dela ficaria da seguinte forma:

 # Essa função procura por arquivos mp3 no diretório
 # passado como argumento e retorna uma lista dos
 # arquivos encontrados
 sub find_mp3 {

     my $location = shift;
         $location .= '/';

     warn "No location passed as argument\n" unless ( defined($location) );

     my @list = <$location*.mp3>;

     warn "No MP3 files found\n" unless (@list);

     return @list;

 }

Uma declaração de subrotina começa sempre com a palavra reservada sub, seguida de seu nome. A declaração do código deve ser feita entre chaves (&147;{&147; e &147;}&148;). Subrotinas podem ser declaradas em qualquer lugar do programa, mas normalmente são feitas no início ou no fim do programa. Neste programa-exemplo, find_mp3 será declarada no final do arquivo descritor.pl.

Dentro da chave, perceba que declaramos outra variável chamada $location: ela não tem nenhuma relação com a outra variável de mesmo nome declarada no corpo principal do programa: a função de my é justamente evitar variáveis globais. $location terá como valor atribuído o valor retornado pela função shift, que por sua vez retorna o primeiro valor de um array, da esquerda para direita, diminuindo o tamanho do array durante o processo.

Se você achou isso complicado, esse exemplo rápido vai ajudar:

 my @colors = qw(blue yellow black red);
 print "@colors\n";
 my $first_value = shift (@colors);
 print "@colors\n";
 print "First value is = $first_value\n";

Executar o código gera como saída:

 4
 3
 First value is = blue

Diferentemente do código da função find_mp3, nesse exemplo foi passado um array como parâmetro para shift: por comodidade, se nenhum parâmetro é informado para essa função automaticamente ela adotará o array especial @ARGV. É possível simular listas e pilhas utilizando as funções pop, push, shift e unshift. Outras funções úteis de manipulação de array são map, sort, grep, reverse, splice, split, join e scalar.

A segunda linha de find_mp3 nos apresenta duas coisas: a função warn e unless. A função warn funciona como print, mas envia uma mensagem (de aviso) para STDERR ao invés de STDOUT. Já unless é a versão negada de if: ao invés de fazer if ( ! $condicao ) o programador pode usar unless. A diferença é meramente estética, mas é mais fácil entender o programa desta forma (se você souber ler em inglês, claro).

A terceira linha declara um array e usa uma variação da função glob. A função glob (ou sua variação usando "<" e ">") retorna uma lista de nomes de arquivos que combinem com um parâmetro fornecido. Nesse caso o glob funciona de forma muito semelhante à digitar em um shell um padrão de arquivo, como

 jackal@valhalla:~$ ls *.mp3

O que retornaria uma lista dos arquivos com nomes terminados em &147;.mp3&148;.

A terceira linha também poderia ser escrita desta forma:

    my @list = glob($location*.mp3);

retornando o mesmo resultado.

Funções em versões modernas do Perl (5.6 e maiores) são chamadas utilizando-se o nome da função seguida de parênteses, mesmo que você não passe nenhum parâmetro para a função. Obviamente, parâmetros para a função devem ser colocados dentro dos parênteses.

Basta agora fazer a chamada da função, como é mostrado abaixo:

 my $location;
 my @mp3_list;

 if (@ARGV) {

     $location = $ARGV[0];
     print "Will try to find files at $location\n";

 } else {

     $location = getcwd;
     print "Will try to find files at current directory ($location)\n";

 }
     @mp3_list = find_mp3($location);

Repare que declarei o array @mp3_list antes da declaração if-then-else ao invés de fazê-lo dentro de cada chamada da função find_mp3. Devido ao escopo aonde foi declarada é possível que @mp3_list seja utilizável por todo o programa (se você fizer dentro de um dos blocos perceberá que a pragma strict irá gerar uma exceção).

O programa já está quase pronto agora: basta agora ler a propriedade de cada arquivo encontrado e imprimir o resultado na tela (STDOUT). Essa é a parte mais complicada do programa, se o leitor considerar que a primeira coisa é entender como recuperar essas informações de um arquivo mp3. Eu mesmo não tenho a menor idéia de como fazer isso.

Felizmente para mim e você, alguém já teve esse trabalho e escreveu o código necessário, disponibilizando um módulo no CPAN.

Como você verá, usar o módulo não exige mais do que a declarar sua importação e executar uma função, que retornará o resultado de alguma forma. Isso funciona bem na maioria dos casos, já que os detalhes da implementação do código estão escondidos do programador atrás de uma interface definida. Ler a documentação online do módulo também ajuda bastante. O módulo que vamos usar, no entanto, não está na distribuição padrão do Perl: você deve instalar ele. A maneira mais fácil, para o caso específico do descritor.pl, é ir até o website http://search.cpan.org e pesquisar por MP3::Info. Na página sobre o módulo, basta baixar o arquivo tar.gz e executar a sequência de comandos abaixo (como usuário root):

 # tar xzvf mp3-info.tar.gz
 # perl Makefile.pl
 # make
 # make test
 # make install

Existem diversas outras formas de instalar um módulo Perl: os módulos mais famosos já ganharam pacotes para sua distribuição Linux favorita, então você pode procurar no respectivo repositório por tal pacote. A maneira mais confiável, no entanto, de usar as últimas versões dos módulos disponíveis é utilizar o módulo CPAN (disponível na distribuição padrão do Perl):

 # perl -MCPAN -e shell

Isso habilita um shell para o usuário, aonde é permitido pesquisar e instalar módulos diretamente do CPAN desde que haja uma conexão disponível com a Internet (o módulo CPAN também suporta proxies). Essa pequena maravilha permite que interdependências entre os módulos sejam resolvidas automaticamente, tal qual o apt-get do Debian GNU Linux faz.

Com o módulo MP3::Info instalado, vamos modificar uma última vez o programa. Para evitar repetir linhas, apenas a parte modificada será mostrada, em destaque:

 use Cwd;
 use MP3::Info;

 # parte do código foi omitida aqui

 @mp3_list = find_mp3($location);

 die "Cannot work without a valid list. Sorry\n" unless (@mp3_list);

 foreach my $mp3_file (@mp3_list) {

     my $mp3_tags = get_mp3tag($mp3_file) or die "Cannot retrieve tags from $mp3_file: $!\n";
     my %mp3_tags = %{$mp3_tags};

     format STDOUT_TOP =
 Report about all mp3 files found.
 Total files found: @<<<<<<<<
                    scalar(@mp3_list)

 .

     format STDOUT =
 File @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
      $mp3_file

 ARTIST         | TITLE          | ALBUM        | GENRE        | TRACK
 ---------------+----------------+--------------+--------------+------
 @<<<<<<<<<<<<< | @<<<<<<<<<<<<< | @<<<<<<<<<<< | @<<<<<<<<<<< | @||||
 $mp3_tags{ARTIST}, $mp3_tags{TITLE}, $mp3_tags{ALBUM}, $mp3_tags{GENRE}, $mp3_tags{TRACKNUM}

 .

     write;

 }

 # Essa função procura por arquivos mp3 no diretório
 # passado como argumento e retorna uma lista dos
 # arquivos encontrados
 sub find_mp3 {

Apesar das poucas novas linhas, o código inserido executa uma quantidade razoável de operações. A primeira linha inserida usa a função die, que com esse nome sugestivo, faz a execução do programa ser abortada caso não exista nenhum item na lista @mp3_list, imprimindo a mensagem entre aspas passada como parâmetro.

A instrução foreach funciona basicamente como uma instrução for, com a diferença que você não precisa informar como deverá ser feita a iteração: foreach irá, automaticamente, obter o tamanho da lista informada (no caso, @mp3_list) e fazer iterações esse mesmo número de vezes. A sintaxe do foreach exige o uso de parênteses para declarar a lista desejada (que não precisa ser necessariamente um array) e a abertura e fechamento de um bloco, respectivamente usando &147;{&147; e &147;}&148;. Dentro desse bloco então são declaradas as instruções que serão executadas a cada iteração do foreach.

Na linha seguinte, executamos a função get_mp3tag, atribuindo seu retorno à variável $mp3_tags ou então abortar o programa com die caso essa função retorne um erro. get_mp3tag não é uma função padrão do Perl, mas ela foi importada para seu programa no momento que a declaração use MP3::Info foi executada. Repare como a palavra reservada or foi utilizada para criar uma sentença condicional: se a função get_mp3tag não retornar nada, as instruções depois de or serão executadas, no caso a função die. Repare também que na mensagem de die é concatenada a variável especial $! que armazena a mensagem de erro informada pelo sistema operacional. Isso é útil, por exemplo, quando você não tem permissão de leitura num arquivo que tenta ler.

A documentação online do módulo MP3::Info informa que a função get_mp3tag retorna uma referência a um hash. Lembra-se quando eu disse que variáveis escalares podem armazenar referências? Pois bem, esse é um caso. No entanto, para fins didáticos, não vamos nos aprofundar em referências: apenas confie no fato de que a instrução seguinte obtem os valores apontados pela referência armazenada em $mp3_tags e copia para um novo hash.

Mas o que é um hash? O hash é o terceiro e último tipo de variável existente em Perl: tal como um array, um hash é uma lista de escalares: a grande diferença entre um e outro é que o acesso aos itens de um hash não é feito através de um índice numérico, mas uma seqüência de caracteres chamada de chave. Hashes são muito práticos quando o programador não sabe a posição exata (o índice numérico) de um escalar dentro de uma lista: basta informa seu nome que o hash encontrará sozinho o item com o mesmo nome. Hashes também são muito úteis para pesquisas não-lineares em uma lista, já que Perl utiliza um algorítmo muito rápido para localizar essas chaves.

Hashes são declarados com o símbolo de percentual seguido do nome do hash. A declaração de seus elementos pode ser feita toda de uma vez ou inserida por vez, indicando sempre o nome da chave e depois seus respectivo valor:

 my %estados = ( 'SP', 'São Paulo','RJ', 'Rio de Janeiro', 'PA', 'Paraná');
 # ou ainda
 my %estados2 = ( SP => 'São Paulo', RJ => 'Rio de Janeiro', PA => 'Paraná');
 # colocando mais um estado em %estados2
 %estados2{MT} = 'Mato Grosso';

Seus elementos são acessados da mesma forma que um array, mas usando chaves (&147;{&147; e &147;}&148;) ao invés de colchetes.

 print "SP é a sigla para $estados{SP}\n";
 # apaga a entrada "Paraná"
 delete $estados2{PA}

É possível fazer a iteração entre os itens de um hash com foreach da mesma forma como é feito com um array utilizando as funções keys (que coincidentemente retorna uma lista com todas as chaves de um hash passado como parâmetro), values (que mais coincidentemente ainda retorna uma lista com todos os valores de um hash passado como parâmetro) e each (que, por mais incrível que pareça, retorna cada par (chave e valor) de um hash passado como parâmetro). Mas para o descritor.pl não vamos utilizar nenhuma desses funções para iterar sobre o hash: a função get_mp3tag retorna mais tags do que precisamos. As tags que queremos são ARTIST, TITLE, ALBUM, GENRE e TRACKNUM, sendo que essas seqüências são as chaves do hash %mp3_tags.

Depois de acessar os dados desejados, basta gerar uma saída sobre as informações obtidas. Apesar de ser possível fazer isso com um simples print, isso não seria tão divertido: o resultado final fica muito melhor a definição de um formato de impressão, tal como um relatório, usando as funções format e write.

format espera um nome de formato para impressão como argumento (não use parênteses para passar os parâmetros): STDOUT_TOP e STDOUT são os formatos padrões para cabeçalho da saída padrão e saída padrão, respectivamente; você pode definir formatos alternativos, no entanto. O formato deve ser definido após uma quebra de linha: tudo o que você inserir ali, incluindo espaços, é considerado pelo Perl ao imprimir.

Campos no relatório são definidos com @<<<< (valores alinhados à esquerda), @>>>>> (valores alinhados à direita) e @|||||| (valores centralizados). Cada caracter &147;<&148;, &147;>&148; ou &147;|&148;, incluindo @, será substituído por cada caracter lido da expressão imediatamente após dessa declaração (quebra de linha). Uma expressão pode ser uma simples variável ou a chamada de uma função, como por exemplo scalar que força @mp3_list a retornar o número de itens que possui. Para terminar o formato, basta usar um ponto final sem nenhum espaço ou tabulação a esquerda do mesmo.

write imprime no handle de arquivo (por padrão STDOUT) o formato padrão definido pela variável especial $~ (que por padrão usa STDOUT).

Sem mais delongas, um teste final com o descriptor.pl mostraria algo parecido com isto:

* Exemplo de saída do programa:

saída.png

Não preciso lembrar o leitor sobre as implicações legais sobre utilização de mp3's. No entanto não existe problema nenhum em usá-los, por exemplo, no rádio do seu carro ou mp3 player caso você tenha os CD's originais.

Conclusão

Apresentar todos os recursos do Perl em um tutorial rápido como este é, no mínimo, complicado. Não é possível detalhar todas as variações de uso das funções demonstradas aqui. No entanto, o tutorial lhe fornece informações suficientes para criar programas úteis como descritor.pl. Perl possui amplo suporte à escrita em dispositivos (arquivos, named pipes e sockets) mas o uso desses recursos demandariam tutoriais à parte, então foram omitidos para manter a brevidade do tutorial. Logicamente, você pode fazer uso de todos redirecionadores de saída existentes no seu sistema operacional (desde que ele suporte esse tipo de recurso).

O programa descriptor.pl mostra uma possível solução para o problema proposto, e as variações possíveis para atingir o mesmo resultado são muito grandes. Esses mesmo programa pode ser reduzido em um punhado de linhas, o suficiente talvez para digitar o código diretamente na linha de comando (basta verificar, por exemplo, o código Perl equivalente do programa DeCSS(5) escrito originalmente em C). Obviamente o programa não seria nada legível.

Como exercício, você poderia melhorar muito esse programa: módulos como Getopt::Std e Getopt::Long permitiriam usar chaves para indicar parâmetros em linha de comando; os diversos módulos DBI ofereceriam suporte à diversos bancos de dados para inserir dados sobre sua coleção de mp3's e IO::Socket permitiria (com algum trabalho extra) fazer o programa consultar um repositório CDDB na falta de informações existentes num dos arquivos lidos. As possibilidades são enormes: verifique o repositório do CPAN mais perto de você e se deslumbre com um novo significado para reaproveitamento de código.

Bibliografia

Documentação online da distribuição padrão do Perl (em inglês):

* man perl
* perldoc perlintro
* perldoc perlform
* perldoc perlop

Endereços na Internet:

Site oficial do Perl:http://www.perl.org/

Site oficial dos Perl monks (grande repositório de tutoriais, sem contar o excelente fórum): http://www.perlmonks.org/

Grupos de programadores Perl do Brasil (oficialmente chamados de monges do Perl): http://brasil.pm.org/

Projeto de tradução da documentação online do Perl para o português do Brasil: http://perl.org.br/view/

Informações sobre CDDB (em inglês):

* http://musicbrainz.org/wd/AboutMusicBrainz
* http://www.freedb.org/modules.php?name=Sections&sop=viewarticle&artid=26#1-2

Informações sobre o DeCSS e sua versão em Perl:

* http://en.wikipedia.org/wiki/DeCSS
* http://www.wired.com/news/culture/0,1284,42259,00.html?tw=wn20010307

Obras impressas

Programação Perl, tradução em português da terceira edição em inglês Autores: Larry Wall, Tom Christiansen e Jon Orwant Editora: Campus ISBN: 85-352-0727-9

Guia de Consulta Rápida Perl Autor: Décio Jr. Editora: Novatec ISBN: 85-85184-80-9

Notas

Notes

1 : O nome do programa é *s2p*

2 : Você encontrará muitas referências sobre isso na Internet, mas abreviado em inglês: TIMTOWTDI (There Is More Than a Way To Do It).

3 : _Common Gateway Interface_, um padrão para criar aplicações web dinâmicas que ainda é muito utilizado.

4 : Essa palavra tem suas origens no fato do caracter de sustenido ser chamado de hash e o caracter de exclamação ser chamado de bang formando a palavra hashbang, que mais tarde foi reduzida para shebang.

5 : Programa para quebrar a criptografia utilizada em DVD's.

----

AUTHOR

Alceu Junior

blog comments powered by Disqus