#! /usr/bin/env runhaskell {- This is a 'skeleton file' for writing shell scripts with Haskell Haskell is my go-to language for anything more complicated than a simple sequence of shell commands in a bash script. Doing list processing and a lot of logic in bash is a grind, to me. The idea here is to take a copy of this script and hack it to do what you need quickly. Throw the rest out. The script starts off (after the imports) with a main full of examples of common things along with their bash counterparts. After that are a few functions that simplify things like getting the date as a String, logging a date-stamped String to stdout and manupulating an ExitCode as a true/false value. Dino Morelli http://hub.darcs.net/dino/hscrtmpl version: 1.3 -} import Control.Monad import Data.Time import System.Directory import System.Environment import System.Exit import System.Process import Text.Printf import Text.Regex main :: IO () main = do putStrLn "This is a shell script" -- in bash: -- dates (Date.Time) putStrLn =<< date -- date putStrLn =<< dateFormat "%Y%m%d" -- date +"%Y%m%d" -- These two functions below -- file/dir things (System.Directory) putStrLn =<< getHomeDirectory -- echo $HOME print =<< doesFileExist "foo" -- [ -f foo ] print =<< doesDirectoryExist "bar" -- [ -d bar ] putStrLn =<< getCurrentDirectory -- pwd --setCurrentDirectory "/tmp" -- cd /tmp -- conditional statements (Control.Monad) e <- doesFileExist "hscrtmpl.hs" -- [ -f hscrtmpl.hs ] when e $ putStrLn "This script exists!" -- && echo "This script exists!" -- environment variables (System.Environment) putStrLn =<< getEnv "SHELL" -- echo $SHELL -- arguments (System.Environment) --(args1 : args2 : _) <- getArgs -- arg1=$1 ; arg2=$2 -- string interpolation (Text.Printf) printf "The %s is %d\n" -- S="answer" ; D=42 "answer" (42::Int) -- echo "The $S is $D" -- execution, exit code (System.Process) ec <- system "ls -l" -- ls -l print ec -- echo $? -- execution, capture stdout (System.Process) output <- readProcess "ls" ["-l"] -- output=$(ls -l) "stdin data, if desired" -- or use readProcessWithExitCode -- :: FilePath -> [String] -> String -> IO (ExitCode, String, String) -- program args stdin exitcode stdout stderr -- regular expressions (Text.Regex) print $ matchRegex -- (see bash docs for =~ (mkRegex "a(.)b(.)") "axby" -- and BASH_REMATCH) -- Handy date-stamping logM function below logM "Example of a log message" -- exiting (System.Exit) exitSuccess -- exit 0 --exitFailure -- exit 1 --exitWith $ ExitFailure 3 -- exit 3 {- Get the current date/time as a string in RFC822 format Looks like this in my locale: Mon Feb 13 16:21:38 EST 2012 -} date :: IO String date = dateFormat "%c" {- Get the current date/time as a string in the specified format For format string help, see man 3 strftime -} dateFormat :: String -> IO String dateFormat fmt = formatTime defaultTimeLocale fmt `fmap` (getCurrentTime >>= utcToLocalZonedTime) {- Output a message with datestamp -} logM :: String -> IO () logM msg = do tstamp <- dateFormat "%F %T" printf "%s> %s\n" tstamp msg {- Turn an exit code (say, from system) into a Bool -} ok :: ExitCode -> Bool ok ExitSuccess = True ok _ = False {- Turn a Bool into an exit code -} toExitCode :: Bool -> ExitCode toExitCode True = ExitSuccess toExitCode False = ExitFailure 1 {- Exit with a success or failure code using a Bool -} exitBool :: Bool -> IO () exitBool = exitWith . toExitCode {- This is similar to the for/in/do/done construct in bash with an important difference. This action evaluates to ExitFailure 1 if *any* of the command executions fails. In bash you only get the exit code of the last one. -} forSystem :: [String] -> IO ExitCode forSystem cs = do ecs <- mapM system cs return $ case all (== ExitSuccess) ecs of True -> ExitSuccess False -> ExitFailure 1