Personal tools

Simple Unix tools

From HaskellWiki

(Difference between revisions)
Jump to: navigation, search
(some follow up links)
Current revision (15:06, 5 November 2011) (edit) (undo)
m (Reverted edits by LindsayMcphee (Talk); changed back to last version by Aaronmcdaid)
 
(29 intermediate revisions not shown.)
Line 1: Line 1:
-
Simple unix tools written in Haskell.
+
Simple Unix commandline tools written in Haskell.
-
This is intended as a beginners tutorial for learning Haskell from a
+
This is intended as a beginner's tutorial for learning Haskell from a
-
"Lets just solve things already!" point of view. The examples should
+
"Let's just solve things already!" point of view. The examples should
-
help give a flavour of the beauty and expressiveness of Haskell
+
help give a flavor of the beauty and expressiveness of Haskell
programming.
programming.
 +
<haskell>
<haskell>
-
--
 
-
-- Some unix-like tools written in elegant Haskell
 
-
--
 
 +
import Control.Monad.Instances
import Data.List
import Data.List
import Data.Char
import Data.Char
-
import System.IO
+
import Data.Maybe
import Text.Printf
import Text.Printf
 +
import System.Environment
-
--
+
-- First, two helpers
-
-- First, a useful helper
+
io f = interact (unlines . f . lines)
-
--
+
-
input f = interact (unlines . f . lines)
+
-
--
+
showln = (++ "\n") . show
-
-- The 'cat' program
+
-
--
+
-
cat = input id
+
-
--
+
-- remove duplicate lines from a file (like uniq)
-
-- Sort a file
+
uniq = nub -- Warning: Unix uniq discards *consecutive* dupes. But 'nub' discards all dupes.
-
--
+
-
sort' = input sort
+
-
--
+
-- repeat the input file infinitely
-
-- Reverse a file (tac)
+
rpt = cycle
-
--
+
-
tac = input reverse
+
-
--
 
-- Return the head -10 line of a file
-- Return the head -10 line of a file
-
--
+
take' = take 10
-
head' = input $ take 10
+
-
--
 
-- Remove the first 10 lines of a file
-- Remove the first 10 lines of a file
-
--
+
drop' = drop 10
-
drop' = input $ drop 10
+
 
 +
-- Return the head -1 line of a file
 +
head' = head
-
--
 
-- Return the tail -1 line of a file
-- Return the tail -1 line of a file
-
--
+
tail' = last
-
tail' = input $ (:[]) . last
+
-
--
+
-- return the last ten lines of a file
-
-- remove duplicate lines from a file (like uniq)
+
tail10 = drop =<< subtract 10 . length
-
--
+
 
-
uniq = input nub
+
-- Reverse lines in a file (tac)
 +
tac = reverse
 +
 
 +
-- Reverse characters on each line (rev)
 +
rev = map reverse
 +
 
 +
-- Reverse words on each line
 +
rev_w = map (unwords . reverse . words)
 +
 
 +
-- Count number of characters in a file (like wc -c)
 +
wc_c = showln . length
 +
 
 +
-- Count number of lines in a file, like wc -l
 +
wc_l = showln . length . lines
 +
 
 +
-- Count number of words in a file (like wc -w)
 +
wc_w = showln . length . words
-
--
 
-- double space a file
-- double space a file
-
--
+
space = intersperse ""
-
space = input $ intersperse ""
+
-
--
+
-- undo double space
-
-- repeat the input file infinitely
+
unspace = filter (not.null)
-
--
+
-
rpt = interact cycle
+
-
--
+
-- remove the first occurrence of the line "str"
-
-- remove the first occurence of the line "str"
+
remove = delete
-
--
+
-
remove = input $ delete "str"
+
-
--
+
-- make a string all upper case
-
-- make a file all upper case
+
upper = map toUpper
-
--
+
-
upper = interact $ map toUpper
+
-
--
 
