Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to read value of ParamStr with different deliminators?

When you read ParamStr(), it's deliminated by spaces between each parameter. However, I've seen many command-line arguments which accept a space between the parameter name and its paired value, while also accepting an equals = sign and even no deliminator (just prefixed with the param name) or no value.

Here's some examples of possible param strings:

-name value
/name value
-name=value
/name=value
-namevalue
/namevalue
-name -nextname
/name /nextname
-name="value with spaces"
/name="value with spaces"

...etc.

What I would like to do is two things both related... Check if a parameter name exists, and read the value of the parameter. For example...

if ParamExists('ParamName') then
  SomeString:= ParamValue('ParamName')
else
  SomeString:= 'SomeOtherString';

Is there something in Delphi which can do this? If not, how do I do this? Everything I find when searching for this just leads me to the same basic example:

for i := 0 to ParamCount do
  ShowMessage(ParamStr(i));

It also needs to be case sensitive. I'm looking for something in particular like OSQL and similar command-line tools use where '-s' could be different from '-S'.

The problem is that if I use a space as a deliminator, I have no clue how to recognize when it's part of the previous parameter, because it splits them by spaces. How do I get around this?

I'm sure there's a standard term for this too, it's the common formatting of command-line arguments. But I don't know how to read them properly using just ParamStr. It seems ParamStr falls short of what it's usually used for.

To be clear, I don't necessarily need to support every above example - those are just examples I've seen before.

like image 947
Jerry Dodge Avatar asked Jun 15 '13 06:06

Jerry Dodge


2 Answers

ParamStr() (and consequently FindCmdLineSwitch()) is not flexible enough to handle all of the examples you have shown. You will have to call the Win32 API GetCommandLine() function and parse it manually.

like image 145
Remy Lebeau Avatar answered Nov 02 '22 23:11

Remy Lebeau


Ironically, just last night I wrote something for this, and just a little bit ago found this got up-voted. Here's a class I just wrote encapsulating this:

unit CmdLine;

(*
  Command Line Parser
  by Jerry Dodge

  Class: TCmdLine
  - Parses out a command line into individual name/value pairs
  - Concatenates name/value pairs into a command line string
  - Property "ModuleFilename" for the current executable path
  - Property "OpenFilename" for the file to be opened, if any
  - Default property "Values" to read/write name/value pairs
*)

interface

uses
  System.Classes, System.SysUtils;

type
  TCmdLine = class(TObject)
  private
    FItems: TStringList;
    FModuleFilename: String;
    FOpenFilename: String;
    function GetAsString: String;
    procedure SetAsString(const Value: String);
    procedure SetModuleFilename(const Value: String);
    procedure SetOpenFilename(const Value: String);
    function GetValue(const Name: String): String;
    procedure SetValue(const Name, Value: String);
    function GetName(const Index: Integer): String;
  public
    constructor Create;
    destructor Destroy; override;
    function Count: Integer;
    function Exists(const N: String; const IgnoreCase: Boolean = False): Boolean;
    property ModuleFilename: String read FModuleFilename write SetModuleFilename;
    property OpenFilename: String read FOpenFilename write SetOpenFilename;
    property AsString: String read GetAsString write SetAsString;
    property Names[const Index: Integer]: String read GetName;
    property Values[const Name: String]: String read GetValue write SetValue; default;
  end;

implementation

{ TCmdLine }

constructor TCmdLine.Create;
begin
  FItems:= TStringList.Create;
end;

destructor TCmdLine.Destroy;
begin
  FItems.Free;
  inherited;
end;

function TCmdLine.Count: Integer;
begin
  Result:= FItems.Count;
end;

function TCmdLine.Exists(const N: String; const IgnoreCase: Boolean = False): Boolean;
var
  X: Integer;
begin
  Result:= False;
  for X := 0 to FItems.Count-1 do begin
    if IgnoreCase then begin
      if SameText(N, FItems.Names[X]) then begin
        Result:= True;
        Break;
      end;
    end else begin
      if N = FItems.Names[X] then begin
        Result:= True;
        Break;
      end;
    end;
  end;
end;

procedure TCmdLine.SetModuleFilename(const Value: String);
begin
  FModuleFilename:= Value;
end;

