How do I execute a list of commands in Haskell? - haskell

I'm new to Haskell and I have been using system to execute shell commands for me, since I'm only interested in exit status. I noticed that since I can do something like:
ls <- system "ls"
pwd <- system "pwd"
and this correctly executes the two commands. I was thinking about executing an array of these commands eg
lsAndPwd <- return $ system <$> ["ls", "pwd"]
and I'm surprised that this doesn't actually execute anything. This compiles and I checked that lsAndPwd has the correct type of [IO GHC.IO.Exception.ExitCode] but the commands are never executed. What's going on and how can I get this to work?

You should use mapM instead of <$> so you get an IO [ExitCode], not an [IO ExitCode]. This way, it's collected into one IO monad that you can run:
lsAndPwd <- mapM system ["ls", "pwd"]
Alternatively, call sequence on the whole thing:
lsAndPwd <- sequence $ system <$> ["ls", "pwd"]

Related

Why doesn't my Haskell cmd line program get arguments from Vim Bang?

Vim has the possibility to let you replace selected text with the output of an external program. I'd like to take advantage of this with programs that I'd write in Haskell. But it doesn’t get the selected text as args.
-- show-input.hs
module Main where
import System.Environment
main = do
input <- getArgs
putStr ("Input was: " ++ (show input))
When I run it from the command line (NixOS GNU/Linux, BASH), I get the expected behavior:
$ ./show-input test
Input was: ["test"]
When I select some text in Vim and invoke :'<,'>!~/show-input, I get this :
Input was: []
There is something weird here, but I can't tell if it is from the way Vim passes arguments or from the way Haskell gets them. I have tried with both console Vim and graphical gVim (8.0.1451), with the same result.
NB: I can successfully use Vim Bang! with other external programs, such as grep. It works great.
---
Correct version after chepner's answer
So, for anyone interested, just replace getArgs with getContents and you get your input all in a string (instead of a list of strings).
module Main where
import System.Environment
main = do
input <- getContents
putStr ("Input was: " ++ (show input))
The ! command sends the seleted text to the program via standard input, not as a command line argument. The command line equivalent would be somecommand | ./show-input.

How to execute commands from Haskell as root?

