views:

290

answers:

2

I'm trying to use some windows command line tools from within a short Pascal program. To make it easier, I'm writing a function called DoShell which takes a command line string as an argument and returns a record type called ShellResult, with one field for the process's exitcode and one field for the process's output text.

I'm having major problems with some standard library functions not working as expected. The DOS Exec() function is not actually carrying out the command i pass to it. The Reset() procedure gives me a runtime error RunError(2) unless i set the compiler mode {I-}. In that case i get no runtime error, but the Readln() functions that i use on that file afterwards don't actually read anything, and furthermore the Writeln() functions used after that point in the code execution do nothing as well.

Here's the source code of my program so far. I'm using Lazarus 0.9.28.2 beta, with Free Pascal Compiler 2.24


program project1;

{$mode objfpc}{$H+}

uses
  Classes, SysUtils, StrUtils, Dos
  { you can add units after this };

{$IFDEF WINDOWS}{$R project1.rc}{$ENDIF}

type
  ShellResult = record
    output    : AnsiString;
    exitcode  : Integer;
  end;

function DoShell(command: AnsiString): ShellResult;
    var
      exitcode: Integer;
      output: AnsiString;
      exepath: AnsiString;
      exeargs: AnsiString;
      splitat: Integer;
      F: Text;
      readbuffer: AnsiString;
    begin
      //Initialize variables
      exitcode   := 0;
      output     := '';
      exepath    := '';
      exeargs    := '';
      splitat    := 0;
      readbuffer := '';
      Result.exitcode := 0;
      Result.output   := '';

      //Split command for processing
      splitat := NPos(' ', command, 1);
      exepath := Copy(command, 1, Pred(splitat));
      exeargs := Copy(command, Succ(splitat), Length(command));

      //Run command and put output in temporary file
      Exec(FExpand(exepath), exeargs + ' >__output');
      exitcode := DosExitCode();

      //Get output from file
      Assign(F, '__output');
      Reset(F);
      Repeat
        Readln(F, readbuffer);
        output := output + readbuffer;
        readbuffer := '';
      Until Eof(F);

      //Set Result
      Result.exitcode := exitcode;
      Result.output   := output;

    end;

var
  I : AnsiString;
  R : ShellResult;
begin
  Writeln('Enter a command line to run.');
  Readln(I);
  R := DoShell(I);
  Writeln('Command Exit Code:');
  Writeln(R.exitcode);
  Writeln('Command Output:');
  Writeln(R.output);
end.
A: 

At a quick look I see that you try to split command based on space. What if:

  • I try execute something without parameters, like fpc? (answer: exepath will be empty)
  • I try execute something with full path and with space in it like C:\Program Files\Edit Plus 3\editplus.exe?

I tried Exec() and it seems to work when you give it full path to executable you want to run, but output redirection does not work. Look at: Command line redirection is performed by the command line interpreter. However you can execute .bat file that does redirection (create temporary .bat file with command user gives + redirection, and run that batch).

Michał Niklas
That's why sysutils.executeprocess (with an array of const as arguments) was introduced in 2004, and dos.exec has been deprecated ever since
Marco van de Voort
A: 

Do not use dos.exec, it is limited to a short (255 char) commandline. Use sysutils.executeprocess.

However Michal's comments probably touch the main issue. When executing via kernel (not shell) functions, one should always provide a complete path. Also, using kernel functions one can't use shell commands like redirection.

In general, I suggest you try to use the "TProcess" class in the "process" unit. It abstracts all of this anad more, and is also used by Lazarus to call external tools.

Marco van de Voort