sh-exec 0.1.3

Set of functions and macros to write more concise rust scripts.
Documentation

shell-exec

This is a crate that simplifies the execution of CLI programs from Rust programs using a macro exec!(error_id, cmd). The error_id is just a unique string literal that is printed in the case of an error. I typically generate this string literal as follows:

echo "\"$RANDOM-$RANDOM-$RANDOM\", "

and pasting the output in the as the first argument of exec!. This simplifies finding the right code snipped in case you need to track down the code that issued a certain error message.

On success, exec! returns the stdout of the executed command. If the execution fails, the macro retuns an Err of type ShellError. One can handle the errors using the question mark operator:

    exec!("10874-26631-30577", "ls")`

The cmd argument is a format sting, i.e., one can use positional and named arguments as well as variable names:

    let path="/tmp";
    exec!("17068-22053-696", "ls {path}")`

As for macro format!, macro exec! supports positional arguments:

    // example: with position argument "/"
    println!("ls of {path} is {}", exec!("15911-12192-19189", "ls {}", "/")?);

exec! also supports named arguments:

    // example: with named argument p="/tmp"
    println!("ls of {path} is {}", exec!("15911-12192-19189", "ls {p}", p="/tmp")?);

Hence, prints on stderr an error message that includes:

  • the command line that failed,

  • the error ID,

  • the stdout,

  • the stderr,

  • the cargo information related to this

Example

Here is a simple program that uses this crate:

#!/usr/bin/env rust-script
//! ```cargo
//! [package]
//! name = "example"
//! edition = "2024"
//!
//! [dependencies]
//! clap = { version = "4", features = ["derive"] }
//! sh-exec = "*" 
//! colored = "*"
//! ```

use shell_exec::*;

fn main() {
    trap_panics_and_errors!("18428-30925-25863", || {

        // example: ls of /tmp
        let path="/etc";
        exec!("17068-22053-696", true, "ls -d {path}")?;

        // example: with position argument "/"
        println!("ls -d of / is {}", exec!("15911-12192-19189", false,  "ls -d {}", "/")?);

        // example: with named argument p="/tmp"
        println!("ls of /etc/hosts is {}", exec!("15911-12192-19189", true, "ls {p}", p="/etc/hosts")?);

        // Test successful command
        let output = exec!("28328-2323-44343", true, "bash -c 'echo Hello World'")?;
        println!("Output: {}", output);

        // Test failing command
        match exec!("28328-2323-3278", true, "nonexistent_command") {
            Ok(output) => println!("Unexpected success: {}", output),
            Err(e) => println!("Expected error: {}", e),
        }
        // expecting to fail:
        exec!( "28328-2323-333", true,  "nonexistent_command arg1 arg2")?;
 
        Ok::<(), Box<dyn Error>>(())
    });
}

Executing the code as a rust-script (see file example.rs), we get the following output:

$ ./example.rs
exec!(17068-22053-696,ls -d /etc)
ls -d of / is /

exec!(15911-12192-19189,ls /etc/hosts)
ls of /etc/hosts is /etc/hosts

exec!(28328-2323-44343,bash -c 'echo Hello World')
Output: Hello World

exec!(28328-2323-3278,nonexistent_command)
Expected error: Command failed: nonexistent_command
Exit code: 127
Error ID: 28328-2323-3278
Standard error:
sh: 1: nonexistent_command: not found


exec!(28328-2323-333,nonexistent_command arg1 arg2)
trap_panics_and_errors: 18428-30925-25863
  Version: 0.1.0
  Name: example
  Authors: Anonymous
  Description:
  Homepage:
  Repository:
  Error: Command failed: nonexistent_command arg1 arg2
Exit code: 127
Error ID: 28328-2323-333
Standard error:
sh: 1: nonexistent_command: not found