Recebendo Argumentos via Linha de Comando em Erlang Script

09 Apr 2012 . category: article . Comments
#erlang #command line #scripts

Introdução

Atualmente estou estudando a linguagem de programação Erlang, ainda estou acostumando-me com a linguagem, porém este mundo já é conhecido, porque há um bom tempo venho estudando linguagens funcionais(há um post que estou escrevendo sobre o assunto, comecei ano passado e ainda sinto que não estou totalmente preparado para terminá-lo). Além disso, em uma das disciplinas que estou cursando esse semestre pude escolher esta linguagem como foco dos meus estudos.

Como primeiros testes com algo útil, quero reescrever em Erlang o meu projeto FrontFile, que para mim talvez seja os scripts mais úteis que criei até hoje, que foi escrito em Perl.

O Erlang é uma linguagem desenvolvida pela Ericsson visando aplicações para telefonia que são altamente concorrentes. Também é bastante utilizado em aplicações financeiras e servidores de mensagens instantâneas. Porém ele também pode ser utilizado para desenvolver script.

Passagem de parâmetros via linha de comando

Existe um modelo de para passagem de parâmetros via linha de comando para programas chamada getopt. Ela foi introduzida no Bash em 1986 e vem sendo utilizada por diversos programas. Veja um exemplo utilizando o comando ls.

ls --all -B

Nos links abaixo do post é possível encontrar mais informações sobre esse padrão de passagem de parâmetros via linha de comando.

Parseando os Parâmetros em Erlang

O Erlang Script não possui esse tipo de recurso nativamente, porém existe um projeto no githup que implementa o getopt. Para utilizá-lo em sua aplicação basta baixar os arquivos do repositório e compilar utilizando o make. Veja os comandos necessário, lembrando que o git deve estar instalado, se não estiver você pode fazer o download do pacote do projeto na própria página do github:

$ git clone https://github.com/jcomellas/getopt

$ cd getopt

$ make

Para podermos utilizar o módulo compilado temos que torná-lo visível ao nosso script, para isso temos algumas opções, as que eu conheço são as seguintes.

  • Colocar no mesmo diretório onde o comando será executado

    Essa é a forma mais simples, porém ela exige que você sempre execute o seu script do diretório onde estiver a biblioteca que será invocada. Se você executar o comando de outro diretório o script não irá conseguir carregar a biblioteca. Nós utilizaremos esta abordagem em nosso exemplo.

  • Adicionar no diretório de libs do Erlang

    Os arquivos da lib podem ser adicionadas no diretório padrão de libs que fica em /usr/lib/erlang/lib ou /usr/lib64/erlang/lib para sistemas de 64 bits. Esse é o caminho para sistemas unix-like (Me desculpem pessoal ai do Windows, não conheço muito deste sistema). O problema desta abordagem e que muitas vezes não iremos ter permissão de escrita nestes diretórios.

  • Utilizar o comando code:add_path(“CaminhoParaDiretorio”)

    Talvez esse modo seja o mais flexível. O diretório passado será carregado em tempo de execução.

  • Setar a variável de ambiente $ERL_LIBS

    Tive alguns problemas com esse modo, parece que nem todos os sistemas utilizam está variável. Ainda irei testar mais vezes e qualquer descoberta eu adiciono neste post. Talvez ela seja utilizadas apenas em aplicações que foram construídas utilizando o OTP.

Implementando

Criei um exemplo simples para demonstrar o uso de passagem de parâmetros. O exemplo lê um arquivo do tipo csv com notas de alunos e calcula a média de cada aluno. Os parâmetros que o script pode receber é a mensagem quando o aluno é aprovado, quando é reprovado e a média que o aluno precisa obter para ser aprovado. Esses são parâmetros opcionais que se não forem informados receberão um valor padrão informado no código. O caminho até o arquivo também deve ser informado.

Como de costume, tentei comentar o bem o código, explicando tudo que o que foi feito no código e qualquer dúvida peço que utilize o sistema de comentário do site.

# !/usr/bin/env escript
% O arquivo que será lido é um csv como o descrito abaixo
% Nome, nota1, nota2, nota3

