Difference between revisions of "Command line parameters and environment variables"

From Lazarus wiki
Jump to navigationJump to search
(New page: When a program is started a user can give command line parameters and setup environment variables. For example the FreePascal compiler gets most of its parameters via command line options:...)
 
m (undo revision 0251B6 by Zoltanleo (talk): undo insertion of unnecessary trailing blank)
Tag: Undo
 
(35 intermediate revisions by 14 users not shown)
Line 1: Line 1:
When a program is started a user can give command line parameters and setup environment variables. For example the FreePascal compiler gets most of its parameters via command line options:
+
{{Command line parameters and environment variables}}
  
  fpc unit1.pas
+
On most (interactive) operating systems [[Program|programs]] can be started via a [[Command-line interface|command line interface]] (CLI) that allows supplying the program with additional data.
 +
The [[System unit|system]] (and [[Object Pascal|<syntaxhighlight lang="bash" inline>objPas</syntaxhighlight>]]) units provide basic [[Function|functions]] in order to access command-line supplied data.
 +
 
 +
== Overview ==
 +
 
 +
Although this page speaks of ''command-line'' parameters and environment variables, an actual command line interface (CLI) is not a requirement to use such data, nor to use the routines and terminology described here.
 +
However, since a CLI is more tangible the following instructions mainly to refer to a shell’s user experience.
 +
 
 +
=== Terminology ===
 +
 
 +
; Option
 +
: Options are yes/no flags. They usually assume the form <syntaxhighlight lang="bash" inline>‑‑dryrun</syntaxhighlight>/<syntaxhighlight lang="bash" inline>‑‑no‑dryrun</syntaxhighlight> (dryrun enabled or disabled respectively). Options are also called “switches”.
 +
; Parameter
 +
: Parameters are key-value-tuples. On the command line they look like: <syntaxhighlight lang="bash" inline>‑‑processors 4</syntaxhighlight>.
 +
; Argument
 +
: Arguments are simple words. On the command line they are usually separated by spaces (confer the <abbr title="internal field separator">IFS</abbr> variable in some shells). In a narrow sense ''arguments'' are all supplied words that are neither options or (part of) parameters. In general, though, arguments are all words on the command line not being interpreted in any way.
 +
; Environment variable
 +
: Environment variables refer to a labeled piece of storage in the environment. They are name-value-pairs. <!-- source: https://en.wikibooks.org/w/index.php?title=Bourne_Shell_Scripting/Environment&oldid=3677309#The_environment_and_environment_variables -->
 +
 
 +
Note, the CLI/OS provides the program only with arguments it is supposed to get:
 +
For example, escaped newline characters, file redirection (pipes or to a file), and assignment of environment variables will not be forwarded to the program.
 +
 
 +
=== Basics ===
 +
 
 +
Since the notion of “option” and “parameters” are already kind of high-level, different styles exist (short options vs. long options), and the system unit aims to provide the ''basic'' means to work with, all command line arguments are simply enumerated starting from zero.
 +
No interpretation is done.
 +
 
 +
The function {{Doc|package=RTL|unit=system|identifier=paramstr|text=<syntaxhighlight lang="pascal" inline>paramStr</syntaxhighlight>}} returns the n-th argument on the command line.
 +
<syntaxhighlight lang="pascal" inline>ParamStr(0)</syntaxhighlight> tries returning the name, and possibly full path to the executable program file.
 +
 
 +
== Command line parameters ==
 +
 
 +
=== The basics ===
 +
 
 +
A Pascal program can a access command line arguments by using the <syntaxhighlight lang="pascal" inline>paramStr</syntaxhighlight> [[Function|function]] in conjunction with {{Doc|package=RTL|unit=system|identifier=paramcount|text=<syntaxhighlight lang="pascal" inline>paramCount</syntaxhighlight>}}.
 +
<syntaxhighlight lang="pascal" inline>ParamCount</syntaxhighlight> returns the number of supplied arguments.
 +
 
 +
<syntaxhighlight lang="pascal" line>
 +
program listArguments(input, output, stdErr);
 +
{$mode objFPC}
 +
var
 +
i: integer;
 +
begin
 +
