Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I call a subprocess in Idris?

Is there some module in the Idris standard library (or a third party library) that allows one to shell out to another program? I'm thinking of modules like Python's subprocess and Haskell's System.Process.

Ideally, I'd like to interact programmatically with the process (writing to its stdin, reading from its stdout, etc.).

like image 635
Langston Avatar asked Oct 01 '16 23:10

Langston


1 Answers

There is the system : String -> IO Int function which takes a shell command, runs it, and returns its exit code. You'll need to import System to use it:

import System

main : IO ()
main = do
  exitCode <- system "echo HelloWorld!"
  putStrLn $ "Exit code: " ++ show exitCode

  exitCode <- system "echo HelloWorld!; false"
  putStrLn $ "Exit code: " ++ show exitCode

On my system the above code results in the following output:

HelloWorld!
Exit code: 0
HelloWorld!
Exit code: 256

I'd expect it to return 1 instead of 256 in the second case. At least it's what echo $? shows.


Another version can be made basing on the Effects library, which is described in this tutorial:

import Effects
import Effect.System
import Effect.StdIO

execAndPrint : (cmd : String) -> Eff () [STDIO, SYSTEM]
execAndPrint cmd = do
  exitCode <- system cmd
  putStrLn $ "Exit code: " ++ show exitCode

script : Eff () [STDIO, SYSTEM]
script = do
  execAndPrint "echo HelloWorld!"
  execAndPrint "sh -c \"echo HelloWorld!; exit 1\""

main : IO ()
main = run script

Here we need to explain to Idris that it needs the Effects package:

idris -p effects <filename.idr>  

I'm not aware of any Idris library that lets you easily work with stdin/stdout of a subprocess. As a workaround we can use the pipes facilities of C, utilizing its popen / pclose functions, which aready have bindings in the Idris standard library. Let me show how we could, for example, read from stdout of a subprocess (please bear in mind that it's a simple snippet with rudimentary error processing):

import System

-- read the contents of a file
readFileH : (fileHandle : File) -> IO String
readFileH h = loop ""
  where
    loop acc = do
      if !(fEOF h) then pure acc
      else do
        Right l <- fGetLine h | Left err => pure acc
        loop (acc ++ l)

execAndReadOutput : (cmd : String) -> IO String
execAndReadOutput cmd = do
  Right fh <- popen cmd Read | Left err => pure ""
  contents <- readFileH fh 
  pclose fh
  pure contents

main : IO ()
main = do
  out <- (execAndReadOutput "echo \"Captured output\"")
  putStrLn "Here is what we got:"
  putStr out

When you run the program, you should see

Here is what we got:
Captured output
like image 119
Anton Trunov Avatar answered Oct 20 '22 14:10

Anton Trunov