5 releases

Uses old Rust 2015

0.13.7 Dec 22, 2023
0.13.6 Nov 29, 2022
0.13.5 Feb 4, 2021
0.13.4 Apr 13, 2020
0.1.0 May 30, 2017

#395 in Command-line interface

Download history 192/week @ 2024-07-19 167/week @ 2024-07-26 149/week @ 2024-08-02 121/week @ 2024-08-09 199/week @ 2024-08-16 317/week @ 2024-08-23 123/week @ 2024-08-30 178/week @ 2024-09-06 495/week @ 2024-09-13 4601/week @ 2024-09-20 5806/week @ 2024-09-27 7293/week @ 2024-10-04 8036/week @ 2024-10-11 9257/week @ 2024-10-18 9385/week @ 2024-10-25 11052/week @ 2024-11-01

38,831 downloads per month
Used in 12 crates (11 directly)

MIT license

115KB
1.5K SLoC

duct_sh crates.io docs.rs

A convenience wrapper around the duct crate for building commands out of shell strings. Duct normally executes commands directly, without going through a shell at all. This is preferable where possile, because it avoids tricky whitespace issues and shell injection vulnerabilities. However, sometimes you need access to more esoteric shell features (e.g. shell builtins like source and exec, or process substitution with <()), and other times you're just stuck with a string of shell code that you have to run somehow.

In these situations, duct_sh is a "cross-platform" way to run shell commands, in combination with all the usual features of Duct. "Cross-platform" is in scare quotes, however, because shell code is necessarily platform-specific. The typical Bourne-like shells on Unix and the cmd.exe shell on Windows systems do have a lot in common, including the | and > operators, but they diverge very quickly when you start to look at the details. For that reason, seriously cross-platform programs should avoid shell code as much as possible.

Example

// Execute a static string of shell code.
let output = duct_sh::sh("echo $MSG | sed s/i/a/").env("MSG", "hi").read()?;
assert_eq!(output, "ha");

// Execute a dynamic string of shell code. Note that this requires the
// "sh_dangerous" function. See the Security section below.
let arg = "hi";
let command = format!("echo {} | sed s/i/a/", arg);
let output = duct_sh::sh_dangerous(&command).read()?;
assert_eq!(output, "ha");

Security

The sh function is restricted to static strings. The sh_dangerous function does exactly the same thing, but it accepts dynamic strings. The scary name is because of security issues related to shell injection. Consider this innocent-looking function:

fn echo_string(s: &str) {
    duct_sh::sh_dangerous(format!("echo {}", s)).run().unwrap();
}

This function appears to work when s is a well-behaved string like "foo" or "foo bar". You might notice something wrong if you try "foo   bar", because those three spaces will collapse into one in the output. But the real problem is a string like "; rm -rf /". The resulting command will print a newline and then try to delete your entire hard drive. Any function resembling echo_string, exposed to any kind of untrusted input, becomes a serious security issue.

It's possible to work around these issues by escaping special characters, but escaping is tricky, and it's difficult to test that you've covered every character. Special characters are also platform-specific, making it even harder to get decent test coverage. The difficulty of doing all this correctly will generally outweigh any convenience provided by the duct_sh crate itself.

For these reasons, in addition to the portability concerned discussed above, most programs intended for production should prefer duct over duct_sh. Since duct avoids invoking the shell at all, it isn't usually vulnerable to shell injection.

Dependencies

~0–6.5MB
~37K SLoC