This document provides a cheat sheet for porting shell scripts to shh.
a=$(git rev-parse HEAD)
a <- captureTrim <| git "rev-parse" "HEAD"
cat sample.txt | grep example
cat "sample.txt" |> grep "example"
my_command >/dev/null
my_command &> devNull
my_command 2>/dev/null
my_command &!> devNull
my_command > /dev/null 2>/dev/null
my_command &> devNull &!> devNull
my_command
exitCode=$?
exitCode <- catchCode my_command
NB: If a command fails in shh, an exception is thrown. catchCode
suppresses
that exception.
(echo a; echo b) | wc -l
-- Use the monad instance
(echo "a" >> echo "b") |> wc "-l"
-- Or, using do-notation
do {echo "a"; echo "b"} |> wc "-l"
( do
echo "a"
echo "b"
) |> wc "-l"
cleanup() {
echo "Do cleanup"
}
trap cleanup EXIT
rest_of_prog
-- Use regular Haskell exception mechanisms.
let cleanup = echo "Do cleanup"
rest_of_prog `finally` cleanup
NB: Exceptions can nest arbitrarily, and multiple cleanup actions can be registered. This get's tricky, and fragile, in bash.
NB: Use bracket for resource acquisition and release.
-- Guarantee the cleanup of a temporary file
bracket
(readTrim mktemp)
rm
$ \tmpfile -> use_temp_file
myFunction() {
echo a
echo b
}
myFunction = do
echo a
echo b
wc *.txt
-- Use the Glob library
import System.FilePath.Glob
wc =<< glob "*.txt"
NB: In shh, globbing is always an explicit action, and not something that can happen accidentally.
for i in $(seq 1 10)
do
echo "iteration"
echo "$i"
done
forM_ [1..10] $ \i -> do
echo "iteration"
echo i
a=example
echo "$a.com"
--
let a = example
echo (a ++ ".com")
NB: Or look at libraries like PyF or interpolate
let a = example
echo [i|#{a}.com|]
task_a &
p=$?
task_b
wait $p
Concurrency is pretty hard to do in a shell script in a robust way, so you
often see something like the above, where maybe task_a
has started a
service that task_b
needs to use. It is unusual for someone to put in the
effort of dealing with all the failure scenarios, as this gets pretty messy.
import Control.Concurrent.Async
-- Very similar to the original shell script
withAsync task_a $ \p -> do
task_b
wait p
-- Waits for both processes to exit normally, but will terminate the other
-- process if something goes wrong, e.g. if task_a exits prematurely, task_b
-- will be cancelled.
concurrently_ task_a task_b
-- Waits for the first one to finish, and kills the other one as soon as that
-- happens. Say, if task_a is a webservice that task_b is going to use, but
-- we want to shut down that service once task_b is done. In the original
-- shell script, this would have been an action that task_b would have to
-- take explicitly, by sending a signal to the process.
race_ task_a task_b
false || echo "It failed"
false `catch` \Failure{} -> echo "It failed"
true && echo "It worked"
-- Shh throws exceptions on failure, so it is implicit that the previous
-- command succeeded. So, we just sequence them.
true >> echo "It worked"
-- Or simply
true
echo "It worked"
cat <<EOF
A here doc is here in the script, and
can span multiple lines.
EOF
-- Using native Haskell string syntax.
cat <<<"\
\A here doc is here in the script, and\n\
\can span multiple lines.\n\
\"
-- Or, using PyF
cat <<< [strTrim|
A here doc is here in the script, and
can span multiple lines.
|]
-- Or, using an interpolation quasi-quoter library
cat <<< [r|
A here doc is here in the script, and
can span multiple lines.
|]