From: Zheng Li <zheng_li@users.sourceforge.net>
To: caml-list@inria.fr
Subject: [ANN] camlish: a simple module for shell scripting in OCaml
Date: Mon, 27 Oct 2008 04:30:32 +0100 [thread overview]
Message-ID: <490535D8.8020500@users.sourceforge.net> (raw)
Hi,
I'd like to announce the availability of a small module for shell
scripting: camlish.
The project URL is: http://zheng.li/projects/ocaml/camlish, where you
can get all the code and docs. A source repo is going to provide later.
It's rather experimental, bug reports are always welcome.
Have fun!
FYI, the rest parts are directly copy&pasted from the README file:
-----------------------------------------------------------------------
===== Motivation =====
I was thinking about using OCaml for shell scripting. I once looked at
Cash, but quickly went away: I didn't really want to read 100+ pages of
manual just for my little scripting purposes. Recently there came
Caml-Shcaml, it also successfully stopped me from exploring any further
with its ~700 APIs (Sure I believe learning a small portion of them can
be enough to get me a quick start, but still...)
I believe they are both excellent and full-fledged shell
implementations, but they are really overkilled for me. Vanilla OCaml is
already a full-functional language which I can do anything with, include
scripting. What I want is just a little more convenience on this aspect,
better with minimal learning cost, not to learn another "language".
Otherwise I could just resort to plain ocaml because I'm lazy. Moreover,
I don't want to patch my compiler source or use Camlp4 magic to have a
more shell-like syntax: it's probably nice-looking but alien.
==== Basics ====
So I wrote camlish, a plain OCaml module to ease shell scripting in
OCaml, especially for those thinking the same.
Camlish follows simple principles:
* It deals with simple coordination and interaction with outside
commands and shells. It provides a very thin layer of glues to
ease that. That's all it does.
* OCaml itself has far better language features than any shell
script languages out there, so when camlish is used for scripting,
it encourages you to stay on the OCaml side as much as possible,
call external and push/pull the inputs/outputs back/forth as OCaml
values, and sometime resort to /bin/sh if necessary.
==== Features ====
Here are some features of camlish:
* Code base is small. The core is just 500+ loc, 30+ definitions
(except operator sugar).
* Interface is simple. It deals and only deals with the follows:
- command construction: wrap a string or a OCaml function to a
typed command
<code ocaml>
let c1 = cmd "seq 1 20" (* (unit, unit) cmd *)
let c2 = func (List.map float_of_int)
(* (int list, float list) cmd *)
</code>
- command redirection: redirect input/output/error to any files,
file descriptors and OCaml values, via plain record syntax. We
call all these direction adapters as "ports". Besides a number
of primitive ports, we can define and compose our own "ports"
for any data type.
<code ocaml>
let c3 = { c1 with pout = pout_lines }
(* (unit, string list) cmd *)
let c4 = { c2 with pin = int_of_string =| c2.pin }
(* (string list, float list) cmd *)
</code>
- command composition: pipeline(>>>), sequence (&&&) and
subshell(~$) and more.
<code ocaml>
let c5 = c3 >>> c4 (* (unit, float list) cmd *)
let c6 = {(cmd "pwd" &&& cmd "date") with pout = pout_file "log"}
(* (unit, unit) cmd *)
</code>
- command execution: input/return values according to command's
redirection. There are different execution schemes depending on
various factors (e.g. having input value or not, running in
background or not etc.)
<code ocaml>
!! c5 (* get float list [1.; ...; 20.] *)
</code>
The example is made up for illustration. In practice you'd
instead write
<code ocaml>
!! { (cmd "seq 1 20") with pout = pout_lines |= float_of_string }
</code>
and commands without redirection look normal:
<code ocaml>
!! (cmd "ls -l" >>> cmd "wc -l")
!$ "ls -l | wc -l" (* resort to /bin/sh pipe *)
</code>
Here is an example collaborating commands and functions to count
the numbers x between 1 .. n where x mod 3 or 5 is 0.
<code ocaml>
let count n=
{ (cmd "seq 1 %d" n) with pout = pout_lines }
>>= int_of_string
>>- List.filter (fun x -> x mod 3 = 0 || x mod 5 = 0)
>>> {( cmd "wc -l") with
pin = string_of_int =| pin_lines;
pout = pout_string |- int_of_string }
(* int -> (unit, int) cmd *)
!! (count 1000) (* return OCaml int: 466 *)
</code>
Please read the manual for more details.
* The interaction between external commands and OCaml values are
implemented asynchronously inside (but with a synchronous
interface). So it won't get stuck unnecessarily when dealing with
large chunk of input/output.
Moreover, a pair of stream ports are provided, so that you can
read/write "not fully available" or infinite string/data on
the borders of OCaml and commands. E.g.
<code ocaml>
let s = !!. { (cmd "yes") with pout = pout_stream "\n" }
(* the endless output of "yes" is
redirected to a stream value. *)
let _ = Stream.iter print_endline s (* print infinite "y" *)
</code>
* It's tempting to use Camlp4 or to modify the compiler in order to
achieving terse shell-like syntax, but we choose not. Camlish is a
plain OCaml module.
* It's also tempting to implement simple versions of common unix
tools such as "ls" and "grep" as OCaml functions to achieve better
composability. We opt not for the core module and not for now.
* Camlish supports RC file written in OCaml, i.e. when you load
camlish module from toplevel or run customized toplevel with
Camlish embedded, it will try to read a file named ".camlishrc"
from the current working directory firstly and the home directory
of current user secondly. The RC file can be any plain ocaml file,
e.g.
<code ocaml>
open Camlish
open List
open Prelude
module A = Array
module S = String
module U = Unix
</code>
* When used interactively, camlish can additionally execute shell
commands directly. So we can now use the toplevel as a shell,
feeding it ocaml phrases and shell commands at the same time.
Here is a trace of record:
<code ocaml>
$ rlwrap camlish (* Or rlwrap ocaml unix.cma camlish.cma *)
Objective Caml version 3.10.2
[1] ls -l | wc -l;;
53
- : unit = ()
[2] date;;
Sat Oct 25 12:06:51 CEST 2008
- : unit = ()
[3] let f x = x;;
val f : 'a -> 'a = <fun>
[4] f 100;;
- : int = 100
[5] for i in `seq 1 3`; do echo "Now: "$i; done;;;
Now: 1
Now: 2
Now: 3
- : unit = ()
[6] emacs -nw;;
</code>
In case there is some ambiguity, e.g. a OCaml value shadows a
command name, a ";;;" ending can be used to enforce it is a
command instead of OCaml toplevel phrase.
Please refer to the manual for more details.
next reply other threads:[~2008-10-27 3:30 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2008-10-27 3:30 Zheng Li [this message]
2008-10-27 8:51 ` Sylvain Le Gall
2008-10-27 10:09 ` Zheng Li
2008-10-27 13:11 ` [Caml-list] " Mikkel Fahnøe Jørgensen
2008-10-27 13:15 ` Andre Nathan
2008-10-27 13:18 ` Sylvain Le Gall
2008-10-27 13:21 ` [Caml-list] " Andre Nathan
2008-10-27 13:33 ` Sylvain Le Gall
2008-10-27 13:43 ` [Caml-list] " Mikkel Fahnøe Jørgensen
2008-10-27 14:10 ` Zheng Li
2008-10-27 14:02 ` [Caml-list] " Mikkel Fahnøe Jørgensen
2008-10-27 14:35 ` Zheng Li
[not found] ` <caee5ad80810270745h352a35bye70f9cc045ad441a@mail.gmail.com>
2008-10-27 15:45 ` Zheng Li
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=490535D8.8020500@users.sourceforge.net \
--to=zheng_li@users.sourceforge.net \
--cc=caml-list@inria.fr \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox