1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
//! The `sh` function was originally included in the `duct` library itself.
//! Because of safety concerns, we decided to split that function into `sh`
//! (taking only static strings) and `sh_dangerous` (taking any string). That in
//! turn raised a portability problem: duct is a
//! [multi-language](https://github.com/oconnor663/duct.py) library, and most
//! languages can't distinguish string lifetimes the way that Rust can. As an
//! experiment, we're splitting these functions into their own crate, with the
//! intention of keeping them Rust-only.
//!
//! I don't know if we'll keep this arrangement, but here are some other
//! points in favor:
//!
//! - Simple `sh` commands are nice when you first write them, but it feels
//!   annoying to rewrite them as `cmd!` once you need to introduce a variable
//!   somewhere. That annoyance is pressure to do something evil.
//! - Legitimate use cases for `sh_dangerous` in small scripts is rare. The
//!   common use case is something like a build tool, where the user is expected
//!   to run arbitrary shell commands through the tool. Importing an extra crate
//!   (or just copying the whole function) is less of a big deal in the source
//!   code of a build tool than it is in a small script.
//! - Many languages have standard library support for launching shell commands.
//!   Rust doesn't, and so `duct_sh` is more valuable in Rust than it would by
//!   in say Python.

extern crate duct;

use std::ffi::OsString;

/// Create a command from a static string of shell code.
///
/// This invokes the operating system's shell to execute the string: `/bin/sh`
/// on Unix-like systems and `cmd.exe` (or `%COMSPEC%`) on Windows. This can be
/// very convenient sometimes, especially in small scripts and examples. You
/// don't need to quote each argument, and all the operators like `|` and `>`
/// work as usual.
///
/// `sh` avoids security issues by accepting only static strings. If you need to
/// build shell commands at runtime, read the documentation for `sh_dangerous`.
///
/// # Example
///
/// ```
/// use duct_sh::sh;
///
/// let output = sh("echo foo bar baz").read();
///
/// assert_eq!("foo bar baz", output.unwrap());
/// ```
pub fn sh(command: &'static str) -> duct::Expression {
    let argv = shell_command_argv(command.into());
    duct::cmd(&argv[0], &argv[1..])
}

/// Create a command from any string of shell code. This works like `sh`, but
/// it's not limited to static strings.
///
/// # Warning
///
/// Building shell commands out of user input raises serious security problems,
/// in addition to ordinary whitespace and escaping issues, so this function has
/// a scary name. If someone sneaks an argument like `$(evil_command.sh)` into
/// your shell string, you will execute the evil command without meaning to.
/// Shell escaping is tricky and platform-dependent, and using `duct::cmd!` is
/// _much_ safer when it's an option.
///
/// # Example
///
/// ```
/// use duct_sh::sh_dangerous;
///
/// let my_command = "echo".to_string() + " foo bar baz";
/// let output = sh_dangerous(my_command).read();
///
/// assert_eq!("foo bar baz", output.unwrap());
/// ```
pub fn sh_dangerous<T: Into<OsString>>(command: T) -> duct::Expression {
    let argv = shell_command_argv(command.into());
    duct::cmd(&argv[0], &argv[1..])
}

#[cfg(unix)]
fn shell_command_argv(command: OsString) -> Vec<OsString> {
    vec!["/bin/sh".into(), "-c".into(), command]
}

#[cfg(windows)]
fn shell_command_argv(command: OsString) -> Vec<OsString> {
    let comspec = std::env::var_os("COMSPEC").unwrap_or_else(|| "cmd.exe".into());
    vec![comspec, "/C".into(), command]
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_sh() {
        let out = ::sh("echo hi").read().unwrap();
        assert_eq!("hi", out);
    }

    #[test]
    fn test_sh_dangerous() {
        let out = ::sh_dangerous("echo hi".to_owned()).read().unwrap();
        assert_eq!("hi", out);
    }
}