writeLn({$ifDef Darwin}
 +
// on Mac OS X return value depends on invocation method
 +
'This program was invoked via: ',
 +
{$else}
 +
// Turbo Pascal-compliant paramStr(0) returns location
 +
'This program is/was stored at: ',
 +
{$endIf}
 +
paramStr(0));
 +
 +
for i := 1 to paramCount() do
 +
begin
 +
writeLn(i:2, '. argument: ', paramStr(i));
 +
end;
 +
end.
 +
</syntaxhighlight>
 +
 
 +
Running this program (on a non-Mac&nbsp;OS&nbsp;Ⅹ platform) may look like this:
 +
<syntaxhighlight lang="text">$ ./listArguments foo bar able
 +
This program is/was stored at: /tmp/listArguments
 +
1. argument: foo
 +
2. argument: bar
 +
3. argument: able
 +
</syntaxhighlight>
 +
 
 +
The [[RTL]]’s system unit provides a <syntaxhighlight lang="pascal" inline>paramStr</syntaxhighlight> version that returns [[Shortstring|<syntaxhighlight lang="pascal" inline>shortString</syntaxhighlight>s]].
 +
Due to their implementation <syntaxhighlight lang="pascal" inline>shortString</syntaxhighlight>s are capped at 255 characters.
 +
However the user may supply longer command line arguments.
 +
To access them the {{Doc|package=RTL|unit=objpas|text=<syntaxhighlight lang="pascal" inline>objPas</syntaxhighlight> unit}} redefines <syntaxhighlight lang="pascal" inline>paramStr</syntaxhighlight> that returns [[Ansistring|<syntaxhighlight lang="pascal" inline>ansiString</syntaxhighlight>s]] instead which do not have this length limitation.
 +
The <syntaxhighlight lang="pascal" inline>objPas</syntaxhighlight> unit is automatically included in the [[Mode ObjFPC|<syntaxhighlight lang="pascal" inline>{$mode objFPC}</syntaxhighlight>]] (line&nbsp;2) and [[Mode Delphi|<syntaxhighlight lang="pascal" inline>{$mode Delphi}</syntaxhighlight>]] compiler modes.
 +
 
 +
The distributed <syntaxhighlight lang="pascal" inline>paramStr</syntaxhighlight> function tries to be [[Turbo Pascal]]-compliant.
 +
In TP <syntaxhighlight lang="pascal" inline>paramStr(0)</syntaxhighlight> returns the location of the program.
 +
However, the operating system needs to support that.
 +
Most notably, on Mac&nbsp;OS&nbsp;Ⅹ the value of <syntaxhighlight lang="pascal" inline>paramStr(0)</syntaxhighlight> depends on the invocation method, how the application is being started.
 +
 
 +
{{Note|Since <syntaxhighlight lang="pascal" inline>paramStr(0)</syntaxhighlight> relies on OS functionality, it is not suitable for [[Multiplatform Programming Guide|cross-platform programs]].}}
 +
{{Note|On Unixoid platforms it is impossible to accurately define the location of a file merely by a string.
 +
There could be multiple hard-links pointing to the same inode, the file could have already been deleted, parts of the file path might have changed, beside other details.
 +
For these reasons, <syntaxhighlight lang="pascal" inline>paramStr(0)</syntaxhighlight> is useless on Unixoid platforms.}}
 +
 
 +
The {{Doc|package=RTL|unit=getopts|text=<syntaxhighlight lang="pascal" inline>getOpts</syntaxhighlight> unit}} provides a few other routines for program invocation following a certain style.
 +
 
 +
=== User friendly ===
 +
 
 +
A good program should give a help message when invoked with the wrong parameters and it should follow a common way of giving parameters.
 +
The {{Doc|package=FCL|unit=custapp|text=unit <syntaxhighlight lang="pascal" inline>custApp</syntaxhighlight>}} that comes with FPC provides the {{Doc|package=FCL|unit=custapp|identifier=tcustomapplication|text=<syntaxhighlight lang="pascal" inline>TCustomApplication</syntaxhighlight> class}}, which provides functions to easily check and read parameters.
 +
Of course you can still access the parameters directly via <syntaxhighlight lang="pascal" inline>paramStr</syntaxhighlight> and <syntaxhighlight lang="pascal" inline>paramCount</syntaxhighlight>.
 +
 
 +
