Can I make readProcess drop the quotes? - haskell

I'm trying to write some code which runs grep externally, then analyses the output. Specifically, I want to do
grep <username> *.test
but, sadly,
readProcess "grep" [username, "*.test"]
seems to generate the command with double quotes around the arguments
grep "<username>" "*.test"
and as there is no individual file called asterisk-dot-test, grep barfs. There are files with .test extensions.
Can I persuade readProcess (or something similar) to issue the command I want?

"*" is expanded not by grep, but by shell. You should run something like sh -c 'grep username *.test if you want the expansion.
A better way is to use createProcess with ShellCommand argument.

You're probably best going to createProcess, which is the most general process creation function. Something like...
import System.Process
import System.IO
makeGrep username file = "grep " ++ username ++ " " ++ file
main :: IO ()
main = do
(_, Just hOut, _, hProc) <- createProcess (
(shell (makeGrep "bob" "*.test"))
{ std_out = CreatePipe }
)
exitCode <- waitForProcess hProc
output <- hGetContents hOut
print output

I usually use system from System.Cmd. You're supposed to build a correct Shell command (e.g. do all the escaping) but it has the advantage that it uses the String as it's provided.

Related

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

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?
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).

fetch environment variables in a cgi with haskell/hugs without a package

I am writing a cgi-script in Haskell.
I am restricted to use only hugs/runhugs.
#!/opt/local/bin/runhugs
module Main where
main = do
putStrLn ("content-type: text/plain\n")
putStrLn ("Hello, Server!")
So far so good.
But now I want to get the server's environment variables.
For example the "SCRIPT_NAME" environment variable.
With bash I can do:
#!/bin/bash
echo "content-type: text/plain;"
echo ""
echo $SCRIPT_NAME
With the result: /path/to/script.cgi in the browser-window.
For Haskell I found something alike: script <- getEnv "SCRIPT_NAME",
but
#!/opt/local/bin/runhugs
module Main where
main = do
putStrLn ("content-type: text/plain\n")
scriptname <- getEnv "SCRIPT_NAME"
putStrLn scriptname
doesn't work.
Is it possible to do it in a way sort of like that ?
plain without an import, or
with an import possible in hugs
Try import System.Environment (getEnv).

How to use readProcessWithExitCode?

This command runs fine in my terminal:
grep --include=\\*.txt --recursive --regexp='answer'
This one runs fine in ghci:
import System.Process
r <- readCreateProcessWithExitCode (shell "grep --include=\\*.txt --recursive --regexp='answer'") ""
But this one fails in ghci:
import System.Process
r <- readProcessWithExitCode "grep" ["--include=\\*.txt", "--recursive", "--regexp='answer'"] ""
It returns (ExitFailure 1,"","").
Am I doing something bad?
Update
This one works:
readProcessWithExitCode "grep" ["-r", "-e 'answer'"]
It looks like one cannot set options starting with --.
Are you sure that this command work in your terminal:
grep --include=\\*.txt --recursive --regexp='answer'
On my system I get the warning:
grep: warning: recursive search of stdin
Try adding . as an additional argument:
readProcessWithExitCode "grep" ["--include=\\*.txt", "--recursive", "--regexp='answer'", "."] ""
Update
This simpler way of passing arguments to grep works for me:
readProcessWithExitCode "grep" ["--include", "*.hs", "--recursive", "--regexp", "answer", "."] ""

Binding the entire output of an ongoing system process to a variable in Haskell

The following snippet of code executes a grep command and binds the output to stdout', stderr' and errCode respectively.
main :: IO ()
main = do
let stdin' = ""
(errCode, stdout', stderr') <- readProcessWithExitCode "grep" ["search-term" ,"-nr", "/path/to/be/searched"] stdin'
putStrLn $ "stdout: " ++ stdout'
putStrLn $ "stderr: " ++ stderr'
putStrLn $ "errCode: " ++ show errCode
The problem is that stdout' only captures the first result of the search. I think this is because grep is something akin to a spawned process that feeds search results one file by one file until it is finished.
Question: I need the entire output of the standard output of grep to be binded to stdout'. Is this possible in Haskell, and if so, what is an idiomatic way to do it?
EDIT: It turns out that the issue wasn't as I thought it was. Namely, I had only one file in my directory that wasn't symlinked. I forgot to use -R as an option with grep, and so those symlinks weren't being traversed in the search! The issue has been resolved.

How to send text to GHCi process?

I'm working on Haskell presentation engine Howerpoint. It is running in GHCi. I would like to create a function which would output a statement to current running GHCi session. It must work on Linux and Mac, Windows is not necessary. Function probably will have type
executeStatement :: String -> IO ()
What I tried already:
getProcessID and getParentProcessID and then sending something like
echo 'xxx' > /proc/92856/fd/1
-bash: /proc/92856/fd/1: No such file or directory
I also tried runCommand but it executes command in the Bash and not in GHCi so I got error that the command was not found
xdotool does not run on mac
You could use the ghcid project from hackage to evaluate expressions. They would not be evaluated in the same session as your are currently running, but you can send expressions and read their output in a session nonetheless.
Here is an example:
import Language.Haskell.Ghcid
main :: IO ()
main = do
(g, _) <- startGhci "ghci" (Just ".") True
let executeStatement = exec g
executeStatement "let x = 33"
executeStatement "x + 8" >>= print . head
executeStatement "print x" >>= print . head
stopGhci g
The output is "41" "33" and g represents a ghci session.
If you really need to execute expressions in an already running ghci instance you can have a look at this function - startGhci and instead of creating a new process you would have to tap into the existing process and then set std_in, std_out and std_err.
You could use a terminal multiplexer like tmux to execute ghci in one pane, and from another pane invoke tmux commands that send keystrokes to ghci.
tmux load-buffer lets you load text into a tmux clipboard (using - as the path reads from stdin).
# from within tmux
$ echo foo | tmux load-buffer -
$ tmux show-buffer
foo
tmux paste-buffer lets you paste the contents of a tmux clipboard into a pane:
$ tmux list-panes
0: [127x24] [history 1850/2000, 1343570 bytes] %0
1: [127x24] [history 0/2000, 0 bytes] %2 (active)
$ tmux paste-buffer -t %0
Another option, already mentioned in the comments, is to use the process library to launch a ghci process, and send the commands through piped stardard input.
Here's a small program that uses my process-streaming helper package for process (not really required, you could do the same using process alone). stdin is piped, stdout and stderr inherited:
{-# LANGUAGE OverloadedStrings #-}
import System.Process.Streaming -- from process-streaming
import Pipes (lift,yield) -- from pipes
import Control.Concurrent (threadDelay)
main :: IO ()
main = executeInteractive program (feedProducer (do
let delay = lift (threadDelay (1000000*6))
delay
yield "4 + 3\n"
delay
yield "5 + 3\n"))
where
program = (shell "ghci"){ std_in = CreatePipe }
The output is:
$ ./Main
GHCi, version 7.10.2: http://www.haskell.org/ghc/ :? for help
Prelude> 7
Prelude> 8
Prelude> Leaving GHCi.

Resources