How do you call an external program in Ada?

Viewed 683

How do you call an external command (as if I'd typed it at the Unix shell or Windows command prompt) from within Ada program text?

I'd prefer a non-gnat-specific solution, but I'll post what I found on my own as an initial answer anyway.

4 Answers

You could also use system. Supported on e.g.

Note that errors are written to standard error.

main.adb

with Ada.Text_IO;
with Interfaces.C;

procedure Main is

   package C renames Interfaces.C;
   use type C.int;
   
   function system (command : C.char_array) return C.int
     with Import, Convention => C;

   command : aliased constant C.char_array :=
     C.To_C ("mv README.md README.txt");

   result : C.int;

begin
   result := system (command);
   if result = 0 then
      Ada.Text_IO.Put_Line ("OK");
   else
      Ada.Text_IO.Put_Line ("Failed");
   end if;
end Main;
with GNAT.OS_Lib;
procedure main is
   Exit_Code : Integer;
begin
   Exit_Code := GNAT.OS_Lib.Spawn (Program_Name => "mv", Args => (new String'("README.md"), new String'("README.txt")));
   if Exit_Code /= 0 then
      raise Program_Error with "Exit code:" & Exit_Code'Image;
   end if;
end main;

Change 'mv' to 'move' if you are on windows.

See the GNAT.OS_Lib documentation to extend this for non-blocking and/or capturing the stdout/stderr.

We have a nice Process API with asynchronous notifications. Take a look at an example. It work on Linux, Windows and Mac OS X. There is optional integration with Glib event loop.

   Args : Spawn.String_Vectors.UTF_8_String_Vector;
   L    : aliased Listeners.Listener;
begin
   Args.Append ("Hello World");
   P.Set_Program ("/bin/echo");
   P.Set_Arguments (Args);
   P.Set_Working_Directory ("/tmp");
   P.Set_Listener (L'Unchecked_Access);
   P.Start;

Based on my comments regarding the Florist library (see other answer), I added another example (just for reference and info, not as a serious answer to the original question). Unfortunately, the version of Florist I refer to in the comments does not compile on recent versions of GNAT (e.g. GNAT CE 2020). This version of the library seems to have become somewhat dependent on GNAT's internals which have been modified slightly (so the library is not as compiler independent anymore as originally intended). Nonetheless, for completeness, if you would be able to compile it (using either an older version of GNAT or by repairing the source code), then the example below would spawn a child process without blocking and wait for it to finish.

main.adb

with Ada.Text_IO; use Ada.Text_IO;
with POSIX;       use POSIX;

with POSIX.Process_Primitives;
with POSIX.Process_Identification;

procedure Main is
   
   package PP renames POSIX.Process_Primitives;
   package PI renames POSIX.Process_Identification;
   
   Filename : constant POSIX_String := "sleep";
   Args     : POSIX_String_List;  
   
   PID : PI.Process_ID;
   PT  : PP.Process_Template;   
   PS  : PP.Termination_Status;
   
begin
   Append (Args, Filename);   --  argv[0]
   Append (Args, "5");        --  argv[1]

   PP.Open_Template (PT);
   
   PP.Start_Process_Search
     (Child    => PID,
      Filename => Filename,
      Template => PT,
      Arg_List => Args);

   loop
      Put_Line ("Waiting...");
      
      PP.Wait_For_Child_Process
        (Status => PS,
         Child  => PID,
         Block  => False);
      
      exit when PP.Status_Available (PS);
      delay 1.0;
   end loop;
   
   Put_Line ("The child terminated with status code: "
               & PP.Exit_Status_Of (PS)'Image);
   
   PP.Close_Template (PT);   
end Main;

output

$./obj/main
Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
Waiting...
The child terminated with status code:  0
Related