oconnor663 / duct.rs

a Rust library for running child processes
MIT License
819 stars 34 forks source link

consider getting rid of `sh` entirely? #40

Closed oconnor663 closed 7 years ago

oconnor663 commented 7 years ago

https://www.reddit.com/r/rust/comments/618jvw/announcing_duct_a_child_process_library_thats/dfdn2m3/

oconnor663 commented 7 years ago

If we believe that sh is too unsafe to keep as is, my next best proposal would've been this: Restrict sh to accept one of two types, either static strings, or regular strings wrapped in some type with a scary name. That would force people to confront the scary name before they do anything wildly unsafe, but would allow the current most common usages with a single hardcoded string.

The main downside I see to that approach is that it won't translate well to other languages. As far as I know, Rust is unique in its ability to tell the difference between a static string and one that's been constructed from user input. (I remember hearing that Ruby has some kind of taint tracking in their strings, though I don't know if that's Ruby proper or actually part of Rails.) I'm not very keen on an API that's tied to Rust's type system.

oconnor663 commented 7 years ago

For the record, the one really good use case of sh I know of (that's not just reducing the number of quotation marks you have to type) is on Windows, where you need to use shell mode to execute anything that isn't a binary or a .bat script. However, the details of how that works are also very tricky. There are several reg keys that control e.g. which Python interpreter gets used. Creating your own .bat wrapper, rather than relying on cmd.exe to find the interpreter for you, is probably a better approach for that case. (We could consider switching to that approach in peru, @olson-sean-k.)

passcod commented 7 years ago

For programs that advertise they're going to run the given command in a shell as a child, this is useful. For example, cargo-watch, which I was in the process of refactoring to use duct when I saw this deprecation pop up.

The one really good use case of sh I know of (that's not just reducing the number of quotation marks you have to type) is on Windows, where you need to use shell mode to execute anything that isn't a binary or a .bat script. However, the details of how that works are also very tricky.

Thus, my use case for sh() is to abstract away the cross-platforms concerns when I specifically want to run a command the user gave me as-is. For Unix, I could of course just rewrite as cmd!("/bin/sh", "-c", command).

My vote on this is to rename, not remove. Alternatively, to extract the start_sh bit into its own tiny crate so it can be used in conjunction with duct, or standalone.

oconnor663 commented 7 years ago

The small separate crate option might make sense, or maybe a submodule. In that thread at the top, @dpc suggested we could provide a shell escape function too, which would be nice to have in a cross platform way. I don't think a function like that quite fits into duct (since the vast majority of use cases really should be using cmd! instead I think), but it could definitely fit into a separate shell utilities crate.

Right now, what sh is doing for you could be totally captured by a single function like this:

fn sh_command_line<T: Into<OsString>>(command: T) -> Vec<OsString> {
    if cfg!(windows) {
        let shell = std::env::var_os("COMSPEC").unwrap_or("cmd.exe".into());
        vec![shell, "/C".into(), command.into()]
    } else {
        vec!["/bin/sh".into(), "-c".into(), command.into()]
    }
}

Would you want to pull in an extra crate to get that function, or is it small enough that it makes more sense to just copy it?

dpc commented 7 years ago

I am all for rename, not a remove. Rust has unsafe for a reason. Just because something is dangerous, doesn't mean there are no legitimate reasons to use it in some circumstances. On reddit I've proposed unescaped_sh, dangerous_sh, unsafe_sh. Putting the risk uprfont in the name should prevent people from being mindless about it.

Shell escaping is another story. It is useful, but is shell-dependant. Eg for bash probably this (might not be Rust syntax):

"'" + str.replace("\n", "'$'\\n''").replace("'",  "'$'\\'''") + "'"

will work as per http://wiki.bash-hackers.org/syntax/quoting#ansi_c_like_strings, but I'm not sure about portable way to do it in sh. Anyway, something to discuss in separate issue.

oconnor663 commented 7 years ago

Ok, here's what I'm leaning towards right now:

oconnor663 commented 7 years ago

Also leaning towards splitting these out into a duct-sh sub-crate. I might not prefer to make them part of the standard cross-language duct API. In Python, for example, you both 1) already have easy access to shell=True and 2) have no way to provide the sh function safely.

oconnor663 commented 7 years ago

Just published a new crate: https://crates.io/crates/duct_sh

Also uploaded version 0.9.0 of duct, which removes the sh function in favor of that crate.