OCaml scripting with Nix

There’s a really convenient way to write quick scripts in haskell on Nix systems, even including haskell packages:

#!/usr/bin/env nix-shell
#!nix-shell --pure -i runghc -p "haskellPackages.ghcWithPackages (pkgs: [ ...all of the packages I want... ])"

main = do
  # do stuff
  putStrLn "Hello world from a Haskell script!"

This is nice because it runs on any Nix system, whether or not they have a haskell installation. But when I try the same for OCaml I get an error:

#!/usr/bin/env nix-shell
#!nix-shell --pure -i ocaml -p ocaml

print_string "Hello world!\n";;
❯ ./testing123.ml 
File "./nixos-up.ml", line 2, characters 0-2:
2 | #!nix-shell --pure -i ocaml -p ocaml
    ^^
Error: Syntax error

Is there any way to get the ocaml interpreter to ignore the shebang lines? It already seems like one of them is being ignore but the second one is causing problems.

2 Likes

I don’t have access to a Nix system, but from what I can tell the problem is caused by a combination of these two factors:

  • Unlike many other programming languages, OCaml doesn’t use # for comments. The interpreter will ignore the first shebang line but not the second like you noted.
  • The use of a second shebang line is a workaround for a limitation of the env command as per nix-shell documentation:

The lines starting with #! nix-shell specify nix-shell options (see above). Note that you cannot write #! /usr/bin/env nix-shell -i ... because many operating systems only allow one argument in #! lines.

However, you might find that your env command supports the -S flag (env documentation for GNU coreutils). In that case you could use just one shebang line like this:

#!/usr/bin/env -S nix-shell --pure -i ocaml -p ocaml

Alternatively you could write a wrapper script that filters out the shebang lines with awk or tail and then invokes the OCaml interpreter. Actually, an even simpler way could be a “heredoc”:

#!/usr/bin/env nix-shell
#!nix-shell --pure -i bash -p ocaml

ocaml <<OCAML_EOF
print_string "Hello world!\n";;
OCAML_EOF
1 Like

Excellent answer! One minor suggestion: give the ocaml invocation the -stdin flag

I think 4.10 already has…

Yes, you’re right! Without the -stdin flag it would go into an interactive toplevel, so this is better:

#!/usr/bin/env nix-shell
#!nix-shell --pure -i bash -p ocaml

ocaml -stdin <<OCAML_EOF
print_string "Hello world!\n";;
OCAML_EOF
1 Like

On my machine at least, the following works:

#! /usr/bin/env nix-shell
(*
#! nix-shell --pure -i ocaml -p ocaml
*)

let () = print_endline "Hello world"

Not sure if this behaviour is intended, but I’m happy to exploit it for better interoperability w/ my OCaml editor tooling.

8 Likes

Nice! It would be great if ocaml dropped multiple shebang lines, but this seems like the next best thing…

You might be interested by ocamlscript (available in opam).

Not specific to Nix or OCaml, but you could use BINFMT_MISC as described in this blog post and in the other blog post it talks about. :slight_smile:

1 Like

woahhhhhhh… that’s cool!