Jump to content
pwseo

[tutorial] Passagem de parâmetros em linha de comandos

Recommended Posts

pwseo

Aqui fica um pequeno tutorial sobre como lidar com parâmetros em pascal.

Os interfaces por linha de comando (CLI, command-line interface) são, na minha opinião, os mais flexíveis e eficientes. Longe de serem os mais amigáveis para utilizadores novos, os CLI permitem aos utilizadores experientes comunicar aos programas, de forma rápida, concisa e precisa, o que pretendem fazer e como querem fazê-lo.

Uma vez que a maioria dos programadores iniciados faz apenas programas CLI (pelo menos nos exercícios académicos), penso que é interessante demonstrar como lidar com parâmetros passados aos nossos programas na linha de comandos.

ParamCount e ParamStr

Nos sistemas operativos que suportem passagem de parâmetros aos programas, os programas pascal disponibilizam duas funções importantes:

  • ParamCount
  • ParamStr

Podemos então obter o número de parâmetros passados ao programa com ParamCount e os parâmetros específicos com ParamStr(idx), sendo idx um inteiro contido em 1..ParamCount.

Existe uma peculiaridade sobre ParamStr. Se lhe passarmos o valor 0, esta função irá devolver-nos o caminho até ao nosso programa, acrescido do nome do executável.

program args1;

begin
  writeln(ParamCount, ' parâmetros.');
  writeln('caminho: ', ParamStr(0));
end.

C:\> args1.exe
0 parâmetros.
caminho: c:\args1.exe

Se quisermos então enumerar os parâmetros passados, poderíamos fazer algo como:

program args2;

var
  i: integer;
begin
  writeln(ParamCount, ' parâmetros.');
  for i := 1 to ParamCount do
    writeln(i, 'º parâmetro: ', ParamStr(i));
end.

C:\> args2.exe --isto --aquilo
2 parâmetros.
1º parâmetro: --isto
2º parâmetro: --aquilo

Qual é a utilidade de tudo isto? Bem, imaginem que querem fazer um programa que leia ou escreva um conjunto de registos de alunos num ficheiro. Podemos fazer com que o nome desse ficheiro seja especificado nos parâmetros que o utilizador lhe passar.

Para isso, basta guardarmos essa string numa variável para utilização posterior. Por exemplo:

program args3;

var
  f: file of integer;
begin
  assign(f, ParamStr(1));
  rewrite(f);
  write(f, 42);
  close(f);
end.

program args4;

var
  f: file of integer;
  i: integer;
begin
  assign(f, ParamStr(1));
  reset(f);
  read(f, i);
  close(f);
  writeln('Número lido: ', i);
end.

C:\> args3.exe teste
C:\> args4.exe teste
Número lido: 42

Podíamos agora fazer coisas mais complexas, como por exemplo um programa que adicione um aluno a uma base de dados presente num ficheiro (deixo o código a cargo da vossa imaginação):

C:\> programa.exe adicionar Pedro 24
Adicionado aluno "Pedro" de 24 anos.

O que interessa reparar neste último exemplo é que os parâmetros estão numa ordem definida. O que nós queremos mesmo é fazer algo como isto:

C:\> programa.exe alunos.db --name Pedro --age 24

Uma pequena nota: eu utilizo o prefixo -- para os parâmetros por ser o mais comum em *nix; pessoas do Windows estarão mais habituadas a algo como /add /name Pedro /age 24.

Vamos então ver como seria um programa desses:

program args5;

uses sysutils;

type
  RAluno = record
    nome: string[60];
    idade: byte;
  end;

var
  aluno: RAluno;
  db: file of RAluno;
  i: integer;
  fname: string;
begin
  fname := ParamStr(1);
  i := 2;
  while i < ParamCount do
  begin
    if ParamStr(i) = '--name' then
    begin
      // Falaremos deste ParamStr(i + 1) a seguir
      aluno.nome := ParamStr(i + 1);
      inc(i);
    end
    else if ParamStr(i) = '--age' then
    begin
      // Atenção nesta linha:
      // a função ParamStr devolve-nos strings, mas o
      // campo idade é um byte (uma espécie de inteiro)
      // e temos que fazer a conversão com StrToInt.
      aluno.idade := strtoint(ParamStr(i + 1));
      inc(i);
    end;
    inc(i);
  end;

  assign(db, fname);
  if fileexists(fname) then
    reset(db)
  else
    rewrite(db);
  // colocamos o cursor no final do ficheiro
  seek(db, filesize(db));
  write(db, aluno);
  close(db);
end.

Relativamente ao ParamStr(i + 1), basta pensar um pouco: o parâmetro actual (ex.: '--name') tem índice i, mas nós queremos guardar em alunos.nome o parâmetro seguinte, de índice i + 1.

Por esta razão, tecnicamente nós lemos 2 parâmetros e não apenas um, o que implica a utilização de um inc(i) no final da condição, de forma a que o loop não tente processar o parâmetro que contém o nome.

Se quiserem confirmar que funciona, compilem e executem este programa:

program args6;

type
  RAluno = record
    nome: string[60];
    idade: byte;
  end;

var
  f: file of RAluno;
  n: integer = 0;
  a: RAluno;