-- remove leading space from each line
-- remove leading space from each line
-
--
+
clean = map (dropWhile isSpace)
-
clean = input $ map (dropWhile isSpace)
+
-
--
+
-- remove trailing whitespace
-
-- join lines of a file
+
clean' = map (reverse . dropWhile isSpace . reverse)
-
--
+
-
join = input $ (:[]) . concat
+
-
--
+
-- delete leading and trailing whitespace
-
-- Translate the letter 'e' to '*', like tr 'e' '*' (or y// in sed)
+
clean'' = map (f . f)
-
--
+
where f = reverse . dropWhile isSpace
-
y = interact $ map f
+
-
where f 'e' = '*'
+
-
f c = c
+
-
--
+
-
-- Filter the letter 'e' from a file, like tr -d 'e'
+
-
--
+
-
tr = interact $ filter (/= 'e')
+
-
--
+
-- insert blank space at beginning of each line
-
-- Count number of characters in a file (like wc -c)
+
blank = map (s ++)
-
--
+
where s = replicate 8 ' '
-
wc_c = interact $ show . length
+
-
--
+
-- join lines of a file
-
-- Count number of lines in a file, like wc -l
+
join = return . concat
-
--
+
-
wc_l = interact $ show . length . lines
+
-
--
+
-- Translate the letter 'e' to '*', like tr 'e' '*' (or y// in sed)
-
-- Count number of words in a file (like wc -w)
+
tr a b = interact (map f)
-
--
+
where f c = if c == a then b else c
-
wc_w = interact $ show . length . words
+
 
 +
-- Delete characters from a string.
 +
tr_d a = tr a ' '
-
--
 
-- grep lines matching "^foo" from a file
-- grep lines matching "^foo" from a file
-
--
+
grep = filter (isPrefixOf "foo")
-
grep = input $ filter (isPrefixOf "foo")
+
-
--
 
-- grep lines that don't match "^foo" (grep -v)
-- grep lines that don't match "^foo" (grep -v)
-
--
+
grep_v = filter (not . isPrefixOf "foo")
-
grep_v = input $ filter (not . isPrefixOf "foo")
+
-
--
 
-- number each line of a file
-- number each line of a file
-
--
+
num = zipWith (printf "%3d %s") [(1::Int)..]
-
num = input $ zipWith (printf "%3d %s") [1::Int..]
+
-
--
+
-- Compute a simple cksum of a file
-
-- Compute a simple hash of a file
+
cksum = foldl' k 5381
-
--
+
where k h c = h * 33 + ord c
-
cksum = interact $ printf "%u\n" . foldl' k 5381
+
 
-
where k h c = h * 33 + fromIntegral (ord c) :: Int
+
-- And our main wrapper
 +
main = do
 +
who <- getProgName
 +
maybe (return ()) id $ lookup who $
 +
[("blank", io blank )
 +
,("cksum", interact (showln . cksum) )
 +
,("clean", io clean'' )
 +
,("echo" , interact id )
 +
,("drop", interact drop' )
 +
,("grep", io grep )
 +
,("grep -v", io grep_v )
 +
,("head", io (return . head') )
 +
,("join", io join )
 +
,("num", io num )
 +
,("remove", io (remove "str") )
 +
,("revw", io rev_w )
 +
,("reverse", io rev )
 +
,("reverseword", io rev_w )
 +
,("rpt", io rpt )
 +
,("sort", interact sort )
 +
,("space", io space )
 +
,("tac", interact tac )
 +
,("take", io take' )
 +
,("tail", io (return . tail') )
 +
-- ,( "tr" , interact tr)
 +
-- ,( "tr -d", interact (tr_d . unwords))
 +
,("unspace", io unspace )
 +
,("upper", interact upper )
 +
,("uniq", interact uniq )
 +
,("wc_c", interact wc_c )
 +
,("wc_l", interact wc_l )
 +
,("wc_w", interact wc_w )
 +
]
</haskell>
</haskell>
-
'''Where to now?'''
+
==How to run==
 +
These functions can be executed as one liners from a shell. For example,
 +
to use the Haskell version of 'wc':
 +
$ cat file.txt | ghc -e 'wc_l' UnixTools.hs
 +
 +
Or, one could define 'main' to be a chosen tool/function (add a line to
 +
the effect that "main = wc_l") and then compile the tool with
 +
 +
$ ghc --make UnixTools.hs
 +
 +
The given Haskell codes presents yet a third way of doing things: much
 +
like the [http://en.wikipedia.org/wiki/BusyBox BusyBox] suite of Unix
 +
tools, it is possible to compile a single monolithic binary and have it
 +
detect what name it is run by and then act appropriately. This is the
 +
approach the following code takes: you can compile it and then make
 +
symbolic links (like <code>"ln -s UnixTools echo; ln -s UnixTools cat"
 +
</code>) and then run those commands (<code>"./echo foo | ./cat"</code>
 +
would produce output of "foo").
 +
 +
 +
 +
==Where to now?==
 +
* [[Haskell|Haskell.org]]
* The Haskell standard [http://www.cse.unsw.edu.au/~dons/data/List.html list library], with [http://haskell.org/ghc/docs/latest/html/libraries/base/Data-List.html docs]
* The Haskell standard [http://www.cse.unsw.edu.au/~dons/data/List.html list library], with [http://haskell.org/ghc/docs/latest/html/libraries/base/Data-List.html docs]
* Alternative [[Wc|implementations]] of the wc program
* Alternative [[Wc|implementations]] of the wc program
-
* [[Example_code|More]] Haskell code
+
* Learn how to [[Introduction to QuickCheck|test Haskell code]]
-
* Haskell for [[Libraries_and_tools/Operating_system#Shell|shell scripting]]
+
* [[Example code|More]] Haskell code
 +
* Haskell for [[Applications and libraries/Operating system#Shell|shell scripting]]
* Export list functions to the shell with [http://www.cse.unsw.edu.au/~dons/h4sh.html h4sh]
* Export list functions to the shell with [http://www.cse.unsw.edu.au/~dons/h4sh.html h4sh]
-
 
+
* [[Checking for correct invocation of a command line haskell program]]
 +
* [[Poor man's here document]]
 +
* [http://andrew.bromage.org/darcs/diff Diff in 120 Lines of Haskell]
[[Category:Tutorials]]
[[Category:Tutorials]]
 +
[[Category:Code]]

Current revision

Simple Unix commandline tools written in Haskell.

This is intended as a beginner's tutorial for learning Haskell from a "Let's just solve things already!" point of view. The examples should help give a flavor of the beauty and expressiveness of Haskell programming.


import Control.Monad.Instances
import Data.List
import Data.Char
import Data.Maybe
import Text.Printf
import System.Environment
 
-- First, two helpers
io f = interact (unlines . f . lines)
 
showln  = (++ "\n") . show
 
-- remove duplicate lines from a file (like uniq)
uniq    = nub   -- Warning: Unix uniq discards *consecutive* dupes. But 'nub' discards all dupes.
 
-- repeat the input file infinitely
rpt     = cycle
 
-- Return the head -10 line of a file
take'   = take 10
 
-- Remove the first 10 lines of a file
drop'   = drop 10
 
-- Return the head -1 line of a file
head'   = head
 
-- Return the tail -1 line of a file
tail'   = last
 
-- return the last ten lines of a file
tail10  = drop =<< subtract 10 . length
 
-- Reverse lines in a file (tac)
tac     = reverse
 
-- Reverse characters on each line (rev)
rev     = map reverse
 
-- Reverse words on each line
rev_w   = map (unwords . reverse . words)
 
-- Count number of characters in a file (like wc -c)
wc_c    = showln . length
 
-- Count number of lines in a file, like wc -l
wc_l    = showln . length . lines
 
-- Count number of words in a file (like wc -w)
wc_w    = showln . length . words
 
-- double space a file
space   = intersperse ""
 
-- undo double space
unspace = filter (not.null)
 
-- remove the first occurrence of the line "str"
remove  = delete
 
-- make a string all upper case
upper   = map toUpper
 
-- remove leading space from each line
clean   = map (dropWhile isSpace)
 
-- remove trailing whitespace
clean'  = map (reverse . dropWhile isSpace . reverse)
 
-- delete leading and trailing whitespace
clean'' = map (f . f)
    where f = reverse . dropWhile isSpace
 
-- insert blank space at beginning of each line
blank   = map (s ++)
     where s = replicate 8 ' '
 
-- join lines of a file
join = return . concat
 
-- Translate the letter 'e' to '*', like tr 'e' '*' (or y// in sed)
tr a b = interact (map f)
    where f c = if c == a then b else c
 
-- Delete characters from a string.
tr_d a = tr a ' '
 
-- grep lines matching "^foo" from a file
grep = filter (isPrefixOf "foo")
 
-- grep lines that don't match "^foo" (grep -v)
grep_v  = filter (not . isPrefixOf "foo")
 
-- number each line of a file
num  = zipWith (printf "%3d %s") [(1::Int)..]
 
-- Compute a simple cksum of a file
cksum   =  foldl' k 5381
   where k h c = h * 33 + ord c
 
-- And our main wrapper
main = do
    who <- getProgName
    maybe (return ()) id $ lookup who $
        [("blank",       io blank                  )
        ,("cksum",       interact (showln . cksum) )
        ,("clean",       io clean''                )
        ,("echo" ,       interact id               )
        ,("drop",        interact drop'            )
        ,("grep",        io grep                   )
        ,("grep -v",     io grep_v                 )
        ,("head",        io (return . head')       )
        ,("join",        io join                   )
        ,("num",         io num                    )
        ,("remove",      io (remove "str")         )
        ,("revw",        io rev_w                  )
        ,("reverse",     io rev                    )
        ,("reverseword", io rev_w                  )
        ,("rpt",         io rpt                    )
        ,("sort",        interact sort             )
        ,("space",       io space                  )
        ,("tac",         interact tac              )
        ,("take",        io take'                  )
        ,("tail",        io (return . tail')       )
    --  ,( "tr"  ,    interact tr)
    --  ,( "tr -d",   interact (tr_d . unwords))
        ,("unspace",     io unspace                )
        ,("upper",       interact upper            )
        ,("uniq",        interact uniq             )
        ,("wc_c",        interact wc_c             )
        ,("wc_l",        interact wc_l             )
        ,("wc_w",        interact wc_w             )
        ]

1 How to run

These functions can be executed as one liners from a shell. For example, to use the Haskell version of 'wc':

   $ cat file.txt | ghc -e 'wc_l' UnixTools.hs

Or, one could define 'main' to be a chosen tool/function (add a line to the effect that "main = wc_l") and then compile the tool with

   $ ghc --make UnixTools.hs

The given Haskell codes presents yet a third way of doing things: much like the BusyBox suite of Unix tools, it is possible to compile a single monolithic binary and have it detect what name it is run by and then act appropriately. This is the approach the following code takes: you can compile it and then make symbolic links (like "ln -s UnixTools echo; ln -s UnixTools cat" ) and then run those commands ("./echo foo | ./cat" would produce output of "foo").


2 Where to now?