Macro rsbash::rashf

source ·
macro_rules! rashf {
    ($($arg:tt)*) => { ... };
}
Expand description

Format and run a bash command.

Arguments:

rashf! expects at least a single argument of a string literal representing the command to run. Any further arguments should be formatting arguments to the command.

This syntax is the exact syntax of the well-known and well-loved format! macro, see std::fmt for more details.

Returns:

rashf! returns a Result<(i32, String, String), RashError>.

The (i32, String, String) tuple contains the return value, the stdout and the stderr of the command, respectively.

See RashError for more details of the error.

Examples

Formatting:

Format rashf! commands just like you would format! strings normally!

 use rsbash::{rashf, RashError};

 pub fn simple_formatting() -> Result<(), RashError> {
     let what = "Hello";
     let who = "world!";
     let (ret_val, stdout, stderr) = rashf!("echo -n '{} {}!'", what, who)?;
     assert_eq!(ret_val, 0);
     assert_eq!(stdout, "Hello world!");
     assert_eq!(stderr, "");
     Ok(())
 }
use rsbash::rashf;
use tempfile::TempDir;

const MESSAGE: &'static str = "Hi from within foo.sh!";

pub fn formatting() -> anyhow::Result<()> {
    let dir = TempDir::new()?;
    let path = dir.path().to_str().unwrap();
    let (ret_val, stdout, stderr) = rashf!(
       "cd {path}; echo -n \"echo -n '{msg}'\" > foo.sh; chmod u+x foo.sh; ./foo.sh;",
       msg = MESSAGE
    )?;

    assert_eq!(ret_val, 0);
    assert_eq!(stdout, MESSAGE);
    assert_eq!(stderr, "");
    Ok(())
}

Compile errors

Passing a non-string literal as an argument:
use rsbash::{rash, RashError};

pub fn wrong_type() -> Result<(), RashError> {
    let (ret_val, stdout, stderr) = rash!(35345)?; // "format argument must be a string literal"
    Ok(())
}
Passing no arguments:
use rsbash::{rash, RashError};

pub fn no_args() -> Result<(), RashError> {
    let (ret_val, stdout, stderr) = rash!()?;     // "requires at least a format string argument"
    Ok(())
}

A word on security

Sometimes the ease and flexibility of bash is exactly what you’re after. But, with great power comes great responsibility, and so I’d be remiss if I wasn’t to mention that formatting bash commands in this manner exposes a vulnerability in the form of a SQL injection-like attack:

use rsbash::{rashf, RashError};

pub fn vulnerability() -> Result<(), RashError> {
    let untrustworthy_user = "";                       // Suppose untrustworthy_user was set to "; reboot;"
    let (ret_val, stdout, stderr) =                    // Uh oh! The command would have been formatted into
        rashf!("echo -n Hello {untrustworthy_user}")?; // "echo -n Hello; reboot";
    Ok(())
}

Of course, best practices such as proper escaping, validating user input and so on would have circumvented the above vulnerability. But, as a general rule only use formatted rashf! commands in situations where you know for certain you can trust the inputs.