#child-process #file-descriptor #process #env-var #child #command #pipe

creche

A replacement for std::process::Command and related types in the standard library. Runs single processes, and pipelines of processes, with configuration of file descriptors and environment for each child process.

5 releases

0.2.1 Jul 1, 2024
0.2.0 Jun 30, 2024
0.1.4 Jun 13, 2024

#233 in Concurrency

MIT/Apache

46KB
776 lines

Creche

Configure, run, and monitor single child processes or entire pipelines of processes. Redirect file descriptors to and from those processes. Control environment variables.

Creche is an alternative to Command and friends in the standard library.

Goals

  • Minimal dependencies
  • No macros, just regular Rust syntax
  • Keep the easy stuff easy, without sacrificing configurability

Limitations

  • Linux only
  • Doesn't support nostd
  • Doesn't handle child process priveleges/capabilities yet
  • No async support yet

How to Use

The ChildBuilder and SimplePipelineBuilder types are top level exports, so many common use cases are enabled with a simple use:

use creche::*;

Read the changelog for a description of breaking changes to the API.

Examples

Read output of cat into a String:

// configure the child process
let mut cmd = ChildBuilder::new("cat");
cmd.arg("somefile")
    .redirect21(); // redirect stderr to stdout
let read_fd = cmd.pipe_from_stdout();

// run it
let mut child = cmd.spawn();

// read stdout from the child process
let f = std::fs::File::from(read_fd);
let output = std::io::read_to_string(f)?;
println!("output is: {:?}", output);
println!("exit status: {:?}", child.wait());

Write data to a child process:

let mut cmd = creche::ChildBuilder::new("tr");
cmd.arg("[:lower:]")
    .arg("[:upper:]");
let write_fd = cmd.pipe_to_stdin();
let child = cmd.spawn();

// write some data
let mut f = std::fs::File::from(write_fd);
writeln!(f, "this is a test message");
// don't forget to close your fd. Or at least .flush() the thing.
drop(f);

println!("child exit: {:?}", child.wait());

Set up fzf to run in an environment with just PATH, HOME, and FZF_DEFAULT_COMMAND. Then run it and get its output:

// configure the environment
let mut env = envconfig::EnvironmentBuilder::new();
env.keep("PATH")
    .set("HOME", "/etc")
    // Note: $HOME is expanded in the shell spawned by fzf.
    // It's not creche magic.
    .set("FZF_DEFAULT_COMMAND", "ls -1 $HOME");
// configure the child process
let mut cmd = ChildBuilder::new("fzf");
cmd.arg("-m")
    .arg(r#"--preview=bat $HOME/{}"#)
    .set_env(env.realize());
let read_fd = cmd.pipe_from_stdout();

// run it
let child = cmd.spawn();

// read the result
let mut f = std::fs::File::from(read_fd);
let output = std::io::read_to_string(f)?;
println!("output is: {:?}", output);
println!("exit status: {:?}", child.wait());

Pipe the output of ls into sort into tr. Redirect stderr for all of that to /dev/null:

let mut ls_cmd = ChildBuilder::new("ls");
ls_cmd.arg("-1");
let mut sort_cmd = ChildBuilder::new("sort");
sort_cmd.arg("-r");
let mut tr_cmd = ChildBuilder::new("tr");
tr_cmd.arg("[:lower:]");
tr_cmd.arg("[:upper:]");

let mut pipeline = SimplePipelineBuilder::new();
let mut children = pipeline
    .add_builder(ls_cmd)
    .add_builder(sort_cmd)
    .add_builder(tr_cmd)
    .quiet()
    .spawn();

println!("{:?}", children.wait());

Block on .wait()ing a child process and send a SIGTERM from another thread:

// sleep for a few seconds
let mut cmd = ChildBuilder::new("sleep");
cmd.arg("6");
println!("sleeping for six seconds");
let child = cmd.spawn();

// get a "handle" to the child process so that we may signal it
let handle = child.get_handle();

// start a thread that sends SIGTERM after a short pause
std::thread::spawn( move || {
    std::thread::sleep(std::time::Duration::from_secs(2));
    println!("sending SIGTERM from thread");
    _ = handle.terminate();
});

// collect the child exit status
println!("child exit: {:?}", child.wait());

Dependencies

~1.5MB
~35K SLoC