How to output to terminal but not pipe (or vice versa) - haskell

I'd like to display some text to the terminal but not send this text to a piped process. The purpose is to format differently the data that is being displayed (for pretty printing) and that which is being sent to subsequent processes.
For example, I would like, once the executable is called like this:
$ execname | otherproc
to both output something that will be displayed on the terminal and send some string through the pipe to otherproc. Is there something, maybe like a System.IO.Handle such as stdout, that I could use to send data either to be displayed in terminal or to the pipe?

Yes, if you make some assumptions. You can check if it is the console by using the System.Console.Terminal.Size module:
import System.Console.Terminal.Size(size)
isConsole :: IO Bool
isConsole = liftM isJust size
basically what happens is it checks if the terminal has defined a size (so it is Just x). If so we assume it is a terminal, if not we make the assumption that it is a pipe (which has no size). Of course it is up to a console to specify its size properly.

The following seems to be working: send the data that is meant to be piped to the standard output (with putStrLn for example), and the data to be displayed to the file /dev/tty. For example, the following send "a" to both:
import qualified System.IO as SIO
main = do
SIO.withFile "/dev/tty" SIO.WriteMode (\h -> SIO.hPutStrLn h "a")
putStrLn "a"
That way, running the shell command
$ myProg | sed 's/a/A/'
in a terminal displays:
a
A
Only the second A was replaced, i.e. the one send to sed through the pipe with putStrLn, and not the one directly written to /dev/tty.

Related

Understanding Haskell's Laziness

I was reading: Haskell interact function
So I tried
interact (unlines . map (show . length) . lines)
And it worked as I expected. I type something, press enter, then I get the length printed at the prompt.
So then I wanted to try make it simply repeat what I typed, so I tried
interact (unlines . map id . lines)
But now it repeats every character I type in. Why is that? I thought the trick was in the lines followed by unlines - but it's clearly not. lines "a" produces ["a"], so how come in the first function when I start typing my input, it doesn't just immediately give "1" as the output? There's clearly something I misunderstand about "Finding the length of a string is not like this -- the whole string must be known before any output can be produced."
The fact that lines "a" produces ["a"] does not mean that if you are currently entering a, that lines just processes the input to a list ["a"]. You should see the input as a (possibly) infinite list of characters. In case the prompt is waiting for user input, it is thus "blocking" on the next input.
That however does not mean that functions like lines can not partially resolve the result already. lines has been implemented in a lazy manner such that it processes the stream of characters, and each time when it sees a new line character, it starts emitting the next element. This thus means that lines could process an infinite sequence of characters into an infinite list of lines.
If you use length :: Foldable f => f a -> Int however, then this requires the list to be evaluated (not the elements of the list however). So that means length will only emit an answer from the moment lines starts emitting the next item.
You can use seq (and variants) to force the evaluation of a term before a certain action is done. For example seq :: a -> b -> b will evaluate the first parameter to Weak Head Normal Form (WHNF), and then return the second parameter.
Based on seq, other functions have been constructed, like seqList :: [a] -> [a] in the Data.Lists module of the lists package.
We can use this to postpone evaluation until the first line is known, like:
-- will echo full lines
import Data.Lists(seqList)
interact (unlines . map (\x -> seqList x x) . lines)
This is to do with lazy evaluation. I'll try to explain this in as intuitive a way as possible.
When you write interact (unlines . map (show . length) . lines), every time a character is input, we don't actually know what the next output character can be until you press enter. So, you get the behaviour you expected.
However, at every point in interact (unlines . map id . lines) = interact id, every time you enter a character, it's guaranteed that that character is included in the output. So, if you input a character, that character is also output immediately.
This is one of the reasons that the word "lazy" is a bit of a misnomer. It's true that Haskell will only evaluate something when it needs to, but the flipside of that is that when it needs to, it'll do so as soon as possible. Here Haskell needs to evaluate the output since you want to print it, so it evaluates it as much as it can—one character at a time—ironically making it seem eager!
More specifically, interact isn't intended for real time user input—it's intended for file input, in which you pipe a file into an executable with bash. It should be run something like this:
$ runhaskell Interactor.hs < my_big_file.txt > list_of_lengths.txt
If you want line-by-line buffering, you'll probably have to do it manually, unless you want to 'trick' the compiler as Willem does. Here's some very simple code that works as you expect—but note that it has no exit state unlike interact, which will terminate at the EOF.
main = do
ln <- getLine -- Buffers until you press enter
putStrLn ln -- Print the line we just got
main -- Loop forever