-module(exemplo).
-author('rubin.diego@gmail.com').

% Função que é chamada quando o script é executado.
% A primeira assinatura é executada quando nenhum parametro é passado.
main([]) ->
  usage();
main(Args) ->
  {ok, {Options, Arquivo}} = getopt:parse(option_spec_list(),Args),
  io:format("Executando o script ~n"
            "Args: ~p~n~n"
            "Options: ~p~n~n"
            "Arquivo: ~p~n~n", [Args, Options, Arquivo]),

  [Media|_] = [X || {media, X} <- Options],
  [Aprovado|_] = [X || {aprovado, X} <- Options],
  [Reprovado|_] = [X || {reprovado, X} <- Options],

  read(Arquivo, Media, Aprovado, Reprovado).

% Se não houver nenhum parametro passado via linha de comando
% essa função será executada.
usage() ->
  usage(escript:script_name()).

usage(Name) ->
  getopt:usage(option_spec_list(), Name, "arquivo", 
               [{"arquivo", "Arquivo csv contendo as informações descritas no código."}]).

% Uma simples função que retorna a configuração dos parametros esperados.
option_spec_list() ->
  [
    % {Nome, NomeCurto,  NomeLongo,  Tipo Esperado, Mensagem de Ajuda}
    {aprovado, $a, "aprovado", {string, "Aprovado"}, "Mensagem que será exibida se o aluno foi aprovado"},
    {reprovado, $r, "reprovado", {string, "Reprovado"}, "Mensagem que será exibida se o aluno foi reprovado"},
    {media, $m, "media", {float, 5}, "Média minima para o aluno ser aprovado"}
  ].

% Leitura do arquivo com os dados dos alunos e calculo das média.
read(Filename, Media, Aprovado, Reprovado) ->
  {ok, Fd} = file:open(Filename, [read]),
  read(io:get_line(Fd, ""), Fd, Media, Aprovado, Reprovado).

read(eof, File, _Media, _Aprovado, _Reprovado) ->
  file:close(File);
read(Line, File, Media, Aprovado, Reprovado) ->
  {Nome, Resultado} = calculate(string:strip(Line, right, $\n)),
  resultado(Nome, Resultado, Media, Aprovado, Reprovado),
  Content = io:get_line(File, ""),
  read(Content, File, Media, Aprovado, Reprovado).

% A função pega as informações de uma linha e trata as mesmas
calculate(Line) ->
  % As informações no arquivo estão dispostas como o exemplo no comeco do arquivo.
  % Iremos quebrar as informações separadas por vírgulas.
  Infos = string:tokens(Line, ","),
  [Nome|Notas] = Infos,
  Resultado = (lists:sum([list_to_float(string:strip(X)) || X <- Notas ])/3.0),
  {Nome, Resultado}.

% Verifica se o aluno foi aprovado ou não e imprime o resultado na tela.
resultado(Nome, Resultado, Media, Aprovado, Reprovado) ->
  Mensagem = if 
    Resultado >= Media ->
      Aprovado;
    true ->
      Reprovado
    end,
  io:format("~s ~s ~.1f~n", [Nome, Mensagem, Resultado]).

O script pode ser executado com o seguinte comando:

./exemplo.escript -a "foi aprovado com média" -r "foi reprovado com média" -m 4.5 notas

Lembrando que o arquivo deve ter permissão de execução.

Conclusão

Estou iniciando minha jornada no mundo do Erlang, porém tenho alguns projetos onde espero utilizar essa tecnologia e muito em breve quero escrever mais artigos abordando a linguagem.

Também gostaria de deixar um pedido, se alguém puder dar alguma dica, alguma boa prática da tecnologia que está ausente no código, por favor, deixe nos comentários.

E lembrando também que o código do exemplo pode ser encontrado no repositório do blog no github.


Me

Tenho estudado esse mundo mágico da programação desde 2005. Já consegui sustentar minha família usando Ruby, Java, Python, C++ e Javascript. O resto tenho usado para diversão ou aprendizado.