qubit-command 0.2.1

Command-line process running utilities for Rust
Documentation

Qubit Command

CircleCI Coverage Status Crates.io Rust License 中文文档

Command-line process running utilities for Rust.

Overview

Qubit Command provides a small, structured API for running external programs, capturing their output, enforcing timeouts, and reporting command failures with clear error values.

Features

  • Structured command execution with program and argument vectors
  • Explicit shell command support for cases that require shell parsing
  • Configurable timeout, working directory, stdin, environment variables, and success exit codes
  • Process-tree termination on timeout using Unix process groups and Windows Job Objects
  • UTF-8 stdout and stderr text accessors, with raw byte accessors for binary output
  • Optional per-stream capture limits plus streaming tee files for large output
  • Typed errors for spawn failures, timeouts, failed output reads, and unexpected exit codes

Timeout Behavior

CommandRunner::new() does not enforce a timeout by default. Use timeout(Duration) when a command must be bounded, or without_timeout() when the absence of a timeout should be explicit in builder chains.

When a timeout is configured, the runner attempts to terminate the process tree: Unix commands are spawned in a new process group and Windows commands are spawned in a Job Object.

Large Output

By default stdout and stderr are captured without an in-memory byte limit. For commands that can emit large logs, configure capture limits and tee files:

use qubit_command::{Command, CommandRunner};

let output = CommandRunner::new()
    .max_output_bytes(64 * 1024)
    .tee_stdout_to_file("stdout.log")
    .tee_stderr_to_file("stderr.log")
    .run(Command::new("cargo").arg("test"))?;

if output.stdout_truncated() {
    eprintln!("stdout was truncated in memory; see stdout.log for the full stream");
}
# Ok::<(), Box<dyn std::error::Error>>(())

Quick Start

use std::time::Duration;

use qubit_command::{Command, CommandRunner};

let output = CommandRunner::new()
    .timeout(Duration::from_secs(10))
    .run(Command::new("git").args(&["status", "--short"]))?;

println!("{}", output.stdout()?);
# Ok::<(), Box<dyn std::error::Error>>(())

Shell Commands

Prefer structured commands whenever possible:

use qubit_command::{Command, CommandRunner};

let output = CommandRunner::new()
    .run(Command::new("printf").arg("hello"))?;

assert_eq!(output.stdout()?, "hello");
# Ok::<(), Box<dyn std::error::Error>>(())

Use Command::shell only when shell parsing, redirection, expansion, or pipes are intentional:

use qubit_command::{Command, CommandRunner};

let output = CommandRunner::new()
    .run(Command::shell("printf hello | tr a-z A-Z"))?;

assert_eq!(output.stdout()?, "HELLO");
# Ok::<(), Box<dyn std::error::Error>>(())

Output Text

stdout() and stderr() return UTF-8 text by default. Use stdout_bytes() and stderr_bytes() when a command can emit arbitrary bytes. To replace invalid UTF-8 bytes with , enable lossy output on the runner.

If lossy output is disabled and the captured stdout or stderr contains invalid UTF-8, stdout() / stderr() return Err(str::Utf8Error) from str::from_utf8—you cannot obtain a &str for that stream. The bytes are still stored on the returned CommandOutput; use stdout_bytes() / stderr_bytes() to read the raw output and decode or handle it yourself.

use qubit_command::{Command, CommandRunner};

let output = CommandRunner::new()
    .lossy_output(true)
    .run(Command::shell("printf '\\377'"))?;

assert_eq!(output.stdout()?, "\u{fffd}");
# Ok::<(), Box<dyn std::error::Error>>(())

Testing

A minimal local run:

cargo test
cargo clippy --all-targets --all-features -- -D warnings

To mirror what continuous integration enforces, run the repository scripts from the project root: ./align-ci.sh brings local tooling and configuration in line with CI, then ./ci-check.sh runs the same checks the pipeline uses. For test coverage, use ./coverage.sh to generate or open reports (see the script’s help and any project coverage notes for options such as HTML or JSON).

Contributing

Issues and pull requests are welcome.

  • Open an issue for bug reports, design questions, or larger feature proposals when it helps align on direction.
  • Keep pull requests scoped to one behavior change, fix, or documentation update when practical.
  • Before submitting, run ./align-ci.sh and then ./ci-check.sh so your branch matches CI rules and passes the same checks as the pipeline. When you need to review or improve coverage, use ./coverage.sh as described under Testing.
  • Add or update tests when you change runtime behavior, and update this README (or public rustdoc) when user-visible API behavior changes.

By contributing, you agree to license your contributions under the Apache License, Version 2.0, the same license as this project.

License

Copyright © 2026 Haixing Hu, Qubit Co. Ltd.

This project is licensed under the Apache License, Version 2.0. See the LICENSE file in the repository for the full text.

Author

Haixing Hu — Qubit Co. Ltd.

Repository github.com/qubit-ltd/rs-command
Documentation docs.rs/qubit-command
Crate crates.io/crates/qubit-command