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.
Nos sistemas operativos que suportem passagem de parâmetros aos programas, os programas pascal disponibilizam duas funções importantes:
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 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.
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.
Como foi dito, 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)
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; Copia:Boolean; i:integer; 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] in ['\','/'] then Copia:=True; // Quando encontrar a primeira '\' ou '/' activa a cópia (a contar da direita para a esquerda) if Copia then r:=s[i]+r; // Se já tiver encontrado uma '\' ou '/' (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…