procedure TCmdLine.SetOpenFilename(const Value: String);
begin
  FOpenFilename:= Value;
end;

function TCmdLine.GetValue(const Name: String): String;
begin
  Result:= FItems.Values[Name];
end;

procedure TCmdLine.SetValue(const Name, Value: String);
begin
  FItems.Values[Name]:= Value;
end;

function TCmdLine.GetAsString: String;
var
  X: Integer;
  Cmd: String;
  Val: String;
begin
  Result:= '"'+FModuleFilename+'"';
  if Trim(FOpenFilename) <> '' then
    Result:= Result + ' "'+FOpenFilename+'"';
  for X := 0 to FItems.Count-1 do begin
    Cmd:= FItems.Names[X];
    Val:= FItems.Values[Cmd];
    Result:= Result + ' -'+Cmd;
    if Trim(Val) <> '' then begin
      Result:= Result + ' ';
      if Pos(' ', Val) > 0 then
        Result:= Result + '"'+Val+'"'
      else
        Result:= Result + Val;
    end;
  end;
end;

function TCmdLine.GetName(const Index: Integer): String;
begin
  Result:= FItems.Names[Index];
end;

procedure TCmdLine.SetAsString(const Value: String);
var
  Str: String;
  Tmp: String;
  Cmd: String;
  Val: String;
  P: Integer;
begin
  FItems.Clear;
  FModuleFilename:= '';
  FOpenFilename:= '';
  Str:= Trim(Value) + ' ';

  //Extract module filename
  P:= Pos('"', Str);
  if P = 1 then begin
    Delete(Str, 1, 1);
    P:= Pos('"', Str);
    Tmp:= Copy(Str, 1, P-1);
    Delete(Str, 1, P);
    FModuleFilename:= Tmp;
  end else begin
    P:= Pos(' ', Str);
    Tmp:= Copy(Str, 1, P-1);
    Delete(Str, 1, P);
    FModuleFilename:= Tmp;
  end;

  Str:= Trim(Str) + ' ';

  //Extract open filename
  P:= Pos('"', Str);
  if P = 1 then begin
    Delete(Str, 1, 1);
    P:= Pos('"', Str);
    Tmp:= Copy(Str, 1, P-1);
    Delete(Str, 1, P);
    FOpenFilename:= Tmp;
  end else begin
    P:= Pos('-', Str);
    if P < 1 then
      P:= Pos('/', 'Str');
    if P < 1 then begin
      P:= Pos(' ', Str);
      Tmp:= Copy(Str, 1, P-1);
      Delete(Str, 1, P);
      FOpenFilename:= Tmp;
    end;
  end;

  Str:= Trim(Str) + ' ';

  //Extract remaining param switches/values
  while Length(Trim(Str)) > 0 do begin
    P:= Pos('-', Str);
    if P < 1 then
      P:= Pos('/', 'Str');
    if P > 0 then begin
      Delete(Str, 1, 1);
      P:= Pos(' ', Str);
      Tmp:= Trim(Copy(Str, 1, P-1));
      Delete(Str, 1, P);
      if Pos('"', Tmp) = 1 then begin
        Delete(Tmp, 1, 1);
        P:= Pos('"', Tmp);
        if P > 0 then
          Delete(Tmp, 1, 1);
      end;
      Cmd:= Tmp;
      Str:= Trim(Str) + ' ';
      if (Pos('-', Str) <> 1) and  (Pos('/', Str) <> 1) then begin
        P:= Pos('"', Str);
        if P = 1 then begin
          Delete(Str, 1, 1);
          P:= Pos('"', Str);
          Tmp:= Copy(Str, 1, P-1);
          Delete(Str, 1, P);
        end else begin
          P:= Pos(' ', Str);
          Tmp:= Copy(Str, 1, P-1);
          Delete(Str, 1, P);
        end;
        Val:= Tmp;
      end else begin
        Val:= '';
      end;
      if Val = '' then
        Val:= ' ';
      FItems.Values[Cmd]:= Val;
    end else begin
      Str:= '';
      raise Exception.Create('Command line parameters malformed ('+Str+')');
    end;
    Str:= Trim(Str) + ' ';
  end;
end;

end.
like image 20
Jerry Dodge Avatar answered Nov 03 '22 00:11

Jerry Dodge