Why does Haskell NoBuffering option still seem to buffer?

I loaded up a file in ghci with the following:
h <- openFile "somefile.txt" ReadMode
hSetBuffering h NoBuffering
I then modified and saved somefile.txt in a text editor. When I call hGetChar several times in ghci, I receive the old characters of the file (as if the entire file was buffered when I opened it). I expected to calls of hGetChar to return the modified contents. Why is this not the case?
Edit:
The reason why it isn't showing the modified contents in the case decribed above is indeed because of the text editor. When the cat command is used instead (cat > somefile.txt), then the modified file contents is returned.
However, it does still seem to doing buffering. Say the file contents is as follows:
ABCDEFGHI
123456789
If I run hGetChar I get the 'A' as expected.
Now if I use cat (cat > somefile.txt) to change the contents to the following, and run hGetChar again, I would expect 'Z' but it's returning 'B':
AZZZZZZZZ
BufferMode is only relevant when writing to a handle, not when reading from it.
From [note Buffered Reading] in GHC.IO.Handle.Types:
Note that the buffering mode (haBufferMode) makes no difference when
reading data into a Handle. When reading, we can always just read all
the data there is available without blocking, decode it into the Char
buffer, and then provide it immediately to the caller.
The documentation for input BufferMode seems to be outdated.

Need to get arrow key codes from stdin after reading file using stdin

I am creating a NASM assembly code to read 2d array of numbers present in file from stdin
i am running the executable like this -> ./abc < input.txt .
and after that i will display the read 2d array on terminal then i want to get keys codes of arrow keys (which normal appear in terminal as special characters) i wrote code for it but its not working. ( I did echo off in termios setting for that)
Although it was working when i am taking file name as an argument & reading and not from stdin but using fopen with proper fd.
./abc abc.txt
in this case after displaying the read 2d array i am able to get arrow keys codes in program but not in earlier case.
Please help me in this matter.
By using input redirection you disconnect stdin from your terminal and instead connect it to a pipe that your shell is reading the file into.
You could use cat input.txt - | ./abc, but you would have to pres Enter to flush the line buffer and make cat pipe the current line into your program.
I would suggest not messing with stdin and just taking the input file as an argument, like you already did before.

How can I get input right when it is typed in Haskell?

I have written a Brainfuck interpreter in Haskell, but it only operates on the input once I hit Ctrl-D to signal EOF. How can I make the program act on each character when it is typed?
Here is the source. To use the program, give a file to interpret as an argument or type your program in the first line of stdin.
It sounds like your input is being buffered. You can modify the buffering mode of a file handle with System.IO.hSetBuffering. If you are reading from standard input, for instance, then you could disable buffering with:
import System.IO
hSetBuffering stdin NoBuffering
getLine waits for a newline character to be typed (\n), because what if the user typed a bunch of characters, but never pressed enter? Then it would be an error if some of the "line" had already be processed, if that "line" wasn't a line after all.
You should use getContents instead which will return everything that is typed at the terminal.
Also, you are using the following line:
then hGetContents =<< openFile (head args) ReadMode
This will open a file and never close it. This is fine for your short program, but it might be a better idea for the future to get used to doing this:
then readFile $ head args

How do I use getContents to take input from the command line?

My program allows the user to specify a file which is read as input, however this is optional. If the user does not specify a file, I'd like to read input in from the command line.
I have this so far:
main :: IO()
main = do
(opts, mbArgs) <- parseCmdLine
input <-
case mbArgs of
Nothing -> getContents
Just file -> readFile file
This doesn't seem to be working. When a user doesn't stipulate a file, they are able to enter input, but there seems to be no way of terminating so that the program can then work on that input.
I thought that you had to press Ctrl+D, but that doesn't do anything.
Thanks for any help.
In a typical Unix-like terminal (such as Cygwin, at least in Cygwin's rxvt; not sure about the Windows Command Prompt), a Ctrl+D only sends EOF when you're at the start of a line. If you hit Enter and then Ctrl+D, it should work. If you want to send EOF without a newline, hit Ctrl+D twice in a row.
If it's not that, then there's presumably some other problem with your terminal; the code looks fine.
I'm going to write hammar's comment as an answer.
For me on windows, hitting ctrl+Z crtl+Z enter worked.

Resources