begin
  assign(f, ParamStr(1));
  reset(f);
  while not eof(f) do
  begin
    read(f, a);
    writeln(a.nome, ', ', a.idade, ' anos.');
    inc(n);
  end;
  close(f);
  writeln(n, ' aluno(s) lido(s).');
end.

C:\> args5.exe teste --name Pedro --age 24
C:\> args6.exe teste
Pedro, 24 anos.
1 aluno(s) lido(s).
C:\> args5.exe teste --age 22 --name Raquel
C:\> args6.exe teste
Pedro, 24 anos.
Raquel, 22 anos.
2 aluno(s) lido(s).

E pronto, é tudo. Espero ter avivado a curiosidade de alguns de vós acerca deste tema.

Atenção, o código acima foi testado (desde o args3 até ao args6) com fpc em linux. Alterei parte dos outputs para ficar mais familiar (i.e. Windows).

Peço desculpa pelo comprimento do tutorial e por eventuais desvios do assunto.

Em compensação, aqui fica um zip com os ficheiros utilizados: pascal-arguments.zip

Adenda importante

No código exemplificado optei por não fazer verificações essenciais como certificar-me que o utilizador introduziu o número de parâmetros necessários, verificar se o comando assign não deu erro e outros que tais pela simples razão de que isto são meros exemplos, e essas verificações iriam certamente aumentar o tamanho do código desnecessariamente.

Obviamente, programas seguros e robustos devem conter sempre essas verificações.

Share this post


Link to post
Share on other sites
thoga31

Um bom tutorial. :)

Porque não o metes na Wiki? Até terá mais visibilidade. :thumbsup:


Knowledge is free!

Share this post


Link to post
Share on other sites
pwseo

Pus agora na wiki. Mesmo assim prefiro o fórum, especialmente porque aqui os utilizadores podem colocar questões, e o tutorial pode evoluir. Considero ser, portanto, um pouco "cedo" para ir para a wiki. Mas já lá está, de qualquer forma :)

Aguardo questões de quem as tiver :P

Share this post


Link to post
Share on other sites
thoga31

Pus agora na wiki. Mesmo assim prefiro o fórum, especialmente porque aqui os utilizadores podem colocar questões, e o tutorial pode evoluir. Considero ser, portanto, um pouco "cedo" para ir para a wiki. Mas já lá está, de qualquer forma :)

Aguardo questões de quem as tiver :P

Até o quadro de Tutoriais do fórum vai acabar mais dia menos dia, pois é para centralizar tudo na Wiki.

Para que se possa discutir, foi instalado o plugin de discussão, presente no final de todas as páginas, excepto a Home Page. :)

E assim criamos a nossa Wikipédia portuguesa da programação, que não existe outra. Temos em certos dias 1000 visualizações, só para teres noção do sucesso crescente da Wiki. :thumbsup:

EDIT: e não há "cedo" e "tarde" para se colocar na Wiki. Se é bom, mete-se logo lá, os visitantes não têm de estar "à espera" de algo que existe e não sabem.


Knowledge is free!

Share this post


Link to post
Share on other sites
nunopicado

Inacreditavelmente ainda não tinha visto este tópico...

Só para adicionar um pormenor sobre os parametros de linha de comandos.

Como o Pedro disse e muito bem, os parametros vão de 1 a ParamCount.

Mas temos ainda um outro parâmetro válido, que é o próprio executável.

Se usarmos:

  s:= ParamStr(0);

A variável s irá guardar o caminho completo (inclusivé o nome do executável) do nosso programa.

Isto é útil para pelo menos duas coisas:

- Verificar se o executável tem o nome original (caso não queiramos que o utilizador possa mudar o nome do executável por algum motivo)

- Saber em que pasta está guardardo o executável.

Esta última, é lógico, é a mais importante. Se quisermos que o nosso software abra ou grave ficheiros na mesma pasta onde está guardado o executável (por exemplo, um ficheiro de configuração ou uma base de dados), podemos usar o seguinte:

Em FreePascal, Delphi, e outros com acesso à função ExtractFilePath (SysUtils)

  Path:=ExtractFilePath(ParamStr(0));

Outros compiladores Pascal sem acesso à ExtractFilePath

function GetExePath:String;
var
   s,r:String;
   i:integer;
   Copia:boolean;
begin
   s:=ParamStr(0); // Atribuimos à variável S o caminho completo do executável
   r:=''; // Limpa a variável de resultado
   Copia:=False;  // Assume à partida que não é para copiar caracteres
   i:=length(s);  // Atribui a i o tamanho do caminho completo
   repeat
      if s[i]='\' then Copia:=True;  // Quando encontrar a primeira '\' activa a cópia (a contar da direita para a esquerda)
      if Copia then r:=s[i]+r;  // Se já tiver encontrado uma '\' (já descartou o ficheiro), copia o caracter lido para a variável de resultado
      dec(i);  // Anda para trás um caracter
   until i=0;  // Pára quando I for 0
   GetExePath:=r;  // Devolve o resultado
end;

Deste modo é fácil atribuir a ficheiros o caminho do executável...


"A humanidade está a perder os seus génios... Aristóteles morreu, Newton já lá está, Einstein finou-se, e eu hoje não me estou a sentir bem!"

> Não esclareço dúvidas por PM: Indica a tua dúvida no quadro correcto do forum.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×
×
  • Create New...

Important Information

By using this site you accept our Terms of Use and Privacy Policy. We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.