Prevent Haskell's getArgs from parsing glob expressions - haskell

I'm using getArgs in my Haskell program to parse arguments from the command line. However, I've noticed that I have problems when I call my program like this:
runhaskell Main *.hs .
As you can imagine, Main looks at *.hs files in the . directory.
But there's a problem. The code below doesn't behave as I'd expect.
import System
main :: IO ()
main = do
args <- getArgs
print args
I want to see
args = ["*.hs","."]
but instead I see
args = ["file1.hs","file2.hs","file3.hs","."]
My program needs to expand the glob expression itself because it does it in multiple directories. But how can I have getArgs return the raw list of arguments instead of trying to be magical and do the parsing itself?

This isn't haskell, this is your shell.
Normally file globs (like *.hs) are interpreted by the shell before the command is passed to haskell.
To see this try running
runhaskell Main '*.hs' .
The single quotes will prevent your shell from interpreting the glob, and pass the argument as-is to runhaskell.

getArgs doesn't parse the glob expressison - getArgs never sees the glob expression. The glob expression is parsed and expanded by your shell before your program even starts.
There's nothing you can do inside your program to prevent this - your only option is to escape the glob in the shell (by adding a backslash before the * for example or surrounding the argument with single quotes).

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.

Why does my Haskell program not accept standard input redirection?

I'm trying to read a file in Haskell by supplying the file name as a command line argument.
I have read that you can accomplish this by:
./program < input.txt
I wrote this code:
main = do
[fileName] <- getArgs
file <- readFile fileName
print file
But I get this error: "pattern match failure in do expression". If I omit the < sign it works, is this the only way to accomplish this? I would much rather not omit it. What should I change?
./program < input.txt calls the program with 0 arguments and redirects stdin to the contents of input.txt.
So you get a pattern matching error because getArgs is empty. So if you want your program to always read from stdin, don't use the command line arguments at all and read from stdin instead of a file.
If you want your program to read from stdin only if no file name was given, check the length of the arguments first and then read from the given file name or from stdin depending on that.
If you run ./program arg then arg is passed as an argument. The standard input is left on its default -- usually reading from keyboard from a terminal.
If you run ./program < filename then no arguments are passed to the program. The standard input now is redirected so to read from file filename.
This is just how the OS shell works.
In Haskell, getArgs gets the program arguments. In the second case, they are empty, and [fileName] <- getArgs fails with your runtime error.

Loading Shell Script Files to Access Their Functions In Haskell

I have a shell script file named /path/to/shell_script.sh which contains a function defintion shell_function() { ...}.
I'd like to be able to do something in Haskell like readProcessWithExitCode shell_function [] "" to eventually get a hold of an IO (String).
How to do this?
If you have a shell script like
#!/bin/bash
foo() {
echo "foo"
}
you can use the readCreateProcess function from the process package to source the script and execute the function in one go, like this:
module Main where
import System.Process
main :: IO ()
main = do
-- I had to put the full path of the script for it to work
result <- readCreateProcess ((shell ". /tmp/foo.sh && foo")) ""
print result
This solution assumes that the script only does things like defining functions and setting environment variables, without running undesired "effectful" code each time it is sourced.

multiline contents of a IO handle in haskell display nothing

I have been experimenting with Haskell. I am trying to write a web crawler and I need to use external curl binary (due to some proxy settings, curl needs to have some special arguments which seem to be impossible/hard to set inside the haskell code, so i rather just pass it as a command line option. but that is another story...)
In the code at the bottom, if I change the marked line with curl instead of curl --help the output renders properly and gives:
"curl: try 'curl --help' or 'curl --manual' for more information
"
otherwise the string is empty - as the `curl --help' response is multiline.
I suspect that in haskell the buffer is cleared with every new line. (same goes for other simple shell commands like ls versus ls -l etc.)
How do I fix it?
The code:
import System.Process
import System.IO
main = do
let sp = (proc "curl --help"[]){std_out=CreatePipe} -- *** THIS LINE ***
(_,Just out_h,_,_)<- createProcess sp
out <-hGetContents out_h
print out
proc takes as a first argument the name of the executable, not a shell command. That, is when you use proc "foo bar" you are not referring to a foo executable, but to an executable named exactly foo bar, with the space in its file name.
This is a useful feature in practice, because sometimes you do have spaces in there (e.g. on Windows you might have c:\Program Files\Foo\Foo.exe). Using a shell command you would have to escape spaces in your command string. Worse, a few other characters need to be escaped as well, and it's cumbersome to check what exactly those are. proc sidesteps the issue by not using the shell at all but passing the string as it is to the OS.
For the executable arguments, proc takes a separate argument list. E.g.
proc "c:\\Program Files\\Foo\\Foo.exe" ["hello world!%$"]
Note that the arguments need no escaping as well.
If you want to pass arguments to curl you have to pass that it in the list:
sp = (proc "/usr/bin/curl" ["--help"]) {std_out=CreatePipe}
Then you will get the complete output in the entire string.

How do I proc out with tilde expansion AND $PATH searching in Haskell?

I'm trying to run the elm-reactor project, which is written in Haskell. It fails because it's trying to proc out to the elm command like this:
createProcess (proc "elm" $ args fileName)
My elm executable is sitting in ~/.cabal/bin, which is in my PATH.
The System.Process.proc command searches the $PATH for its command argument, but it doesn't do tilde (~) expansion, so it doesn't find elm.
System.Process.shell has the opposite problem. It does tilde expansion, but it doesn't search the $PATH, apparently.
From the source of the System.Process command, it looks like most everything rests on a foreign ccall to "runInteractiveProcess", which I assume is doing whatever $PATH searching is being done. I don't know where the source for runInteractiveProcess would be, and my C is about 15 years worth of rusty.
I can work around this issue by
a) adding the fully-expanded cabal/bin path to my PATH or
b) symlinking an elm from the working directory to its location in cabal/bin.
However, I'd like to offer a suggested fix to the elm project, to save future adopters the trouble I've gone through. Is there a System.Process call that they should be making here that I haven't tried? Or is there a different method they should be using? I suppose at worst they could getEnv for the PATH and HOME, and implement their own file search using that before calling proc - but that breaks cross-platform compatibility. Any other suggestions?
Try using shell instead of proc, i.e.:
createProcess (shell "elm")
This should invoke elm via a shell, which hopefully will interpret tildes in $PATH as desired.
Update: Here is the experiment I performed to test what shell does...
Compile the following program (I called it run-foofoo):
import System.Process
main = do
(,,_,h) <- createProcess $ shell "foofoo"
ec <- waitForProcess h
print ec
Create a new directory ~/new-bin and place the following perl script there as the file foofoo:
#!/usr/bin/perl
print "Got here and PATH is $ENV{PATH}\n";
Run: chmod a+rx ~/new-bin/foofoo
Test with:
PATH="/bin:/usr/bin:/sbin" ./run-foofoo # should fail
PATH="$HOME/new-bin:/bin:/usr/bin:/sbin" ./run-foofoo # should succeed
PATH="~/new-bin:/bin:/usr/bin:/sbin" ./run-foofoo # ???
On my OSX system, the third test reports:
Got here and PATH is ~/new-bin:/bin:/usr/bin:/sbin
ExitSuccess

Resources