Every [[LCL]] application uses this automatically.
 +
The Application object is a TCustomApplication.
 +
 
 +
If you want to write a non LCL program, then create in [[Lazarus]] a new project of type “Console Application”.
 +
This will create a project1.lpr with some nice goodies, that almost all programs need.
 +
Go to the <syntaxhighlight lang="pascal" inline>doRun</syntaxhighlight> method.
 +
 
 +
==== Check for a parameter ====
 +
 
 +
With TCustomApplication you can access parameters by name.
 +
For example your program should print a help text when the user gave the common help parameter ''-h''.
 +
The ''-h'' is a short option.
 +
The long form is the ''--help''.
 +
To test whether the user called the program with ''-h'' or ''--help'' you can use:
 +
 
 +
<syntaxhighlight lang="delphi">
 +
if hasOption('h', 'help') then
 +
begin
 +
writeHelp;
 +
halt;
 +
end;
 +
</syntaxhighlight>
 +
 
 +
{{Note|In an LCL form you must prepend <syntaxhighlight lang="delphi" inline>application.</syntaxhighlight> in front of <syntaxhighlight lang="delphi" inline>hasOption</syntaxhighlight>.}}
 +
For example:
 +
 
 +
<syntaxhighlight lang="delphi" highlight="1">
 +
if application.hasOption('h', 'help') then
 +
begin
 +
writeHelp;
 +
halt;
 +
end;
 +
</syntaxhighlight>
 +
 
 +
If you only want to support the short option, use:
 +
<syntaxhighlight lang="delphi">
 +
if hasOption('h', '') then
 +
</syntaxhighlight>
 +
 
 +
If you only want to support the long option, use:
 +
 
 +
<syntaxhighlight lang="delphi">
 +
if hasOption('help') then
 +
</syntaxhighlight>
 +
 
 +
==== Read the parameter value ====
 +
 
 +
Each parameter can be given a value.
 +
For example:
 +
 
 +
<syntaxhighlight lang="bash">
 +
$ project1 -f filename
 +
</syntaxhighlight>
 +
 
 +
or in the long form:
 +
 
 +
<syntaxhighlight lang="bash">
 +
$ project1 --file=filename
 +
</syntaxhighlight>
 +
 
 +
In order to retrieve <syntaxhighlight lang="bash" inline>filename</syntaxhighlight> use:
 +
 
 +
<syntaxhighlight lang="delphi">
 +
writeLn('f=', getOptionValue('f', 'file'));
 +
</syntaxhighlight>
 +
 
 +
Note: if you get the error message “<syntaxhighlight lang="text" inline>Option at position 1 needs an argument : f.</syntaxhighlight>” then you forgot to add the option in the {{Doc|package=FCL|unit=custapp|identifier=tcustomapplication.checkoptions|text=<syntaxhighlight lang="delphi" inline>checkOptions</syntaxhighlight>}} call.
 +
 
 +
==== Checking parameters for validity ====
 +
 
 +
Command line parameters are free text, so the user can easily type errors.
 +
Checking the syntax of the parameters is therefore mandatory.
 +
You can use the [[doc:fcl/custapp/tcustomapplication.checkoptions.html|CheckOptions]] method for this:
 +
 
 +
You can define, what parameters are allowed, which ones ones need a parameter and in case of a syntax error you can get an error message plus the options that were wrong to print helpful and detailed errors.
 +
 
 +
Examples:
 +
<syntaxhighlight lang="delphi">
 +
errorMsg := checkOptions('hf:', 'help file:');
 +
</syntaxhighlight>
 +
 
 +
This allows passing short options <syntaxhighlight lang="bash" inline>‑f value</syntaxhighlight> and <syntaxhighlight lang="bash" inline>‑h</syntaxhighlight>.
 +
It allows passing long options <syntaxhighlight lang="bash" inline>‑‑help</syntaxhighlight> or <syntaxhighlight lang="bash" inline>‑‑file=filename</syntaxhighlight>.
 +
It does not allow <syntaxhighlight lang="bash" inline>‑‑help</syntaxhighlight> with a value, nor <syntaxhighlight lang="bash" inline>‑‑file</syntaxhighlight> without a value.
 +
 
 +
A Parameter Example:
 +
 
 +
