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.