Compile your modules first, e.g.:
ocamlc -c mod1.ml
ocamlc -c mod2.ml
Then, when you link, list unix.cma first:
ocamlc -o prog unix.cma mod1.cmo mod2.cmo
If you use the native code compiler, compile the modules first:
ocamlopt -c mod1.ml
ocamlopt -c mod2.ml
Then, when you link, list unix.cmxa first:
ocamlopt -o prog unix.cmxa mod1.cmx mod2.cmx
If you use ocamlbuild, add this to your _tags file:
<prog.{native,byte}> : use_unix
In a REPL:
#load "unix.cma";;
Make a REPL with unix.cma preloaded:
ocamlmktop -o ocamlunix unix.cma
Start it:
./ocamlunix
Command line arguments are stored in Sys.argv.
In a file main.ml, get the number of arguments:
let print_num_args () =
let len = Array.length Sys.argv in
print_endline (string_of_int len)
let () = print_num_args ()
Try it:
ocaml main.ml
ocaml main.ml a b c
Or, print all the arguments:
let print_args () =
let len = Array.length Sys.argv in
for i = 0 to len - 1 do
print_endline Sys.argv.(i);
done
let () = print_args ()
Try it:
ocaml main.ml
ocaml main.ml a b c
Sys.argv.(0) is the program itself. Print it,
and then the arguments passed to it:
let print_invocation () =
let len = Array.length Sys.argv in
let exe = Sys.argv.(0) in
Printf.printf "Program: '%s'.\n" exe;
if len > 1 then
begin
print_endline "Invoked with arguments:";
for i = 1 to len - 1 do
Printf.printf "- '%s'\n" Sys.argv.(i);
done
end
let () = print_invocation ()
Use the exit int function to exit with an exit code.
For instance, to exit with code 2:
let exit_badly () =
exit 2
let () = exit_badly ()
Try it:
ocaml main.ml
echo $?
The exit function always flushes all open buffers.
If you want a function to be called when exit calls,
register it with the at_exit.
let report_shutdown () =
print_endline "Shutting down."
let exit_badly () =
exit 2
let () =
at_exit report_shutdown;
exit_badly ()
You can register multiple functions. They are executed last-registered first.
let report_status () =
print_endline "Things are not good, Jim."
let report_shutdown () =
print_endline "Shutting down."
let exit_badly () =
exit 2
let () =
at_exit report_shutdown;
at_exit report_status;
exit_badly ()
Use handle_unix_error to run a program, and if unix errors
are caught, display the error and exit. For example:
let list_root_files () =
raise (Unix.Unix_error (Unix.EACCES, "ls -la", "/root"))
let () =
Unix.handle_unix_error list_root_files ()
The handle_unix_error function executes list_root_files.
If a unix exception is caught, it will display the error
and exit the program nicely. In the function list_root_files,
it does indeed raise a "permission denied" error.
Try it to see the error it displays:
ocaml -c main.ml
ocaml -o main.byte unix.cma main.cmo
./main.byte
If you like, you can remove the Unix prefixes, and instead
open the Unix module at the top of the file:
open Unix
let list_root_files () =
raise (Unix_error (EACCES, "ls -la", "/root"))
let () =
handle_unix_error list_root_files ()
Here are a variety of programs that start processes
and read their output, using the Unix.open_process* functions.
This reads stdout:
let print_stat s =
match s with
| Unix.WEXITED r -> Printf.printf "Exited with %d\n" r
| Unix.WSIGNALED r -> Printf.printf "Signaled with %d\n" r
| Unix.WSTOPPED r -> Printf.printf "Stopped with %d\n" r
let syscall cmd =
let ic = Unix.open_process_in cmd in
let buf = Buffer.create 32 in
ignore (try
while true do
Buffer.add_channel buf ic 1
done
with End_of_file -> ());
let s = Unix.close_process_in ic in
print_stat s;
Buffer.contents buf
let () =
let cmd = Sys.argv.(1) in
let out = syscall cmd in
print_endline out
This does the same, but it returns both the exit code and stdout:
let stat_code s =
match s with
| Unix.WEXITED r -> r
| Unix.WSIGNALED r -> r
| Unix.WSTOPPED r -> r
let syscall cmd =
let ic = Unix.open_process_in cmd in
let buf = Buffer.create 32 in
ignore (try
while true do
Buffer.add_channel buf ic 1
done
with End_of_file -> ());
let s = Unix.close_process_in ic in
let exit_code = stat_code s in
(exit_code, Buffer.contents buf)
let () =
let cmd = Sys.argv.(1) in
let exit_code, out = syscall cmd in
print_endline (string_of_int exit_code);
print_endline out
This is the same, but it adds stderr in too:
let status_code s =
match s with
| Unix.WEXITED r -> r
| Unix.WSIGNALED r -> r
| Unix.WSTOPPED r -> r
let syscall cmd env =
let ic, oc, ec = Unix.open_process_full cmd env in
let out_buf = Buffer.create 32 and
err_buf = Buffer.create 32 in
ignore (try
while true do
Buffer.add_channel out_buf ic 1
done
with End_of_file -> ());
ignore (try
while true do
Buffer.add_channel err_buf ec 1
done
with End_of_file -> ());
let s = Unix.close_process_full (ic, oc, ec) in
let exit_code = status_code s in
(exit_code, Buffer.contents out_buf, Buffer.contents err_buf)
let () =
let cmd = Sys.argv.(1) in
let env = [|"foo=bar"|] in
let exit_code, out, err = syscall cmd env in
print_endline "------ stdout";
print_endline out;
print_endline "------ stderr";
print_endline err;
print_endline "------ exit code";
print_endline (string_of_int exit_code)
This is the same as above, but it moves some of the work into smaller functions:
let status_code s =
match s with
| Unix.WEXITED r -> r
| Unix.WSIGNALED r -> r
| Unix.WSTOPPED r -> r
let append_to_buffer b c =
Buffer.add_channel b c 1
let read_to_EOF b c =
try
while true do
append_to_buffer b c
done
with End_of_file -> ()
let dump_buffer b =
Buffer.contents b
let syscall cmd env =
let ic, oc, ec = Unix.open_process_full cmd env in
let out_buf = Buffer.create 32 and
err_buf = Buffer.create 32 in
ignore (read_to_EOF out_buf ic);
ignore (read_to_EOF err_buf ec);
let s = Unix.close_process_full (ic, oc, ec) in
let exit_code = status_code s in
(exit_code, dump_buffer out_buf, dump_buffer err_buf)
let () =
let cmd = Sys.argv.(1) in
let env = [|"foo=bar"|] in
let exit_code, out, err = syscall cmd env in
print_endline "------ stdout";
print_endline out;
print_endline "------ stderr";
print_endline err;
print_endline "------ exit code";
print_endline (string_of_int exit_code)
A final version of a general process runner using
the open_process_full function:
(** Compile with:
[ocamlc -c simpleproc.ml]
[ocamlc -o simpleproc.byte unix.cmo simpleproc.cmo]
Run it:
[./simpleproc.byte "ls -la"]
*)
module Buff = struct
let create = Buffer.create 32
let dump b = Buffer.contents b
let read_all b c =
try
while true do
Buffer.add_channel b c 1
done
with End_of_file -> ()
end
module Proc = struct
let popen = Unix.open_process_full
let pclose = Unix.close_process_full
let int_of_status s =
match s with
| Unix.WEXITED r -> r
| Unix.WSIGNALED r -> r
| Unix.WSTOPPED r -> r
end
module Cmd = struct
let proc cmd env out_buf err_buf =
let out_ch, in_ch, err_ch = Proc.popen cmd env in
ignore (Buff.read_all out_buf out_ch);
ignore (Buff.read_all err_buf err_ch);
let s = Proc.pclose (out_ch, in_ch, err_ch) in
let c = Proc.int_of_status s in
(c, Buff.dump out_buf, Buff.dump err_buf)
end
let main () =
let cmd = Sys.argv.(1) in
let env = [|"foo=bar"|] in
let out_buf = Buffer.create 32 and
err_buf = Buffer.create 32 in
let exit_code, out, err = Cmd.proc cmd env out_buf err_buf in
print_endline "------ stdout";
print_endline out;
print_endline "------ stderr";
print_endline err;
print_endline "------ exit code";
print_endline (string_of_int exit_code)
let () = Unix.handle_unix_error main ()
Create a file called forking.ml:
let pid = Unix.fork () in
if pid = 0 then (* child process *)
begin
Printf.printf "Child pid: %d\n" (Unix.getpid ())
end
else (* original (parent) process *)
begin
Printf.printf "Parent pid: %d\n" (Unix.getpid ());
exit 0
end
Fork copies the whole file into a child process. The child's pid is 0. Try it:
ocamlc -c forking.ml
ocamlc -o forking.byte unix.cma forking.cmo
./forking.byte
We can do the same with matching:
let pid = Unix.fork () in
match pid with
| 0 -> Printf.printf "Child pid: %d\n" (Unix.getpid ())
| -1 -> Printf.printf "Error forking.\n"
| _ -> Printf.printf "Parent pid: %d\n" (Unix.getpid ())
Try it:
ocamlc -c forking.ml
ocamlc -o forking.byte unix.cma forking.cmo
./forking.byte
Create a file forkandwait.ml:
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
in
let pid = Unix.fork () in
match pid with
| 0 ->
begin
Printf.printf "Child pid: %d\n" (Unix.getpid ());
Unix.sleep 2;
exit 5
end
| -1 ->
begin
Printf.printf "Error forking.\n"
end
| _ ->
begin
Printf.printf "Parent pid: %d\n" (Unix.getpid ());
let child_pid, status = Unix.wait () in
Printf.printf "Child %d exited with code %d\n"
child_pid (int_of_status status)
end
Try it:
ocamlc -c forkandwait.ml
ocamlc -o forkandwait.byte unix.cma forkandwait.cmo
./forkandwaait.byte
Here's another version:
let () =
match Unix.fork () with
| -1 ->
Printf.printf "Error forking\n"
| 0 ->
Printf.printf "Going to sleep\n";
flush stdout;
Unix.execv "/bin/sleep" [| "/bin/sleep"; "3" |]
| pid ->
let running = ref true in
while !running do
try
begin
ignore (Unix.waitpid [] pid);
running := false;
Printf.printf "Not running anymore.\n";
flush stdout
end
with Unix.Unix_error _ -> ()
done;
Run a command with a shell (/bin/sh or cmd.exe):
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let () =
let cmd = "echo running cmd && sleep 2 && echo done" in
let s = Unix.system cmd in
Printf.printf "Exit code: %d\n" (int_of_status s)
You can run the system call in a separate process:
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let () =
Printf.printf "Starting 'sleep 5'...\n"; flush stdout;
let pid = Unix.fork () in
match pid with
| -1 -> Printf.printf "Error forking.\n"
| 0 ->
Printf.printf "Child pid: %d\n" (Unix.getpid ()); flush stdout;
let s = Unix.system "echo runnin && sleep 20 && echo boo" in
Printf.printf "Done with code: %d\n" (int_of_status s)
| cpid ->
Printf.printf "Parent pid: %d - Child id: %d\n" (Unix.getpid ()) cpid;
flush stdout;
let cpid', s = Unix.waitpid [] (-1) in
Printf.printf "Child %d exited with code %d\n" cpid' (int_of_status s)
You could also use one of the execv* functions:
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let () =
Printf.printf "Starting 'sleep 5'...\n"; flush stdout;
let pid = Unix.fork () in
match pid with
| -1 -> Printf.printf "Error forking.\n"
| 0 ->
Printf.printf "Child pid: %d\n" (Unix.getpid ()); flush stdout;
let cmd = "echo running && sleep 10 && echo done" in
Unix.execvp "bash" [| "bash"; "-c"; cmd |]
| cpid ->
Printf.printf "Parent pid: %d - Child id: %d\n" (Unix.getpid ()) cpid;
flush stdout;
let cpid', s = Unix.waitpid [] (-1) in
Printf.printf "Child %d exited with code %d\n" cpid' (int_of_status s)
Note: if you run ps ax while either of the last two programs
are running, you'll notice that in
Create a process with create_process, then use waitpid [] pid
to wait for it to finish:
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let check_exit pid =
let cpid, s = Unix.waitpid [] pid in
s
let () =
Printf.printf "Starting 'sleep 3'...\n"; flush stdout;
let pid = Unix.create_process
"sleep" [| "sleep"; "3" |]
Unix.stdin Unix.stdout Unix.stderr
in
Printf.printf "Pid: %d\n" pid; flush stdout;
let s = check_exit pid in
Printf.printf "Exit code: %d\n" (int_of_status s)
If you don't want to block, we can use Unix.WNOHANG, and recursively
check the PID:
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let rec check_exit pid =
let cpid, s = Unix.waitpid [Unix.WNOHANG] pid in
match cpid with
| 0 -> (
Printf.printf "...\n"; flush stdout;
Unix.sleepf 0.5; check_exit pid)
| n -> s
let () =
Printf.printf "Starting 'sleep 3'...\n"; flush stdout;
let pid = Unix.create_process
"sleep" [| "sleep"; "3" |]
Unix.stdin Unix.stdout Unix.stderr
in
Printf.printf "Pid: %d\n" pid; flush stdout;
let s = check_exit pid in
Printf.printf "Exit code: %d\n" (int_of_status s)
A simple example. Make a file called ps.ml:
let ps cmd =
match Unix.fork() with
| 0 -> Unix.execv "/bin/sh" [| "/bin/sh"; "-c"; cmd |]
| child_pid -> child_pid
let () =
let pid = ps "echo running && sleep 5 && echo done now" in
Unix.waitpid [] pid
Here we define a function called ps that takes a command (as a string).
For instance, you would invoke this like so:
ps "ls -la"
In the body of the function, it forks. In the child process, it uses execv
to execute /bin/sh -c cmd. In the parent process, it simply returns
the child's pid.
In the main function at the bottom, we use the ps function to call
echo running && sleep 5 && echo done now. This returns the child's pid.
Then we use Unix.waitpid [] pid to wait until that pid is finished.
Compile and run it (you'll get a warning when you compile it, but that's okay for now):
ocamlc -c ps.ml
ocamlc -o ps.byte unix.cma ps.cmo
./ps.byte
Let's add some printing so we can see what's going on.
let ps cmd =
Printf.printf "Running '%s'\n" cmd; flush stdout;
match Unix.fork() with
| 0 ->
begin
let parent_pid = Unix.getppid () in
let child_pid = Unix.getpid () in
Printf.printf "Child pid: %d - Parent pid: %d\n" child_pid parent_pid;
flush stdout;
Unix.execv "/bin/sh" [| "/bin/sh"; "-c"; cmd |]
end
| child_pid ->
begin
let parent_pid = Unix.getpid () in
Printf.printf "Parent pid: %d - Child pid: %d\n" parent_pid child_pid;
flush stdout;
end;
child_pid
let () =
let main_pid = Unix.getpid () in
Printf.printf "Starting main pid: %d\n" main_pid;
flush stdout;
let pid = ps "echo running && sleep 5 && echo done now" in
Printf.printf "Spawned process id: %d\n" pid;
Printf.printf "Waiting for it to stop...\n";
flush stdout;
let pid', s = Unix.waitpid [] pid in
Printf.printf "Exit code: %d\n" (int_of_status s);
Printf.printf "Done\n"
The messages we print out show that there are two processes:
the parent and child. The parent is the OCaml program itself,
which spawns a child. The child then runs a command with execv,
and that command actually takes over the whole child process.
The child process simply becomes the bin/sh -c ... command.
You can confirm this by executing ps.byte, and then running
ps ax. You can look at the pids printed out by your OCaml program,
and then in the ps ax output, you'll see that the parent is
ps.byte, and the child is /bin/sh -c ....
What happens if the attempt to execute the child fails? Let's try it.
Here we try to execute /bin/shz rather than /bin/sh, which should
throw an error since /bin/shz does not exist.
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let ps cmd =
match Unix.fork() with
| 0 -> Unix.execv "/bin/shz" [| "/bin/shz"; "-c"; cmd |]
| child_pid -> child_pid
let main () =
let pid = ps "echo running && sleep 5 && echo done now" in
let pid', s = Unix.waitpid [] pid in
Printf.printf "Child %d exit code: %d\n" pid' (int_of_status s)
let () = main ()
If you run that, you'll see it throw an error which says that
execv failed on /bin/shz because there is no such /bin/shz.
This can be a useful message. We can use the Unix.handle_unix_error
function to catch and pretty-print it:
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let ps cmd =
match Unix.fork() with
| 0 -> Unix.execv "/bin/shz" [| "/bin/shz"; "-c"; cmd |]
| child_pid -> child_pid
let main () =
let pid = ps "echo running && sleep 5 && echo done now" in
let pid', s = Unix.waitpid [] pid in
Printf.printf "Child %d exit code: %d\n" pid' (int_of_status s)
let () = Unix.handle_unix_error main ()
We might also wish to catch the error in the child, and simply
exit with a 127. This is actually what OCaml itself does with most
of the process functions (system, create_process, etc). If you
use any of those to run a command, and it fails to run that command,
OCaml will catch it and have the child process exit with code 127.
Here is how we can do that:
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let ps cmd =
match Unix.fork() with
| 0 ->
begin try
Unix.execv "/bin/shz" [| "/bin/shz"; "-c"; cmd |]
with _ -> exit 127
end
| child_pid -> child_pid
let main () =
let pid = ps "echo running && sleep 5 && echo done now" in
let pid', s = Unix.waitpid [] pid in
Printf.printf "Child %d exit code: %d\n" pid' (int_of_status s)
let () = Unix.handle_unix_error main ()
If you run that, you'll that no unix error is thrown. Instead, the child simply exits with code 127.
Exiting with 127 is fairly standard behavior. Sometimes it may be more useful to display the actual error message though. It depends on the context.
Let's rename ps to pssh, and let's add an optional argument
for the shell executable:
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let psshell ?exe:(exe="/bin/sh") cmd =
match Unix.fork() with
| 0 -> Unix.execvp exe [| exe; "-c"; cmd |]
| child_pid -> child_pid
let main () =
let pid = psshell ~exe:("/bin/bash") "ls -la" in
let pid', s = Unix.waitpid [] pid in
Printf.printf "Child %d exit code: %d\n" pid' (int_of_status s)
let () = Unix.handle_unix_error main ()
This way, you can specify which shell you want to execute the command with.
Create a file called redirect.ml:
let () =
let cmd = "echo boo" in
Unix.execvp "/bin/sh" [| "/bin/sh"; "-c"; cmd |]
This simple runs the shell command echo boo.
Compile it, link it, and run it:
ocamlc -c redirect.ml
ocamlc -o redirect.byte unix.cma redirect.cmo
./redirect.byte
The program prints boo to stdout.
Now let's redirect that output to a file:
let () =
let cmd = "echo boo" in
let fd = Unix.openfile "out.log" [O_WRONLY; O_TRUNC; O_CREAT] 0o666 in
Unix.dup2 fd Unix.stdout;
Unix.close fd;
Unix.execvp "/bin/sh" [| "/bin/sh"; "-c"; cmd |]
Run this, and the output goes into the out.log file
rather than stdout.
Now let's make a pipe. Connect the write end up to stdout
(so that anything that gets written to stdout is written
to the pipe), and connect a read channel (called an in_channel)
to the read end of the pipe:
let () =
let cmd = "echo boo" in
let (stdout_read, stdout_write) = Unix.pipe () in
let stdout_ch = Unix.in_channel_of_descr stdout_read in
Unix.dup2 stdout_write Unix.stdout;
Unix.close stdout_write;
Unix.execvp "/bin/sh" [| "/bin/sh"; "-c"; cmd |]
Run this, and you'll see no output. The reason is that the shell command writes its stdout to the pipe, and that's where it ends. We don't actually read anything out of that channel.
We can't read anything, in fact, because execvp
takes over the program, and never returns. So we have
no opportunity to read from the channel.
But we can launch execvp in its own child process,
and then read from the parent process.
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let shell cmd out =
match Unix.fork () with
| 0 ->
Unix.dup2 out Unix.stdout;
Unix.close out;
Unix.execvp "/bin/sh" [| "/bin/sh"; "-c"; cmd |]
| pid -> pid
let () =
let cmd = "echo boo" in
let (stdout_read, stdout_write) = Unix.pipe () in
let stdout_ch = Unix.in_channel_of_descr stdout_read in
let pid = shell cmd stdout_write in
let pid', s = Unix.waitpid [] pid in
Printf.printf "Child %d exit %d\n" pid' (int_of_status s);
let first_line = input_line stdout_ch in
Printf.printf "stdout: %s\n" first_line;
close_in stdout_ch;
Let's do the same thing, but connect the shell command's stdout to a file:
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let shell cmd out =
match Unix.fork () with
| 0 ->
Unix.dup2 out Unix.stdout;
Unix.close out;
Unix.execvp "/bin/sh" [| "/bin/sh"; "-c"; cmd |]
| pid -> pid
let () =
let cmd = "echo boo" in
let write_file =
Unix.openfile "out.log" [O_WRONLY; O_TRUNC; O_CREAT] 0o666 in
let pid = shell cmd write_file in
let pid', s = Unix.waitpid [] pid in
Printf.printf "Child %d exit %d\n" pid' (int_of_status s);
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let shell cmd out err =
match Unix.fork () with
| 0 ->
Unix.dup2 out Unix.stdout;
Unix.close out;
Unix.dup2 err Unix.stderr;
Unix.close err;
Unix.execvp "/bin/sh" [| "/bin/sh"; "-c"; cmd |]
| pid -> pid
let start_ps cmd =
let (stdout_read, stdout_write) = Unix.pipe () in
let (stderr_read, stderr_write) = Unix.pipe () in
let pid = shell cmd stdout_write stderr_write in
(pid, stdout_read, stderr_read)
let poll_ps pid =
let pid', s = Unix.waitpid [Unix.WNOHANG] pid in
match pid' with
| 0 -> None
| _ -> Some (int_of_status s)
let () =
let cmd = "sleep 1 && echo boo && echo foo > /dev/stderr && exit 3" in
let pid, stdout_fd, stderr_fd = start_ps cmd in
Printf.printf "Child %d started.\n%!" pid;
(let exit_code = poll_ps pid in
match exit_code with
| None -> Printf.printf "Child not finished yet.\n%!"
| Some n -> Printf.printf "Exit code: %d\n%!" n);
Printf.printf "Sleeping\n%!";
Unix.sleep 1;
let stdout_ch = Unix.in_channel_of_descr stdout_fd in
Printf.printf "Stdout: %s\n%!" (input_line stdout_ch);
let stderr_ch = Unix.in_channel_of_descr stderr_fd in
Printf.printf "Stderr: %s\n%!" (input_line stderr_ch);
(let exit_code = poll_ps pid in
match exit_code with
| None -> Printf.printf "Child not finished yet.\n%!"
| Some n -> Printf.printf "Exit code: %d\n%!" n);
Printf.printf "Sleeping again\n%!";
Unix.sleep 1;
(let exit_code = poll_ps pid in
match exit_code with
| None -> Printf.printf "Child not finished yet.\n%!"
| Some n -> Printf.printf "Exit code: %d\n%!" n);
Printf.printf "Done\n%!";
close_in stdout_ch;
close_in stderr_ch;
Add a wait_and_do polling ability:
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let shell cmd out err =
match Unix.fork () with
| 0 ->
Unix.dup2 out Unix.stdout;
Unix.close out;
Unix.dup2 err Unix.stderr;
Unix.close err;
Unix.execvp "/bin/sh" [| "/bin/sh"; "-c"; cmd |]
| pid -> pid
let start_ps cmd =
let (stdout_read, stdout_write) = Unix.pipe () in
let (stderr_read, stderr_write) = Unix.pipe () in
let pid = shell cmd stdout_write stderr_write in
(pid, stdout_read, stderr_read)
let poll_ps pid =
let pid', s = Unix.waitpid [Unix.WNOHANG] pid in
match pid' with
| 0 -> None
| _ -> Some (int_of_status s)
let rec wait_and_do pid delay f x =
let exit_code = poll_ps pid in
match exit_code with
| None ->
f x;
Unix.sleepf delay;
wait_and_do pid delay f x
| Some n -> n
let report () =
Printf.printf "Polling...\n%!"
let () =
let cmd = "sleep 2 && echo boo && echo foo > /dev/stderr && exit 3" in
let pid, stdout_fd, stderr_fd = start_ps cmd in
Printf.printf "Child %d started.\n%!" pid;
let exit_code = wait_and_do pid 0.25 report () in
Printf.printf "Exit code: %d\n%!" exit_code;
let stdout_ch = Unix.in_channel_of_descr stdout_fd in
Printf.printf "Stdout: %s\n%!" (input_line stdout_ch);
let stderr_ch = Unix.in_channel_of_descr stderr_fd in
Printf.printf "Stderr: %s\n%!" (input_line stderr_ch);
Printf.printf "Done\n%!";
close_in stdout_ch;
close_in stderr_ch;
Let's move the channels into the start_ps command, and add some error handling around the attempts to open file descriptors -- if these fail, we need to be sure to close them:
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let shell cmd out err =
match Unix.fork () with
| 0 ->
Unix.dup2 out Unix.stdout;
Unix.close out;
Unix.dup2 err Unix.stderr;
Unix.close err;
Unix.execvp "/bin/sh" [| "/bin/sh"; "-c"; cmd |]
| pid -> pid
let start_ps cmd =
let (stdout_read, stdout_write) = Unix.pipe ~cloexec:true () in
let (stderr_read, stderr_write) =
try Unix.pipe ~cloexec:true ()
with e ->
Unix.close stdout_read;
Unix.close stdout_write;
raise e in
let out_ch = Unix.in_channel_of_descr stdout_read in
let err_ch = Unix.in_channel_of_descr stderr_read in
begin
try
let pid = shell cmd stdout_write stderr_write in
Unix.close stdout_write;
Unix.close stderr_write;
(pid, out_ch, err_ch)
with e ->
Unix.close stdout_read; Unix.close stdout_write;
Unix.close stderr_read; Unix.close stderr_write;
raise e;
end
let poll_ps pid =
let pid', s = Unix.waitpid [Unix.WNOHANG] pid in
match pid' with
| 0 -> None
| _ -> Some (int_of_status s)
let rec poll_ps_every pid delay f x =
let exit_code = poll_ps pid in
match exit_code with
| None ->
f x;
Unix.sleepf delay;
poll_ps_every pid delay f x
| Some n -> n
let report stdout_ch stderr_ch =
Printf.printf "Polling...\n%!";
Printf.printf "stdout %s\n%!" (input_line stdout_ch);
Printf.printf "stderr %s\n%!" (input_line stderr_ch);
Printf.printf "-- done polling...\n%!"
let () =
let cmd = "bash test.sh" in
let pid, stdout_ch, stderr_ch = start_ps cmd in
Printf.printf "Child %d started.\n%!" pid;
let exit_code = poll_ps_every pid 0.25 (report stdout_ch) stderr_ch in
Printf.printf "Exit code: %d\n%!" exit_code;
Printf.printf "Done\n%!";
close_in stdout_ch;
close_in stderr_ch;
Let's make the pipes non-blocking:
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let shell cmd out err =
match Unix.fork () with
| 0 ->
Unix.dup2 out Unix.stdout;
Unix.close out;
Unix.dup2 err Unix.stderr;
Unix.close err;
Unix.execvp "/bin/sh" [| "/bin/sh"; "-c"; cmd |]
| pid -> pid
let start_ps cmd =
let (stdout_read, stdout_write) = Unix.pipe ~cloexec:true () in
let (stderr_read, stderr_write) =
try Unix.pipe ~cloexec:true ()
with e ->
Unix.close stdout_read;
Unix.close stdout_write;
raise e in
Unix.set_nonblock stdout_read;
Unix.set_nonblock stderr_read;
let out_ch = Unix.in_channel_of_descr stdout_read in
let err_ch = Unix.in_channel_of_descr stderr_read in
begin
try
let pid = shell cmd stdout_write stderr_write in
Unix.close stdout_write;
Unix.close stderr_write;
(pid, out_ch, err_ch)
with e ->
Unix.close stdout_read; Unix.close stdout_write;
Unix.close stderr_read; Unix.close stderr_write;
raise e;
end
let poll_ps pid =
let pid', s = Unix.waitpid [Unix.WNOHANG] pid in
match pid' with
| 0 -> None
| _ -> Some (int_of_status s)
let rec poll_ps_every pid delay f x =
let exit_code = poll_ps pid in
match exit_code with
| None ->
f x;
Unix.sleepf delay;
poll_ps_every pid delay f x
| Some n ->
f x;
n
let read_line ch lbl =
Printf.printf "%s - attempting to read\n%!" lbl;
try
let line = input_line ch in
Printf.printf "%s line: %s\n%!" lbl line
with Sys_blocked_io ->
Printf.printf "%s - Sys_blocked_io\n%!" lbl
let report stdout_ch stderr_ch =
Printf.printf "Polling...\n%!";
read_line stdout_ch "out";
read_line stderr_ch "err";
Printf.printf "-- done polling...\n%!"
let () =
let cmd = "bash test.sh" in
let pid, stdout_ch, stderr_ch = start_ps cmd in
Printf.printf "Child %d started.\n%!" pid;
let exit_code = poll_ps_every pid 0.25 (report stdout_ch) stderr_ch in
Printf.printf "Exit code: %d\n%!" exit_code;
Printf.printf "Done\n%!";
close_in stdout_ch;
close_in stderr_ch;
Instead of printing out the output, let's put stdout/stderr into buffers.
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let shell cmd out err =
match Unix.fork () with
| 0 ->
Unix.dup2 out Unix.stdout;
Unix.close out;
Unix.dup2 err Unix.stderr;
Unix.close err;
Unix.execvp "/bin/sh" [| "/bin/sh"; "-c"; cmd |]
| pid -> pid
let start_ps cmd =
let (stdout_read, stdout_write) = Unix.pipe ~cloexec:true () in
let (stderr_read, stderr_write) =
try Unix.pipe ~cloexec:true ()
with e ->
Unix.close stdout_read;
Unix.close stdout_write;
raise e in
Unix.set_nonblock stdout_read;
Unix.set_nonblock stderr_read;
let out_ch = Unix.in_channel_of_descr stdout_read in
let err_ch = Unix.in_channel_of_descr stderr_read in
begin
try
let pid = shell cmd stdout_write stderr_write in
Unix.close stdout_write;
Unix.close stderr_write;
(pid, out_ch, err_ch)
with e ->
Unix.close stdout_read; Unix.close stdout_write;
Unix.close stderr_read; Unix.close stderr_write;
raise e;
end
let poll_ps pid =
let pid', s = Unix.waitpid [Unix.WNOHANG] pid in
match pid' with
| 0 -> None
| _ -> Some (int_of_status s)
let rec poll_ps_every pid delay f x =
let exit_code = poll_ps pid in
match exit_code with
| None ->
f x;
Unix.sleepf delay;
poll_ps_every pid delay f x
| Some n ->
f x;
n
let ch_buf ch len = (ch, Buffer.create len)
let read_buf cb =
let ch = fst(cb) and buf = snd(cb) in
try
while true do
Buffer.add_channel buf ch 1
done
with
| Sys_blocked_io -> ()
| End_of_file -> ()
let string_of_buf cb =
let buf = snd(cb) in
Buffer.contents buf
let collect out_buf err_buf =
read_buf out_buf; read_buf err_buf
let () =
let cmd = "bash test.sh" in
let pid, stdout_ch, stderr_ch = start_ps cmd in
Printf.printf "Child %d started.\n%!" pid;
let out_buf = ch_buf stdout_ch 80 and
err_buf = ch_buf stderr_ch 80 in
let exit_code = poll_ps_every pid 0.25 (collect out_buf) err_buf in
Printf.printf "Exit code: %d\n%!" exit_code;
Printf.printf "\nOut buffer: %s\n%!" (string_of_buf out_buf);
Printf.printf "\nErr buffer: %s\n%!" (string_of_buf err_buf);
Printf.printf "Done\n%!";
close_in stdout_ch;
close_in stderr_ch;
module Proc = struct
let int_of_status s =
match s with
| Unix.WEXITED n -> n
| Unix.WSIGNALED n -> n
| Unix.WSTOPPED n -> n
let shell cmd out err =
match Unix.fork () with
| 0 ->
Unix.dup2 out Unix.stdout;
Unix.close out;
Unix.dup2 err Unix.stderr;
Unix.close err;
Unix.execvp "/bin/sh" [| "/bin/sh"; "-c"; cmd |]
| pid -> pid
let popen cmd =
let (stdout_read, stdout_write) = Unix.pipe ~cloexec:true () in
let (stderr_read, stderr_write) =
try Unix.pipe ~cloexec:true ()
with e ->
Unix.close stdout_read;
Unix.close stdout_write;
raise e in
Unix.set_nonblock stdout_read;
Unix.set_nonblock stderr_read;
let out_ch = Unix.in_channel_of_descr stdout_read in
let err_ch = Unix.in_channel_of_descr stderr_read in
begin
try
let pid = shell cmd stdout_write stderr_write in
Unix.close stdout_write;
Unix.close stderr_write;
(pid, out_ch, err_ch)
with e ->
Unix.close stdout_read; Unix.close stdout_write;
Unix.close stderr_read; Unix.close stderr_write;
raise e;
end
let poll pid =
let pid', s = Unix.waitpid [Unix.WNOHANG] pid in
match pid' with
| 0 -> None
| _ -> Some (int_of_status s)
end
module Buff = struct
let create ch len = (ch, Buffer.create len)
let contents b =
let buf = snd(b) in
Buffer.contents buf
let read b =
let ch = fst(b) and buf = snd(b) in
try
while true do
Buffer.add_channel buf ch 1
done
with
| Sys_blocked_io -> ()
| End_of_file -> ()
end
module Cmd = struct
let rec while_waiting pid delay f x =
match Proc.poll pid with
| None ->
f x;
Unix.sleepf delay;
while_waiting pid delay f x
| Some n ->
f x;
n
let collect out_buf err_buf =
Buff.read out_buf; Buff.read err_buf
let run cmd =
let pid, stdout_ch, stderr_ch = Proc.popen cmd in
let out_buf = Buff.create stdout_ch 80 in
let err_buf = Buff.create stderr_ch 80 in
let exit_code = while_waiting pid 0.25 (collect out_buf) err_buf in
close_in stdout_ch;
close_in stderr_ch;
(exit_code, out_buf, err_buf)
end
let () =
let cmd = "bash test.sh" in
let exit_code, out_buf, err_buf = Cmd.run cmd in
Printf.printf "Out buffer:\n%s\n%!" (Buff.contents out_buf);
Printf.printf "Err buffer:\n%s\n%!" (Buff.contents err_buf);
Printf.printf "Exit code: %d\n%!" exit_code;
let () =
match Unix.fork () with
| 0 ->
(* child ignores INT and does its thing *)
Sys.set_signal Sys.sigint Sys.Signal_ignore;
Unix.execv "/bin/sleep" [| "/bin/sleep"; "10" |]
| pid ->
(* parent catches INT and berates user *)
Sys.set_signal Sys.sigint
(Sys.Signal_handle
(fun _ -> print_endline "Tsk tsk, no process interruptus"));
let running = ref true in
while !running do
try (ignore (Unix.waitpid [] pid); running := false)
with Unix.Unix_error _ -> ()
done;
Sys.set_signal Sys.sigint Sys.Signal_default
Create a file called measuresleep.ml:
let repeats = 5 in
let total_time = ref 0.0 in
for i = 1 to repeats do
let before = Unix.gettimeofday () in
Unix.sleep 1;
let after = Unix.gettimeofday () in
let delta = after -. before in
Printf.printf "Delta: %.5f\n" delta;
total_time := !total_time +. delta
done;
let avg = !total_time /. (float_of_int repeats) in
Printf.printf "Total: %.5f - Avg: %.5f\n" !total_time avg
Try it:
ocamlc -c measuresleep.ml
ocamlc -o measuresleep.byte unix.cma measuresleep.cmo
./measuresleep.byte
Save the above file as ps.ml, and remove the
final let () = ..., so that it only contains
the modules.
Then create a new file called timeit.ml:
let time_one f x =
let start = Unix.gettimeofday () in
let result = f x in
let stop = Unix.gettimeofday () in
let delta = stop -. start in
(delta, result)
let () =
Printf.printf "Starting...\n%!";
let cmd = "bash test.sh" in
let delta, result = time_one Ps.Cmd.run cmd in
let exit_code (n, _, _) = n in
let stdout (_, n, _) = n in
Printf.printf "exit code: %d\n%!" (exit_code result);
Printf.printf "stdout: %s\n%!" (Ps.Buff.contents (stdout result));
Printf.printf "Time: %.3f\n%!" delta
Compile the modules:
ocamlc -c ps.ml
ocamlc -c timeit.ml
Then link it:
ocamlc -o timeit.byte unix.cma ps.cmo timeit.cmo
And run it:
./timeit.byte
Let's add the ability to run a function many times, collect the results, and average the running time:
let time f x =
let start = Unix.gettimeofday () in
let result = f x in
let stop = Unix.gettimeofday () in
let delta = stop -. start in
(delta, result)
let rec time_r count n results f x =
let result = time f x in
match (count < n) with
| false -> results
| true ->
time_r (count + 1) n (results @ [result]) f x
let rec sumf l acc =
match l with
| [] -> acc
| h :: t -> sumf t (acc +. h)
let deltas l = List.map (fun x -> fst(x)) l
let avg l =
let times = deltas l in
let total_time = sumf times 0.0 in
total_time /. (float_of_int (List.length times))
let () =
Printf.printf "Starting...\n%!";
let cmd = "bash test.sh" in
let trials = time_r 0 3 [] Ps.Cmd.run cmd in
List.iter (fun x ->
let get_delta (n, _) = n in
let get_result (_, n) = n in
let get_exit_code (n, _, _) = n in
let get_stdout (_, n, _) = n in
let delta = get_delta x in
let result = get_result x in
Printf.printf "----\n%!";
Printf.printf "exit code: %d\n%!" (get_exit_code result);
Printf.printf "stdout:\n%s\n%!" (Ps.Buff.contents (get_stdout result));
Printf.printf "Time: %.3f\n%!" delta)
trials;
let avg_time = avg trials in
Printf.printf "Avg time: %.3f\n%!" avg_time;
let total_time = sumf (deltas trials) 0.0 in
Printf.printf "Total time: %.3f\n%!" total_time
Let's package up some of the functions into a module:
module Timer = struct
let time f x =
let start = Unix.gettimeofday () in
let result = f x in
let stop = Unix.gettimeofday () in
let delta = stop -. start in
(delta, result)
let rec repeat count n results f x =
let result = time f x in
match (count < n) with
| false -> results
| true ->
repeat (count + 1) n (results @ [result]) f x
let rec sumf l acc =
match l with
| [] -> acc
| h :: t -> sumf t (acc +. h)
let deltas l = List.map (fun x -> fst(x)) l
let total_time l =
let times = deltas l in
sumf times 0.0
let avg l =
let num_trials = List.length l in
let total = total_time l in
total /. (float_of_int (num_trials))
end
let () =
Printf.printf "Starting...\n%!";
let cmd = "bash test.sh" in
let trials = Timer.repeat 0 3 [] Ps.Cmd.run cmd in
List.iter (fun x ->
let get_delta (n, _) = n in
let get_result (_, n) = n in
let get_exit_code (n, _, _) = n in
let get_stdout (_, n, _) = n in
let delta = get_delta x in
let result = get_result x in
Printf.printf "----\n%!";
Printf.printf "exit code: %d\n%!" (get_exit_code result);
Printf.printf "stdout:\n%s\n%!" (Ps.Buff.contents (get_stdout result));
Printf.printf "Time: %.3f\n%!" delta)
trials;
let avg_time = Timer.avg trials in
Printf.printf "Avg time: %.3f\n%!" avg_time;
let total_time = Timer.total_time trials in
Printf.printf "Total time: %.3f\n%!" total_time
Open a file, write a message to it, and close the file:
let main () =
let ch = open_out "log.txt" in
Printf.fprintf ch "Lorem ipsum...\n%!";
close_out ch
let () = Unix.handle_unix_error main ()
Let's put the writing operation into a function:
let log ch msg = Printf.fprintf ch "%s\n%!" msg
let main () =
let ch = open_out "/root/test-log.txt" in
log ch "Lorem ipsum...";
close_out ch
let () = Unix.handle_unix_error main ()
Let's write the channel (with a name) to a hash table, and then let's close it by looking it up from the hash table.
let log ch msg = Printf.fprintf ch "%s\n%!" msg
let main () =
let log_hashtbl = Hashtbl.create 256 in
let ch = open_out "verbose.log" in
Hashtbl.add log_hashtbl "verbose_log" ch;
log ch "Lorem ipsum...";
let log_ch = Hashtbl.find log_hashtbl "verbose_log" in
close_out log_ch
let () = Unix.handle_unix_error main ()
Now let's put the opening and closing into functions:
let log_hashtbl = Hashtbl.create 256
let log ch msg = Printf.fprintf ch "%s\n%!" msg
let open_log name filename =
let ch = open_out filename in
Hashtbl.add log_hashtbl name ch;
ch
let close_log name =
let ch = Hashtbl.find log_hashtbl name in
close_out ch
let main () =
let verbose_log = open_log "verbose_log" "out.log" in
log verbose_log "Lorem ipsum...";
close_log "verbose_log"
let () = Unix.handle_unix_error main ()
Let's register a function that will close the log at exit. Then we don't have to explicitly close the log (unless we want to).
let log_hashtbl = Hashtbl.create 256
let log ch msg = Printf.fprintf ch "%s\n%!" msg
let close_log name =
let ch = Hashtbl.find log_hashtbl name in
close_out ch
let open_log name filename =
let ch = open_out filename in
Hashtbl.add log_hashtbl name ch;
at_exit (fun _ -> close_log name);
ch
let main () =
let verbose_log = open_log "verbose_log" "out.log" in
log verbose_log "Lorem ipsum..."
let () = Unix.handle_unix_error main ()
Let's add the ability to open a channel to stdout, stderr, or a file:
let log_hashtbl = Hashtbl.create 256
let log ch msg = Printf.fprintf ch "%s\n%!" msg
let close_log name =
let ch = Hashtbl.find log_hashtbl name in
close_out ch
let log_channel target =
match target with
| "stdout" -> stdout
| "stderr" -> stderr
| filename -> open_out filename
let open_log name target =
let ch = log_channel target in
Hashtbl.add log_hashtbl name ch;
at_exit (fun _ -> close_log name);
ch
let main () =
let verbose_log = open_log "verbose_log" "out.log" in
log verbose_log "Lorem ipsum...";
let program_log = open_log "program_log" "stdout" in
log program_log "Logging to stdout..."
let () = Unix.handle_unix_error main ()
Let's package it up into a module:
module Logs = struct
let hash = Hashtbl.create 256
let log name msg =
let ch = Hashtbl.find hash name in
Printf.fprintf ch "%s\n%!" msg
let close name =
let ch = Hashtbl.find hash name in
close_out ch
let channel target =
match target with
| "stdout" -> stdout
| "stderr" -> stderr
| filename -> open_out_gen [Open_creat; Open_text; Open_append] 0o640 filename
let create name target =
let ch = channel target in
Hashtbl.add hash name ch;
at_exit (fun _ -> close name)
end
let main () =
Logs.create "verbose_log" "verbose.log";
Logs.log "verbose_log" "Logging to verbose log...";
Logs.create "other_verbose_log" "verbose.log";
Logs.log "other_verbose_log" "Logging to other verbose log...";
Logs.create "main_log" "stdout";
Logs.log "main_log" "Logging to stdout...";
Printf.printf "Done\n%!"
let () = Unix.handle_unix_error main ()
- The source code for the
Unixlibrary is excellent: https://github.com/ocaml/ocaml/blob/trunk/otherlibs/unix/unix.ml