<syntaxhighlight lang="pascal">procedure TMainForm.FormShow(Sender: TObject);
 +
var
 +
    I: Integer;
 +
    Params : TStringList
 +
    LongOpts : array [1..2] of string = ('debug-sync', 'config-dir:');
 +
begin
 +
    Params := TStringList.Create;
 +
    try
 +
        Application.GetNonOptions('hgo:', LongOpts, Params);
 +
        for I := 0 to Params.Count -1 do
 +
            debugln('Extra Param ' + inttostr(I) + ' is ' + Params[I]);  }
 +
    finally
 +
        FreeAndNil(Params);
 +
    end;
 +
end;
 +
</syntaxhighlight>
 +
 
 +
This example finds parameters and does not mix them with command line switches or options.
 +
In this example, the app also accepts the switches --debug-sync and --config-dir=somedir but they are not reported here.
 +
 
 +
Note that Application.GetNonOptions() takes the long options as an array but Application.CheckOptions takes the same data but as a string.
 +
Bit sad!
 +
 
 +
== Environment variables ==
 +
 
 +
The [[sysutils|<syntaxhighlight lang="pascal" inline>sysUtils</syntaxhighlight> unit]] defines three basic functions in order to retrieve environment variables.
 +
 
 +
* {{Doc|package=RTL|unit=sysutils|identifier=getenvironmentvariablecount|text=<syntaxhighlight lang="pascal" inline>getEnvironmentVariableCount</syntaxhighlight>}} returns the total number of environment variables
 +
* {{Doc|package=RTL|unit=sysutils|identifier=getenvironmentstring|text=<syntaxhighlight lang="pascal" inline>getEnvironmentString</syntaxhighlight>}} returns an environment variable by its index
 +
* {{Doc|package=RTL|unit=sysutils|identifier=getenvironmentvariable|text=<syntaxhighlight lang="pascal" inline>getEnvironmentVariable</syntaxhighlight>}} returns an environment variable by its key
 +
 
 +
Example:
 +
 
 +
<syntaxhighlight lang="pascal" line highlight="3,9">
 +
program environmentVariablesList(input, output, stdErr);
 +
uses
 +
sysUtils;
 +
var
 +
i: integer;
 +
begin
 +
for i := 0 to getEnvironmentVariableCount() - 1 do
 +
begin
 +
writeLn(getEnvironmentString(i));
 +
end;
 +
end.
 +
</syntaxhighlight>
 +
 
 +
It is also possible to load all environment variables to a {{Doc|package=RTL|unit=classes|identifier=tstringlist|text=<syntaxhighlight lang="delphi" inline>tStringList</syntaxhighlight> object}} and access to name-value pair easily.
 +
String lists automatically take care of the separator character which is by defaut is an [[=|equal sign (<syntaxhighlight lang="pascal" inline>'='</syntaxhighlight>)]].
 +
 
 +
<syntaxhighlight lang="delphi" line highlight="4,7,9,15,21,23,26">
 +
program environmentVariablesSorted(input, output, stdErr);
 +
{$mode objFPC}
 +
uses
 +
classes, sysUtils;
 +
var
 +
i: integer;
 +
environmentVariables: tStringList;
 +
begin
 +
environmentVariables := tStringList.create();
 +
 +
try
 +
// load all variables to string list
 +
for i := 0 to getEnvironmentVariableCount() - 1 do
 +
begin
 +
environmentVariables.append(getEnvironmentString(i));
 +
end;
 +
 +
environmentVariables.sort;
 +
for i := 0 to environmentVariables.count - 1 do
 +
begin
 +
writeLn(environmentVariables.names[i]:20,
 +
' = ',
 +
environmentVariables.valueFromIndex[i]);
 +
end;
 +
finally
 +
environmentVariables.free;
 +
end;
 +
end.
 +
</syntaxhighlight>
 +
 
 +
There is also a <syntaxhighlight lang="delphi" inline>tCustomApplication</syntaxhighlight> [[Method|method]] available to get all environment variables as a [[TStringList|<syntaxhighlight lang="delphi" inline>tStringList</syntaxhighlight>]] at once.
 +
Then the pattern may look like this:
 +
 
 +
<syntaxhighlight lang="delphi">
 +
