System.Process How to keep the shell session instead of creating a new process?

Viewed 71
import System.Process

createProcess (shell "pwd") -- /Users/username/current-directory    
createProcess (shell "cd app") -- LOST
createProcess (shell "pwd") -- /Users/username/current-directory

Obviously, createProcess (shell "cd app") is not persistent in the next process.

But, how can I keep the session persistent?

I know I can pass cwd but

createProcess (shell "mkdir some-dir && cd some-dir")
createProcess (shell "pwd") { cwd = Just "some-dir" } 

But, I have to parse the previous command to get "some-dir."

Is there something better than parsing the command?

2 Answers

First a working code example:

module Main where

import System.Process
import System.IO

ourShell :: CreateProcess
ourShell =  (proc "/bin/bash" []) { std_in = CreatePipe, std_out = CreatePipe }

main = do
  (Just bashIn, Just bashOut, Nothing, bashHandle) <- createProcess ourShell

  hSetBuffering bashIn NoBuffering
  hSetBuffering bashOut NoBuffering

  print "Sending pwd"
  hPutStrLn bashIn "pwd"
  print "reading response"
  hGetLine bashOut >>= print

  print "Sending cd test"
  hPutStrLn bashIn "cd test"
  print "reading response"
--  hGetLine bashOut >>= print you need to know there is no answer ....

  print "Sending pwd"
  hPutStrLn bashIn "pwd"
  print "reading response"
  hGetLine bashOut >>= print

  hPutStrLn bashIn "exit"
  hGetContents bashOut >>= print
  ec <- waitForProcess bashHandle
  print ec

This outputs on my machine in /tmp with an existing /tmp/test:

"Sending pwd"
"reading response"
"/tmp"
"Sending cd test"
"reading response"
"Sending pwd"
"reading response"
"/tmp/test"
""
ExitSuccess

You start a shell and connect a pipe to its input stream and a pipe to its output stream. Now you can send commands to its input stream and read the responses from its output stream via the connected pipes.

But now you need a protocol, so you know what output belongs to which command. So you need to know, for example, how many output lines will be generated for which output. If you for exampled tried to read a response for the cd test command, your program would hang, as there isn't any output.

There are other ways to handle that, but they all involve heuristics of some kind and exceed the scope of the question.

You cannot use an external program to change the current directory of the current program. That just won't work.

It is for this reason that cd is a shell built-in operator, not an external program. (At least, that's how Unix does it. I'm not 100% sure about Windows.)

Try using setCurrentDirectory instead. That should allow you to change your Haskell program's current directory, which will then stay permanent for the rest of the program's run (or until you change it again).

Related