generating shell scripts from haskell using a shell monad
Shell script is the lingua franca of Unix, it's available everywhere and often the only reasonable choice to Get Stuff Done. But it's also clumsy and it's easy to write unsafe shell scripts, that forget to quote variables, typo names of functions, etc.
Wouldn't it be nice if we could write code in some better language, that generated nicely formed shell scripts and avoided such gotchas? Today, I've built a Haskell monad that can generate shell code.
Here's a fairly involved example. This demonstrates several features,
including the variadic cmd
, the ability to define shell functions,
to bind and use shell variables, to build pipes (with the -:-
operator),
and to factor out generally useful haskell functions like pipeLess
and promptFor
...
santa = script $ do hohoho <- func $ cmd "echo" "Ho, ho, ho!" "Merry xmas!" hohoho promptFor "What's your name?" $ \name -> pipeLess $ do cmd "echo" "Let's see what's in" (val name <> quote "'s") "stocking!" forCmd (cmd "ls" "-1" (quote "/home/" <> val name)) $ \f -> do cmd "echo" "a shiny new" f hohoho cmd "rm" "/table/cookies" "/table/milk" hohoho pipeLess :: Script () -> Script () pipeLess c = c -|- cmd "less" promptFor :: T.Text -> (Var -> Script ()) -> Script () promptFor prompt cont = do cmd "printf" (prompt <> " ") var <- newVar "prompt" readVar var cont var
When run, that haskell program generates this shell code. Which, while machine-generated, has nice indentation, and is generally pretty readable.
#!/bin/sh f1 () { : echo 'Ho, ho, ho!' 'Merry xmas!' } f1 printf 'What'"'"'s your name? ' read '_prompt1' ( echo 'Let'"'"'s see what'"'"'s in' "$_prompt1"''"'"'s' 'stocking!' for _x1 in $(ls '-1' '/home/'"$_prompt1") do : echo 'a shiny new' "$_x1" f1 done ) | ( less ) rm '/table/cookies' '/table/milk' f1
Santa has already uploaded shell-monad to hackage and git.
There's a lot of things that could be added to this library
(if
, while
, redirection, etc), but I can already see using
it in various parts of propellor and git-annex that need to generate
shell code.