var
 +
environmentVariables: tStringList;
 +
begin
 +
environmentVariables := tStringList.create();
 +
try
 +
application.getEnvironmentList(environmentVariables);
 +
 +
finally
 +
environmentVariables.free;
 +
end;
 +
end;
 +
</syntaxhighlight>
 +
 
 +
[[Category: Code]]

Latest revision as of 18:55, 16 May 2022

English (en) español (es) suomi (fi) français (fr) русский (ru)

On most (interactive) operating systems programs can be started via a command line interface (CLI) that allows supplying the program with additional data. The system (and objPas) units provide basic functions in order to access command-line supplied data.

Overview

Although this page speaks of command-line parameters and environment variables, an actual command line interface (CLI) is not a requirement to use such data, nor to use the routines and terminology described here. However, since a CLI is more tangible the following instructions mainly to refer to a shell’s user experience.

Terminology

Option
Options are yes/no flags. They usually assume the form ‑‑dryrun/‑‑no‑dryrun (dryrun enabled or disabled respectively). Options are also called “switches”.
Parameter
Parameters are key-value-tuples. On the command line they look like: ‑‑processors 4.
Argument
Arguments are simple words. On the command line they are usually separated by spaces (confer the IFS variable in some shells). In a narrow sense arguments are all supplied words that are neither options or (part of) parameters. In general, though, arguments are all words on the command line not being interpreted in any way.
Environment variable
Environment variables refer to a labeled piece of storage in the environment. They are name-value-pairs.

Note, the CLI/OS provides the program only with arguments it is supposed to get: For example, escaped newline characters, file redirection (pipes or to a file), and assignment of environment variables will not be forwarded to the program.

Basics

Since the notion of “option” and “parameters” are already kind of high-level, different styles exist (short options vs. long options), and the system unit aims to provide the basic means to work with, all command line arguments are simply enumerated starting from zero. No interpretation is done.

The function paramStr returns the n-th argument on the command line. ParamStr(0) tries returning the name, and possibly full path to the executable program file.

Command line parameters

The basics

A Pascal program can a access command line arguments by using the paramStr function in conjunction with paramCount. ParamCount returns the number of supplied arguments.

 1program listArguments(input, output, stdErr);
 2{$mode objFPC}
 3var
 4	i: integer;
 5begin
 6	writeLn({$ifDef Darwin}
 7			// on Mac OS X return value depends on invocation method
 8			'This program was invoked via: ',
 9		{$else}
10			// Turbo Pascal-compliant paramStr(0) returns location
11			'This program is/was stored at: ',
12		{$endIf}
13		paramStr(0));
14	
15	for i := 1 to paramCount() do
16	begin
17		writeLn(i:2, '. argument: ', paramStr(i));
18	end;
19end.

Running this program (on a non-Mac OS Ⅹ platform) may look like this:

$ ./listArguments foo bar able
This program is/was stored at: /tmp/listArguments
 1. argument: foo
 2. argument: bar
 3. argument: able

The RTL’s system unit provides a paramStr version that returns shortStrings. Due to their implementation shortStrings are capped at 255 characters. However the user may supply longer command line arguments. To access them the objPas unit redefines paramStr that returns ansiStrings instead which do not have this length limitation. The objPas unit is automatically included in the {$mode objFPC} (line 2) and {$mode Delphi} compiler modes.

The distributed paramStr function tries to be Turbo Pascal-compliant. In TP paramStr(0) returns the location of the program. However, the operating system needs to support that. Most notably, on Mac OS Ⅹ the value of paramStr(0) depends on the invocation method, how the application is being started.

Light bulb  Note: Since paramStr(0) relies on OS functionality, it is not suitable for cross-platform programs.

Light bulb  Note: On Unixoid platforms it is impossible to accurately define the location of a file merely by a string. There could be multiple hard-links pointing to the same inode, the file could have already been deleted, parts of the file path might have changed, beside other details.

For these reasons, paramStr(0) is useless on Unixoid platforms.

The getOpts unit provides a few other routines for program invocation following a certain style.

User friendly

A good program should give a help message when invoked with the wrong parameters and it should follow a common way of giving parameters. The unit custApp that comes with FPC provides the TCustomApplication class, which provides functions to easily check and read parameters. Of course you can still access the parameters directly via paramStr and paramCount.

