Subprocesses (was: [ANNOUNCE] shell-haskell 1.0.0)

Daan Leijen daan@cs.uu.nl
Fri, 9 May 2003 12:45:31 +0200


This is a multi-part message in MIME format.

------=_NextPart_000_009B_01C31628.E0201E10
Content-Type: text/plain;
	charset="iso-8859-1"
Content-Transfer-Encoding: 7bit

Hi all,

I have implemented a generalised library for communicating with child processes some
time ago for windows (but it is easy to implement for unix's too).  I believe that
the interface encompasses all current mechanism around  (system, runProcess, popen, launch).

Basically, it allows one to create pipes in Haskell that can be read and written just like
normal file "Handle"s. Furthermore, you can start a child process that can get its stdin, stdout,
and stderr redirected to one of those pipes. This "runProcess" call is basically the same to
"Posix.runProcess".

I have attached the documentation and description of the functions to this mail in HTML format.
It also contains an example of a "spawn" function that closely resemebles the "launch" function
of shell-haskell.

I think that this interface is a good starting point for a discussion on the final library interface
for sub-processes.

All the best,
  Daan.


----- Original Message -----
From: "Simon Marlow" <simonmar@microsoft.com>
To: <libraries@haskell.org>
Sent: Friday, May 09, 2003 12:03 PM
Subject: Subprocesses (was: [ANNOUNCE] shell-haskell 1.0.0)


> Libraries people,
>
> There are currently two ways to create and communicate with external
> processes:
>
>   - System.Cmd.system  (very basic, doesn't give you access to the
>     process's output but you can use shell primitives to redirect its
>     input/output).
>
>   - Posix.runProcess (can specify Handles for the process's
> input/output,
>     but it doesn't create pipes so you can't actually communicate with
>     the subprocess using these Handles.).
>
>   - POpen.popen (can specify an input String, and gives you lazy output
>     Strings.  No interactive communication is possible.)
>
> Ok, *three* ways.  System.Cmd.system, Posix.runProcess, POpen.popen, and
> shell-haskell.  Er.  *Four* ways.
>
> David Sankel's shell-haskell creates pipes for stdin/stdout/stderr, and
> lets you communicate interactively with the sub-process via Handles.  It
> could do with a way to specify the environment and working directory of
> the sub-process (ala POpen and runProcess), but apart from that it seems
> to be the most flexible of the four.  It could be simplified slightly by
> using one Handle rather than two for stdin/stdout.
>
> We should think about what functionality we want to provide in the core
> libraries.  My preference would be to go for a full version with three
> pipes (like shell-haskell) and perhaps one or two more specialised
> versions covering common cases (I don't know what these should be,
> though).
>
> What do people think?
>
> Cheers,
> Simon
>
> -----Original Message-----
> From: haskell-admin@haskell.org [mailto:haskell-admin@haskell.org] On
> Behalf Of David Sankel
> Sent: 06 May 2003 03:06
> To: haskell@haskell.org
> Subject: [ANNOUNCE] shell-haskell 1.0.0
>
> ===================
> shell-haskell 1.0.0
> ===================
>
>   shell-haskell provides utilities to communicate with
> other process's via Haskell code.  This library can be
> used for something as simple as getting the output of
> another program or as complex as interacting with an
> interpreter.
>
> see
>
>   http://www.electronconsulting.com/shell-haskell
>
> for details.
>
> David J. Sankel
> _______________________________________________
> Haskell mailing list
> Haskell@haskell.org
> http://www.haskell.org/mailman/listinfo/haskell
>
>
> _______________________________________________
> Libraries mailing list
> Libraries@haskell.org
> http://www.haskell.org/mailman/listinfo/libraries
>
>
>

------=_NextPart_000_009B_01C31628.E0201E10
Content-Type: text/html;
	name="RunProcess.html"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="RunProcess.html"

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" =
"http://www.w3.org/TR/html4/loose.dtd">
<!--Rendered using the Haskell Html Library v0.2-->
<HTML
><HEAD
  ><TITLE
    >RunProcess</TITLE
    ><LINK HREF =3D "haddock.css" REL =3D "stylesheet" TYPE =3D =
"text/css"
    ></HEAD
  ><BODY
  ><TABLE CLASS =3D "vanilla" CELLSPACING =3D "0" CELLPADDING =3D "0"
    ><TR
      ><TD CLASS =3D "topbar"
	><TABLE CLASS =3D "vanilla" CELLSPACING =3D "0" CELLPADDING =3D "0"
	  ><TR
	    ><TD
	      ><IMG SRC =3D "haskell_icon.gif" WIDTH =3D "16" HEIGHT =3D "16" =
ALT =3D " "
		></TD
	      ><TD CLASS =3D "title"
	      ></TD
	      ><TD CLASS =3D "topbut"
	      ><A HREF =3D "index.html"
		>Contents</A
		></TD
	      ><TD CLASS =3D "topbut"
	      ><A HREF =3D "doc-index.html"
		>Index</A
		></TD
	      ></TR
	    ></TABLE
	  ></TD
	></TR
      ><TR
      ><TD CLASS =3D "modulebar"
	><TABLE CLASS =3D "vanilla" CELLSPACING =3D "0" CELLPADDING =3D "0"
	  ><TR
	    ><TD
	      ><FONT SIZE =3D "6"
		>Win32RunProcess</FONT
		></TD
	      ><TD ALIGN =3D "right"
	      ><TABLE CLASS =3D "narrow" CELLSPACING =3D "0" CELLPADDING =3D =
"0"
		><TR
		  ><TD CLASS =3D "infohead"
		    >Portability</TD
		    ><TD CLASS =3D "infoval"
		    >  win32</TD
		    ></TR
		  ><TR
		  ><TD CLASS =3D "infohead"
		    >Stability</TD
		    ><TD CLASS =3D "infoval"
		    >  provisional</TD
		    ></TR
		  ><TR
		  ><TD CLASS =3D "infohead"
		    >Maintainer</TD
		    ><TD CLASS =3D "infoval"
		    >  daan@cs.uu.nl</TD
		    ></TR
		  ></TABLE
		></TD
	      ></TR
	    ></TABLE
	  ></TD
	></TR
      ><TR
      ><TD CLASS =3D "s15"
	></TD
	></TR
      ><TR
      ><TD
	><TR
	  ><TD CLASS =3D "section4"
	    ><B
	      >Contents</B
	      ></TD
	    ></TR
	  ><TR
	  ><TD
	    ><DL
	      ><DT
		><A HREF =3D "#1"
		  > Process control</A
		  ></DT
		><DT
		><A HREF =3D "#2"
		  > Pipes</A
		  ></DT
		><DT
		><A HREF =3D "#3"
		  > Data types (<TT
		    >IOModeEx</TT
		    >) </A
		  ></DT
		></DL
	      ></TD
	    ></TR
	  ></TD
	></TR
      ><TR
      ><TD CLASS =3D "s15"
	></TD
	></TR
      ><TR
      ><TD CLASS =3D "section1"
	>Description</TD
	></TR
      ><TR
      ><TD CLASS =3D "doc"
	>    High level primitives for process spawning on windows.
</TD
	></TR
      ><TR
      ><TD CLASS =3D "s15"
	></TD
	></TR
      ><TR
      ><TD CLASS =3D "section1"
	>Synopsis</TD
	></TR
      ><TR
      ><TD CLASS =3D "s15"
	></TD
	></TR
      ><TR
      ><TD CLASS =3D "body"
	><TABLE CLASS =3D "vanilla" CELLSPACING =3D "0" CELLPADDING =3D "0"
	  ><TR
	    ><TD CLASS =3D "decl"
	      ><A HREF =3D "#runProcess"
		>runProcess</A
		> :: FilePath -&gt; [String] -&gt; Maybe [(String, String)] -&gt; =
Maybe FilePath -&gt; Maybe Handle -&gt; Maybe Handle -&gt; Maybe Handle =
-&gt; IO ()</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "s8"
	      ></TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      ><A HREF =3D "#runProcessEx"
		>runProcessEx</A
		> :: Bool -&gt; FilePath -&gt; [String] -&gt; Maybe [(String, String)] =
-&gt; Maybe FilePath -&gt; Maybe Handle -&gt; Maybe Handle -&gt; Maybe =
Handle -&gt; IO Int</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "s8"
	      ></TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      ><A HREF =3D "#createPipe"
		>createPipe</A
		> :: IO (Handle, Handle)</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "s8"
	      ></TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      ><A HREF =3D "#createPipeEx"
		>createPipeEx</A
		> :: IOModeEx -&gt; IO (Handle, Handle)</TD
	      ></TR
	    ></TABLE
	  ></TD
	></TR
      ><TR
      ><TD CLASS =3D "s15"
	></TD
	></TR
      ><TR
      ><TD CLASS =3D "s15"
	></TD
	></TR
      ><TR
      ><TD CLASS =3D "section1"
	><A NAME =3D "1"
	  > Process control</A
	  ></TD
	></TR
      ><TR
      ><TD CLASS =3D "s15"
	></TD
	></TR
      ><TR
      ><TD CLASS =3D "decl"
	><A NAME =3D "runProcess"
	  ></A
	  ><B
	  >runProcess</B
	  ></TD
	></TR
      ><TR
      ><TD CLASS =3D "body"
	><TABLE CLASS =3D "vanilla" CELLSPACING =3D "0" CELLPADDING =3D "0"
	  ><TR
	    ><TD CLASS =3D "decl"
	      >:: FilePath</TD
	      ><TD CLASS =3D "rdoc"
	      > Command</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; [String]</TD
	      ><TD CLASS =3D "rdoc"
	      > Arguments</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; Maybe [(String, String)]</TD
	      ><TD CLASS =3D "rdoc"
	      > Environment (Nothing -&gt; Inherited)</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; Maybe FilePath</TD
	      ><TD CLASS =3D "rdoc"
	      > Working directory (Nothing -&gt; inherited)</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; Maybe Handle</TD
	      ><TD CLASS =3D "rdoc"
	      > stdin  (Nothing -&gt; inherited)</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; Maybe Handle</TD
	      ><TD CLASS =3D "rdoc"
	      > stdout (Nothing -&gt; inherited)</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; Maybe Handle</TD
	      ><TD CLASS =3D "rdoc"
	      > stderr (Nothing -&gt; inherited)</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; IO ()</TD
	      ><TD CLASS =3D "rdoc"
	      ></TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "ndoc" COLSPAN =3D "2"
	      ><P
		>(<TT
		  >runProcess cmd args env workdir in out err</TT
		  >) runs a command <TT
		  >cmd</TT
		  >. It searches
the application along the current directory, the windows system =
directories and=20
finally the current PATH. The command is run with the arguments <TT
		  >args</TT
		  >. The
child process runs concurrently with the parent process and a call to =
<TT
		  >runProcess</TT
		  >
returns immediately.</P
		><P
		>If the environment <TT
		  >env</TT
		  > is (<TT
		  >Just pairs</TT
		  >),=20
the command is executed with the environment specified by pairs of =
variables and values; otherwise,=20
the command is executed with the current environment. </P
		><P
		>If <TT
		  >workdir</TT
		  > is (<TT
		  >Just dir</TT
		  >), the command is executed=20
with working directory <TT
		  >dir</TT
		  > and otherwise, the command is executed in the current working =
directory. </P
		><P
		>If {<TT
		  >in</TT
		  >,<TT
		  >out</TT
		  >,<TT
		  >err</TT
		  >} is (<TT
		  >Just handle</TT
		  >), the command is executed with=20
<TT
		  >std</TT
		  >{<TT
		  >in</TT
		  >,<TT
		  >out</TT
		  >,<TT
		  >err</TT
		  >} attached to the specified handle;=20
otherwise, <TT
		  >std</TT
		  >{<TT
		  >in</TT
		  >,<TT
		  >out</TT
		  >,<TT
		  >err</TT
		  >} is left unchanged.</P
		><PRE
		> runProcess =3D runProcessEx False
</PRE
		></TD
	      ></TR
	    ></TABLE
	  ></TD
	></TR
      ><TR
      ><TD CLASS =3D "s15"
	></TD
	></TR
      ><TR
      ><TD CLASS =3D "decl"
	><A NAME =3D "runProcessEx"
	  ></A
	  ><B
	  >runProcessEx</B
	  ></TD
	></TR
      ><TR
      ><TD CLASS =3D "body"
	><TABLE CLASS =3D "vanilla" CELLSPACING =3D "0" CELLPADDING =3D "0"
	  ><TR
	    ><TD CLASS =3D "decl"
	      >:: Bool</TD
	      ><TD CLASS =3D "rdoc"
	      > Wait for termination? </TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; FilePath</TD
	      ><TD CLASS =3D "rdoc"
	      > Command</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; [String]</TD
	      ><TD CLASS =3D "rdoc"
	      > Arguments</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; Maybe [(String, String)]</TD
	      ><TD CLASS =3D "rdoc"
	      > Environment (Nothing -&gt; Inherited)</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; Maybe FilePath</TD
	      ><TD CLASS =3D "rdoc"
	      > Working directory (Nothing -&gt; inherited)</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; Maybe Handle</TD
	      ><TD CLASS =3D "rdoc"
	      > stdin  (Nothing -&gt; inherited)</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; Maybe Handle</TD
	      ><TD CLASS =3D "rdoc"
	      > stdout (Nothing -&gt; inherited)</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; Maybe Handle</TD
	      ><TD CLASS =3D "rdoc"
	      > stderr (Nothing -&gt; inherited)</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "decl"
	      >-&gt; IO Int</TD
	      ><TD CLASS =3D "rdoc"
	      > The exit code (or 0 if wait is False)</TD
	      ></TR
	    ><TR
	    ><TD CLASS =3D "ndoc" COLSPAN =3D "2"
	      ><P
		>(<TT
		  >runProcessEx wait cmd args env workdir in out err</TT
		  >) runs a command <TT
		  >cmd</TT
		  >.
It searches the application along the=20
current directory, the windows system directories and finally the =
current PATH.
The command is run with the arguments <TT
		  >args</TT
		  >. If <TT
		  >wait</TT
		  > is True, the current
process is suspended until the child terminates, otherwise they are run =
concurrently.</P
		><P
		>If the environment <TT
		  >env</TT
		  > is (<TT
		  >Just pairs</TT
		  >),=20
the command is executed with the environment specified by pairs of =
variables and values; otherwise,=20
the command is executed with the current environment. </P
		><P
		>If <TT
		  >workdir</TT
		  > is (<TT
		  >Just dir</TT
		  >), the command is executed=20
with working directory <TT
		  >dir</TT
		  > and otherwise, the command is executed in the current working =
directory. </P
		><P
		>If {<TT
		  >in</TT
		  >,<TT
		  >out</TT
		  >,<TT
		  >err</TT
		  >} is (<TT
		  >Just handle</TT
		  >), the command is executed with=20
<TT
		  >std</TT
		  >{<TT
		  >in</TT
		  >,<TT
		  >out</TT
		  >,<TT
		  >err</TT
		  >} attached to the specified handle;=20
otherwise, <TT
		  >std</TT
		  >{<TT
		  >in</TT
		  >,<TT
		  >out</TT
		  >,<TT
		  >err</TT
		  >} is left unchanged.</P
		><P
		>If <TT
		  >wait</TT
		  > is True, the call to <TT
		  ><A HREF =3D "Win32RunProcess.html#runProcessEx"
		    >runProcessEx</A
		    ></TT
		  > returns after the child process
has terminated with the exit code of the process. If <TT
		  >wait</TT
		  > is false, the
call to <TT
		  ><A HREF =3D "Win32RunProcess.html#runProcessEx"
		    >runProcessEx</A
		    ></TT
		  > returns immediately with an exit code of 0.</P
		><P
		>Here is a short example of running a command <TT
		  >cmd</TT
		  > that gets its input
from a file <TT
		  >foo</TT
		  >. The process is run synchronously.</P
		><PRE
		>     do{ input    &lt;- openFile &quot;foo&quot; ReadMode
       ; exitcode &lt;- runProcessEx True &quot;cmd&quot; [] Nothing =
Nothing (Just input) Nothing Nothing
       ; hClose input
       ; ...
</PRE
		></TD
	      ></TR
	    ></TABLE
	  ></TD
	></TR
      ><TR
      ><TD CLASS =3D "s15"
	></TD
	></TR
      ><TR
      ><TD CLASS =3D "section1"
	><A NAME =3D "2"
	  > Pipes</A
	  ></TD
	></TR
      ><TR
      ><TD CLASS =3D "s15"
	></TD
	></TR
      ><TR
      ><TD CLASS =3D "decl"
	><A NAME =3D "createPipe"
	  ></A
	  ><B
	  >createPipe</B
	  > :: IO (Handle, Handle)</TD
	></TR
      ><TR
      ><TD CLASS =3D "doc"
	><P
	  >=20
(<TT
	    >createPipe</TT
	    >) creates an anonymous <EM
	    >pipe</EM
	    > and returns a pair of=20
handles, the first for reading and the second for writing. Both
pipe ends can be inherited by a child process.</P
	  ><PRE
	  > createPipe  =3D createPipeEx (BinaryMode AppendMode)  =20
</PRE
	  ></TD
	></TR
      ><TR
      ><TD CLASS =3D "s15"
	></TD
	></TR
      ><TR
      ><TD CLASS =3D "decl"
	><A NAME =3D "createPipeEx"
	  ></A
	  ><B
	  >createPipeEx</B
	  > :: IOModeEx -&gt; IO (Handle, Handle)</TD
	></TR
      ><TR
      ><TD CLASS =3D "doc"
	><P
	  >=20
(<TT
	    >createPipeEx modeEx</TT
	    >) creates an anonymous <EM
	    >pipe</EM
	    > and returns a pair of=20
handles, the first for reading and the second for writing.=20
The pipe mode <TT
	    >modeEx</TT
	    > can be:</P
	  ><UL
	  ><LI
	    ><P
	      > <TT
		>TextMode mode</TT
		> -- the pipe is opened in text mode.</P
	      ></LI
	    ><LI
	    ><P
	      > <TT
		>BinaryMode mode</TT
		> -- the pipe is opened in binary mode.</P
	      ></LI
	    ></UL
	  ><P
	  >The <TT
	    >mode</TT
	    > determines if child processes can inherit the pipe handles:</P
	  ><UL
	  ><LI
	    ><P
	      > ReadMode -- The <EM
		>read</EM
		> handle of the pipe is private to this process. </P
	      ></LI
	    ><LI
	    ><P
	      > WriteMode -- The <EM
		>write</EM
		> handle of the pipe is private to this process. </P
	      ></LI
	    ><LI
	    ><P
	      > ReadWriteMode -- Both handles are private to this process.</P
	      ></LI
	    ><LI
	    ><P
	      > AppendMode -- Both handles are available (inheritable) to child =
processes.
      This mode can be used to <EM
		>append</EM
		> (|) two seperate child processes.</P
	      ></LI
	    ></UL
	  ><P
	  >If a broken pipe is read, an end-of-file (<TT
	    ><A HREF =3D "GHC.IOBase.html#EOF"
	      >EOF</A
	      ></TT
	    >)=20
exception is raised. If a broken pipe is written to, an invalid argument =
exception
is raised (<TT
	    ><A HREF =3D "GHC.IOBase.html#InvalidArgument"
	      >InvalidArgument</A
	      ></TT
	    >).</P
	  ><P
	  >Here is a short example that redirects stdin, stdout and stderr of a =
spawned=20
child process to private handles in the parent process.</P
	  ><PRE
	  > spawn :: String -&gt; IO (Handle,Handle,Handle)
 spawn command=20
   =3D do{ (stdinRead,stdinWrite)   &lt;- createPipeEx (TextMode =
WriteMode)
       ; (stdoutRead,stdoutWrite) &lt;- createPipeEx (TextMode ReadMode)
       ; (stderrRead,stderrWrite) &lt;- createPipeEx (TextMode ReadMode)
       ; runProcess command [] Nothing Nothing (Just stdinRead) (Just =
stdoutWrite) (Just stderrWrite)
       ; hClose stdinRead
       ; hClose stdoutWrite
       ; hClose stderrWrite
       ; return (stdinWrite,stdoutRead,stderrRead)
       }
</PRE
	  ></TD
	></TR
      ><TR
      ><TD CLASS =3D "s15"
	></TD
	></TR
      ><TR
      ><TD CLASS =3D "section1"
	><A NAME =3D "3"
	  > Data types (<TT
	    >IOModeEx</TT
	    >) </A
	  ></TD
	></TR
      ><TR
      ><TD CLASS =3D "s15"
	></TD
	></TR
      ><TR
      ><TD CLASS =3D "botbar"
	>Produced by <A HREF =3D "http://www.haskell.org/haddock"
	  >Haddock</A
	  > version 0.4</TD
	></TR
      ></TABLE
    ></BODY
  ></HTML
>

------=_NextPart_000_009B_01C31628.E0201E10--