Consider the following Haskell function:
eraseFile :: FilePath -> IO ()
eraseFile basename =
do let cmd' = ">"
args' = ("/path/to/file/" ++ basename) :: String
(exitcode', stdout', stderr') <- readProcessWithExitCode cmd' [args'] ""
return ()
When I try to run this in a stack ghci repl, or from the main function, I get a permission denied error from the console. Normally, in a bash console, you could just run this command as sudo, but this doesn't seem to work when invoked from Haskell.
Question: How to execute system commands in Haskell as root?
As already pointed out in the comments, you can just run the entire stack/ghc under root, but I daresay that's a bad idea. Preferrably, I'd just invoke sudo as a process from within your program. The particular command – emptying a file, if I have understood that correctly? – is then easiest done with tee:
do let cmd' = "sudo"
args' = ["tee", "/path/to/file/" ++ basename :: String]
(exitcode', stdout', stderr') <- readProcessWithExitCode cmd' args' ""
As Zeta remarks, truncate --size 0 would probably be a cleaner command.
To get around password entering, you probably also want to make an exception in the sudoers file. It's a hairy matter; of course the really best thing would be if you could avoid needing root permissions altogether.

What's the right way to :reload and run :main as a single command in GHCi?

Is there a way to chain :reload/:r along with :main as a single command in GHCi?
The goal here is to avoid typing both every time I change something in my other terminal, but to just type ↑Enter.
:cmd seems to accept string with multiple lines.
Therefore you can do the following command.
:cmd return $ unlines [":reload",":main"]
also you can add following code to ~/.ghci
:def hoge const $ return $ unlines [":reload",":main"]
now you can execute :hoge in ghci

Deleting items in stdin with haskell

I have a bit of code in my haskell program like so:
evaluate :: String -> IO ()
evaluate = ...
repl = forever $ do
putStr "> " >> hFlush stdout
getLine >>= evaluate
Problem is, when I press the delete key (backspace on windows), instead of deleting a character from the buffer, I get a ^? character instead. What's the canonical way of getting delete to delete a character when reading from stdin? Similarly, I'd like to be able to get the arrow keys to move a cursor around, etc.
Compile the program and then run the compiled executable. This will give the correct behavior for the Delete key. For some reason interpreting the program screws up the use of Delete.
To compile the program, just invoke ghc like this:
$ ghc -O2 myProgram.hs
This will generate a myProgram executable that you can run from the command line:
$ ./myProgram
That will then give the correct behavior for Delete.

Running vi from within haskell program (dealing with ptys)

I'm trying to write a logging shell; e.g. one that captures data about commands that are being run in a structured format. To do this, I'm using readline to read in commands and then executing them in a subshell whilst capturing things such as the time taken, the environment, the exit status and so on.
So far so good. However, initial attempts to run things such as vi or less from within this logging shell failed. Investigation suggested that the thing to do was to establish a pseudo-tty and connect the subshell to that rather than to a normal pipe. This stops vi complaining about not being connected to a terminal, but still fails - I get some nonsense printed to the screen and commands print as characters in the editor - e.g. 'ESC' just displays ^[.
I thought that what I needed to do was put the pty in raw mode. To do this, I tried the following:
pty <- do
parentTerminal <- getControllingTerminalName >>=
\a -> openFd a ReadWrite Nothing defaultFileFlags
sttyp <- getTerminalAttributes parentTerminal
(a, b) <- openPseudoTerminal
let rawModes = [ProcessInput, KeyboardInterrupts, ExtendedFunctions,
EnableEcho, InterruptOnBreak, MapCRtoLF, IgnoreBreak,
IgnoreCR, MapLFtoCR, CheckParity, StripHighBit,
StartStopOutput, MarkParityErrors, ProcessOutput]
sttym = withoutModes rawModes sttyp
withoutModes modes tty = foldl withoutMode tty modes
setTerminalAttributes b sttym Immediately
setTerminalAttributes a sttym Immediately
a' <- fdToHandle a
b' <- fdToHandle b
return (a',b')
E.g. we get the parent terminal's attributes, remove the various flags that I think correspond to setting the tty into raw mode (based on this code and the haddock for System.Posix.Terminal), and then set these on both sides of the pty.
I then start up a process within a shell using createProcess and use waitForProcess to connect to it, giving the slave side of the pty for the stdin and stdout handles on the child process:
eval :: (Handle, Handle) -> String -> IO ()
eval pty command = do
let (ptym, ptys) = pty
(_, _, hErr, ph) <- createProcess $ (shell command) {
delegate_ctlc = True
, std_err = CreatePipe
, std_out = UseHandle ptys
, std_in = UseHandle ptys
}
snipOut <- tee ptym stdout
snipErr <- sequence $ fmap (\h -> tee h stderr) hErr
exitCode <- waitForProcess ph
return ()
where tee :: Handle -> Handle -> IO B.ByteString
tee from to = DCB.sourceHandle from
$= DCB.conduitHandle to -- Sink contents to out Handle
$$ DCB.take 256 -- Pull off the start of the stream
This definitely changes terminal settings (confirmed with stty), but doesn't fix the problem. Am I missing something? Is there some other device I need to set attributes on?
Edit: The full runnable code is available at https://github.com/nc6/tabula - I've simplified a few things for this post.
This is how you create the vi process:
(_, _, hErr, ph) <- createProcess $ (shell command) {
Those return values are stdin/stdout/stderr. You throw away stdin/stdout (and keep stderr). You will need those to communicate with vi. Basically, when you type in ESC, it isn't even getting to the process.
As a larger architectural note- You are rewriting not just the terminal code, but a full REPL/shell script.... This is a larger project than you probably want to get into (go read the bash manual to see all the stuff they needed to implement). You might want to consider wrapping around a user choosable shell script (like bash). Unix is pretty modular this way, that is why xterm, ssh, the command prompt etc all work the same way- they proxy the chosen shell script, rather than each write their own.
#jamshidh pointed out that I wasn't actually connecting my stdin to the master side of the pty, so the issues I was getting were nothing to do with vi or terminal modes, and entirely to do with not passing in any input!

Resources