Every LCL application uses this automatically. The Application object is a TCustomApplication.

If you want to write a non LCL program, then create in Lazarus a new project of type “Console Application”. This will create a project1.lpr with some nice goodies, that almost all programs need. Go to the doRun method.

Check for a parameter

With TCustomApplication you can access parameters by name. For example your program should print a help text when the user gave the common help parameter -h. The -h is a short option. The long form is the --help. To test whether the user called the program with -h or --help you can use:

if hasOption('h', 'help') then
begin
	writeHelp;
	halt;
end;
Light bulb  Note: In an LCL form you must prepend application. in front of hasOption.

For example:

if application.hasOption('h', 'help') then
begin
	writeHelp;
	halt;
end;

If you only want to support the short option, use:

if hasOption('h', '') then

If you only want to support the long option, use:

if hasOption('help') then

Read the parameter value

Each parameter can be given a value. For example:

$ project1 -f filename

or in the long form:

$ project1 --file=filename

In order to retrieve filename use:

writeLn('f=', getOptionValue('f', 'file'));

Note: if you get the error message “Option at position 1 needs an argument : f.” then you forgot to add the option in the checkOptions call.

Checking parameters for validity

Command line parameters are free text, so the user can easily type errors. Checking the syntax of the parameters is therefore mandatory. You can use the CheckOptions method for this:

You can define, what parameters are allowed, which ones ones need a parameter and in case of a syntax error you can get an error message plus the options that were wrong to print helpful and detailed errors.

Examples:

errorMsg := checkOptions('hf:', 'help file:');

This allows passing short options ‑f value and ‑h. It allows passing long options ‑‑help or ‑‑file=filename. It does not allow ‑‑help with a value, nor ‑‑file without a value.

A Parameter Example:

procedure TMainForm.FormShow(Sender: TObject);
var
    I: Integer;
    Params : TStringList
    LongOpts : array [1..2] of string = ('debug-sync', 'config-dir:');
begin
    Params := TStringList.Create;
    try
        Application.GetNonOptions('hgo:', LongOpts, Params);
        for I := 0 to Params.Count -1 do
            debugln('Extra Param ' + inttostr(I) + ' is ' + Params[I]);  }
    finally
        FreeAndNil(Params);
    end;
end;

This example finds parameters and does not mix them with command line switches or options. In this example, the app also accepts the switches --debug-sync and --config-dir=somedir but they are not reported here.

Note that Application.GetNonOptions() takes the long options as an array but Application.CheckOptions takes the same data but as a string. Bit sad!

Environment variables

The sysUtils unit defines three basic functions in order to retrieve environment variables.

Example:

 1program environmentVariablesList(input, output, stdErr);
 2uses
 3	sysUtils;
 4var
 5	i: integer;
 6begin
 7	for i := 0 to getEnvironmentVariableCount() - 1 do
 8	begin
 9		writeLn(getEnvironmentString(i));
10	end;
11end.

It is also possible to load all environment variables to a tStringList object and access to name-value pair easily. String lists automatically take care of the separator character which is by defaut is an equal sign ('=').

 1program environmentVariablesSorted(input, output, stdErr);
 2{$mode objFPC}
 3uses
 4	classes, sysUtils;
 5var
 6	i: integer;
 7	environmentVariables: tStringList;
 8begin
 9	environmentVariables := tStringList.create();
10	
11	try
12		// load all variables to string list
13		for i := 0 to getEnvironmentVariableCount() - 1 do
14		begin
15			environmentVariables.append(getEnvironmentString(i));
16		end;
17		
18		environmentVariables.sort;
19		for i := 0 to environmentVariables.count - 1 do
20		begin
21			writeLn(environmentVariables.names[i]:20,
22				' = ',
23				environmentVariables.valueFromIndex[i]);
24		end;
25	finally
26		environmentVariables.free;
27	end;
28end.

There is also a tCustomApplication method available to get all environment variables as a tStringList at once. Then the pattern may look like this:

var
	environmentVariables: tStringList;
begin
	environmentVariables := tStringList.create();
	try
		application.getEnvironmentList(environmentVariables);
		
	finally 
		environmentVariables.free;
	end;
end;