Haskell - getLine called before putStr [duplicate] - haskell

This question already has an answer here:
Wrong IO actions order using putStr and getLine
(1 answer)
Closed 5 years ago.
When I want to put some text before reading an input in Haskell, I tried writing it like this:
putStr "enter value: "
var <- getLine
However the output requires the user input before it displays the text:
[input]
enter value:
When I use putStrLn instead of putStr, it displays as it should:
enter value:
[input]
Why do these two statements function differently? Is it the addition of the newline?

putStr "enter value: " actually writes to an output buffer, which is flushed to the actual standard output only later on, when the buffer becomes full or when a newline is found.
This is roughly the same mechanism found in the C programming language.
So, even if putStr "enter value: " is run before getLine, we don't see the output message, yet, which feels wrong.
The solution is to flush the standard output handle explicitly.
import System.IO
-- ...
putStr "enter value: "
hFlush stdout
var <- getLine

Related

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.

Getting multiple params from user

How do I get multiple params from user in haskell?
module Main where
main :: IO ()
main = do
putStrLn "Please enter param1: "
param1 <- getLine
putStrLn "Please enter param2: "
param2 <- getLine
putStrLn $ "you entered" ++ param1 ++ "param 2:" ++ param2
I am using http://www.compileonline.com/compile_haskell_online.php to feed in params.
I am not sure if the program is wrong or the STDINPUT is not fine.
can someone guide me here.
All I get this is this :
Please enter param1: Please enter param2: demo: : hGetLine:
end of file
The output is not even printed.
STDIN Input: 123 231
Your compileonline.com site does not support multiple lines in stdin. If you remove the second getLine and param2 your program works.
The error you are seeing relates to stdin being closed before the second getLine is completed.
Any site that spells it 'Haskel' is probably not a good one.
It looks like the input is 1 line, while you're expecting 2 lines. Either put the input on two lines or change your code to be
module Main where
main :: IO ()
main = do
line <- getLine
let
params = words line
param1 = params !! 0
param2 = params !! 1
putStrLn $ "you entered" ++ param1 ++ "param 2:" ++ param2
This takes the single line of stdin and splits it by space.

Can I make readProcess drop the quotes?

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.

How to exit main in haskell given a condition

I have a main function that does a lot of IO. At one point, however, I want to check a variable like not (null shouldBeNull) exit the whole program, without continuing, with a linux exitcode 1 and output an error message.
I've tried playing around with error "..." like putting that in an if:
if (not (null shouldBeNull)) error "something bad happened" else putStrLn "ok"
but I get a parse error (possibly incorrect indentation) :(.
Here's an altered snippet.
main :: IO ExitCode
main = do
--Get the file name using program argument
args <- getArgs
file <- readFile (args !! 0)
putStrLn("\n")
-- ... (some other io)
-- [DO A CHECK HERE], exit according to check..
-- ... (even more io)
echotry <- system "echo success"
rmtry <- system "rm -f test.txt"
system "echo done."
As you may notice, I want to do the check where I've put [DO A CHECK HERE] comment above...
Thanks for your help!
The parse error is because you're missing the then keyword in your if expression.
if condition then truePart else falsePart
For exiting, a more appropriate choice than error might be to use one of the functions from System.Exit, for example exitFailure.
So for example,
if not $ null shouldBeNull
then do putStrLn "something bad happened"
exitFailure
else putStrLn "ok"

Resources