diff --git a/src/cli.rs b/src/cli.rs index 3ef1a7f..c197e59 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -14,6 +14,7 @@ pub struct Args { pub restart: bool, pub debug: bool, pub run_initially: bool, + pub no_shell: bool, pub no_vcs_ignore: bool, pub once: bool, pub poll: bool, @@ -102,6 +103,10 @@ pub fn get_args() -> Args { .help("Forces polling mode") .long("force-poll") .value_name("interval")) + .arg(Arg::with_name("no-shell") + .help("Do not wrap command in 'sh -c'") + .short("n") + .long("no-shell")) .arg(Arg::with_name("once").short("1").hidden(true)) .get_matches(); @@ -164,6 +169,7 @@ pub fn get_args() -> Args { restart: args.is_present("restart"), debug: args.is_present("debug"), run_initially: !args.is_present("postpone"), + no_shell: args.is_present("no-shell"), no_vcs_ignore: args.is_present("no-vcs-ignore"), once: args.is_present("once"), poll: args.occurrences_of("poll") > 0, diff --git a/src/main.rs b/src/main.rs index ed1ec3f..bc5dfeb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,7 +107,7 @@ fn main() { } let mut guard = child_process.write().unwrap(); - *guard = Some(process::spawn(&args.cmd, vec![])); + *guard = Some(process::spawn(&args.cmd, vec![], args.no_shell)); } loop { @@ -140,7 +140,7 @@ fn main() { debug!("Launching child process"); { let mut guard = child_process.write().unwrap(); - *guard = Some(process::spawn(&args.cmd, paths)); + *guard = Some(process::spawn(&args.cmd, paths, args.no_shell)); } } @@ -158,7 +158,7 @@ fn main() { debug!("Launching child process"); { let mut guard = child_process.write().unwrap(); - *guard = Some(process::spawn(&args.cmd, paths)); + *guard = Some(process::spawn(&args.cmd, paths, args.no_shell)); } } @@ -179,7 +179,7 @@ fn main() { debug!("Launching child process"); { let mut guard = child_process.write().unwrap(); - *guard = Some(process::spawn(&args.cmd, paths)); + *guard = Some(process::spawn(&args.cmd, paths, args.no_shell)); } } } diff --git a/src/process.rs b/src/process.rs index c95ffc4..31e9cb1 100644 --- a/src/process.rs +++ b/src/process.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; -pub fn spawn(cmd: &str, updated_paths: Vec) -> Process { - self::imp::Process::new(cmd, updated_paths).expect("unable to spawn process") +pub fn spawn(cmd: &str, updated_paths: Vec, no_shell: bool) -> Process { + self::imp::Process::new(cmd, updated_paths, no_shell).expect("unable to spawn process") } pub use self::imp::Process; @@ -24,13 +24,33 @@ mod imp { #[allow(unknown_lints)] #[allow(mutex_atomic)] impl Process { - pub fn new(cmd: &str, updated_paths: Vec) -> Result { + pub fn new(cmd: &str, updated_paths: Vec, no_shell: bool) -> Result { use nix::unistd::*; use std::io; use std::os::unix::process::CommandExt; - let mut command = Command::new("sh"); - command.arg("-c").arg(cmd); + // Assemble command to run. + // This is either the first argument from cmd (if no_shell was given) or "sh". + // Using "sh -c" gives us features like supportin pipes and redirects, + // but is a little less performant and can cause trouble when using custom signals + // (e.g. --signal SIGHUP) + let mut iter_args = cmd.split_whitespace(); + let arg0 = match no_shell { + true => iter_args.next().unwrap(), + false => "sh", + }; + + // TODO: There might be a better way of doing this with &str. + // I've had to fall back to String, as I wasn't able to join(" ") a Vec<&str> + // into a &str + let args: Vec = match no_shell { + true => iter_args.map(str::to_string).collect(), + false => vec!["-c".to_string(), iter_args.collect::>().join(" ")], + }; + + let mut command = Command::new(arg0); + command.args(args); + debug!("Assembled command {:?}", command); if let Some(single_path) = super::get_single_updated_path(&updated_paths) { command.env("WATCHEXEC_UPDATED_PATH", single_path);