3

I found this question and the first answer contains some example code demonstrating how to start an executable with Ada code. The output of the executable is written to the standard output.

What options do I have to read the output of the executable for further parsing/processing in Ada (for example line by line)?

Marcello90
  • 1,313
  • 4
  • 18
  • 33
  • 1
    One option is to use a pipe as explain in this question: https://stackoverflow.com/questions/48187514/read-input-from-a-pipe-in-ada – NeoSer Jun 15 '19 at 10:43

1 Answers1

3

If you use GNAT, then you might want to take a look at Get_Command_Output in the GNAT.Expect package. Here's an example:

with Ada.Text_IO, GNAT.Expect;

procedure Main is

   Command    : constant String := "gnat";   
   Argument_1 : aliased String  := "--version";
   Input      : constant String := ""; 
   Status     : aliased Integer := 0;      

   --  Execute the command and retrieve the output.

   Output : String :=
              GNAT.Expect.Get_Command_Output
                (Command    => Command,
                 Arguments  => (1 => Argument_1'Unchecked_Access),
                 Input      => Input,
                 Status     => Status'Access,
                 Err_To_Out => False);

   --  NOTE: Cheating with Unchecked_Access, OK for demo. You may want
   --        to properly new and Free these strings (see Argument_List 
   --        type in package GNAT.OS_Lib).

begin  
   Ada.Text_IO.Put_Line (Output);
end Main;

The program returns after execution:

$ ./main
GNAT Community 2019 (20190517-83)
Copyright (C) 1996-2019, Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

As can be seen, the result is returned as a single string. You will have do the line splitting yourself.


Update

An update in response to some comments below.

You might also consider using the system function if you're targeting the Windows platform (see also this post on SO). Quoting from the function reference:

The system function passes command to the command interpreter, which executes the string as an operating-system command.

This is similar to what the program cmd.exe does. You can obtain the output of the command by redirecting its output to a file (i.e. using >) and then subsequently read it back. Here's an example:

with Ada.Text_IO;              
with Ada.Text_IO.Unbounded_IO;
with Ada.Strings.Unbounded;

with Interfaces.C;
with Interfaces.C.Strings;         

procedure Main is

   use Ada.Strings.Unbounded;
   Content : Unbounded_String := Null_Unbounded_String;   

begin

   --  Execute.
   declare   

      use Interfaces.C;
      use Interfaces.C.Strings; 

      function system (command : chars_ptr) return int
        with Import, Convention => C, External_Name => "system";      

      command : chars_ptr := New_String("gnat --version > gnat_version.out");
      result  : int       := system (command); 

   begin
      --  Check value of result (omitted in this example).
      Free(Command);
   end;

   --  Read.
   declare

      use Ada.Text_IO;
      use Ada.Text_IO.Unbounded_IO;  

      Fd : File_Type;

   begin  
      Open (Fd, In_File, "./gnat_version.out");
      while not End_Of_File (Fd) loop
         Content := Content 
           & Unbounded_String'(Get_Line (Fd))
           & ASCII.CR & ASCII.LF;   --  Restore the line break removed by Get_Line.
      end loop;
      Close (fd);
   end;

   --  Show.
   Ada.Text_IO.Unbounded_IO.Put (Content);   

end Main;
DeeDee
  • 5,654
  • 7
  • 14
  • Your code example works fine but I noticed a strange behaviour (on Windows 7): If one tasks runs cyclically your code example and another task writes to the standard output using `Ada.Text_IO.Put_Line`, it happens often that the variable `Output` contains not the expected output of the executed program but the last or multiple lines that were written by the second task to the standard output. Am I missing something? – Marcello90 Jul 12 '19 at 17:36
  • 1
    I suspect that `GNAT.Expect.Get_Command_Output` is not thread-safe. In particular, as `Get_Command_Output` interacts with the standard streams (`stdin`, `stdout` and `stderr`) under the hood (see e.g. [here](https://gcc.gnu.org/viewcvs/gcc/trunk/gcc/ada/libgnat/g-expect.adb?view=markup#l1310)), I would avoid simultaneous execution of `Get_Command_Output` and any other standard stream related subprograms (e.g. `Put_Line` to `stdout`) in order to prevent race conditions or other kinds of unexpected behavior. – DeeDee Jul 13 '19 at 10:11
  • Ok, I can not avoid simultaneous execution. I am using a mutex based on the type `GNAT.Semaphores.Binary_Semaphore` to prevent the execution of mutliple commands at the same time from different tasks. I tried [this](https://gcc.gnu.org/viewcvs/gcc/trunk/gcc/ada/libgnat/s-os_lib.ads?view=markup#l911) procedure `Spawn` and read the file content into a string using [this](https://www.rosettacode.org/wiki/Read_entire_file#Ada.Direct_IO) method. Unfortunately the result is the same. How can I execute a command on system level using output redirection? For example: `ls > /path/to/output-file.txt`. – Marcello90 Jul 13 '19 at 17:35
  • 1
    From what I read in the comments near the ```Spawn``` function (see [here](https://gcc.gnu.org/viewcvs/gcc/trunk/gcc/ada/libgnat/s-os_lib.ads?view=markup#l949)), spawning a new process from a task is in general problematic and should be avoided. The comments also provide a possible solution (see [here](https://gcc.gnu.org/viewcvs/gcc/trunk/gcc/ada/libgnat/s-os_lib.ads?view=markup#l1001)). I extended my answer with a small example on how you might execute a command on system level (Windows only), but I cannot guarantee it will work when it's executed from within a task. – DeeDee Jul 14 '19 at 10:33