Many Unix utilities execute their arguments as a command line in
turn. Some well-known examples are
programs are sometimes called meta-commands, but I think
of them as higher-order programs by analogy to
Surely I was not the first to write the following two higher-order programs, given how useful they are.
tmp :: String -> (FilePath -> IO ()) -> IO ()
The first program is
tmp. It puts
standard input in a temporary file, then invokes the arguments as a
command with the name of the temporary file appended.
This program is useful because some programs require input in
the form of an ordinary file rather than a pipe. For example, say
that we want to retrieve a URL and display the contents using
xdvi. It would be nice to be able to say
curl http://www.diku.dk/~andrzej/papers/RC.dvi | xdvi -
xdvi <(curl http://www.diku.dk/~andrzej/papers/RC.dvi)
xdvi only works on an ordinary file named on
the command line:
$ curl http://www.diku.dk/~andrzej/papers/RC.dvi | xdvi - xdvi.bin: Fatal error: -: No such file, and -.dvi doesn't exist either. $ xdvi <(curl http://www.diku.dk/~andrzej/papers/RC.dvi) xdvi.bin: Fatal error: /dev/fd/63: File has zero size, and /dev/fd/63.dvi doesn't exist either.
Instead, we can use
tmp to adapt
to our purpose.
$ curl --silent http://www.diku.dk/~andrzej/papers/RC.dvi | tmp xdvi
When the command is done,
tmp deletes the temporary
$ echo foo | tmp cat foo $ echo foo | tmp echo | xargs cat cat: /tmp/uyocf2fHQx: No such file or directory
Often I use
tmp when I need to process the same
data twice. For example, it takes two invocations of
psselect to print duplex using manual feed on a
printer without duplex.
$ a2ps -o- Higher-order_shell.mdwn | tmp sh -c 'psselect -e -r $0 | lpr; read </dev/tty; psselect -o $0 | lpr'
The use of
$0 above is documented in the
sh man page:
Read commands from the command_string operand. Set the value of special parameter 0 (see Special Parameters) from the value of the command_name operand and the positional parameters ($1, $2, and so on) in sequence from the remaining argument operands. No commands shall be read from the standard input.
We can also use
sh -c in the same way to nest
multiple invocations of
tmp together. For example, the
following command uses
lynx to convert two HTML files
into plain text, then uses
xxdiff to show how the
results differ. We need to invoke
tmp twice because
xxdiff requires both of its arguments to name ordinary
files rather than pipes.
$ lynx -dump Control-Monad-State-Lazy.html | tmp sh -c 'lynx -dump Control-Monad-State-Strict.html | tmp xxdiff $0'
xxdiff takes the argument
mean standard input, the following command works too.
$ lynx -dump Control-Monad-State-Lazy.html | tmp sh -c 'lynx -dump Control-Monad-State-Strict.html | xxdiff $0 -'
keep :: (forall m. MonadIO m => m ()) -> IO a
The second program is
invokes the arguments as a command and monitors the files the
command accesses. Whenever any of the files change,
keep invokes the command again. Most of the time, I
keep to invoke LaTeX when I don’t want to bother
writing a Makefile:
$ keep pdflatex talk.tex
higher-order program) to find out which files are accessed by the
command, and Linux’s
inotify facility to watch the
files for changes. The incron utility also uses
inotify to run a command when files change, but
keep discovers which files to watch automatically.
Thanks to bennymack on reddit for pointing out that
file can work on standard input. I changed the example
I also fixed the out-of-scope type variable in the faux