mirror of
https://github.com/watchexec/watchexec.git
synced 2024-09-28 22:21:33 +02:00
Merge pull request #48 from chr4/no_shell
Add support for --no-shell option
This commit is contained in:
commit
eea9428f41
@ -58,9 +58,9 @@ Call/restart `my_server` when any file in the current directory (and all subdire
|
|||||||
|
|
||||||
$ watchexec -r -s SIGKILL my_server
|
$ watchexec -r -s SIGKILL my_server
|
||||||
|
|
||||||
Send a SIGHUP to the child process upon changes:
|
Send a SIGHUP to the child process upon changes (Note: with using `-n | --no-shell` here, we're executing `my_server` directly, instead of wrapping it in a shell:
|
||||||
|
|
||||||
$ watchexec -s SIGHUP my_server
|
$ watchexec -n -s SIGHUP my_server
|
||||||
|
|
||||||
Run `make` when any file changes, using the `.gitignore` file in the current directory to filter:
|
Run `make` when any file changes, using the `.gitignore` file in the current directory to filter:
|
||||||
|
|
||||||
|
@ -34,6 +34,10 @@ Ignores modifications from paths that do not match \fIpattern\fR\. This option c
|
|||||||
Sends the specified signal (e\.g\. \fBSIGKILL\fR) to the child process\. Defaults to \fBSIGTERM\fR\.
|
Sends the specified signal (e\.g\. \fBSIGKILL\fR) to the child process\. Defaults to \fBSIGTERM\fR\.
|
||||||
.
|
.
|
||||||
.TP
|
.TP
|
||||||
|
\fB\-n\fR, \fB\-\-no\-shell\fR
|
||||||
|
Execute command directly, do not wrap it in \fBsh -c\fR resp\. \fBcmd.exec /C\R\. This is especially useful in combination with \fB--signal\fR, as the signal is then send directly to the specified command\. While \fB--no-shell\fR is a little more performant than the default, it prevents using shell-features like pipes and redirects\.
|
||||||
|
.
|
||||||
|
.TP
|
||||||
\fB\-i\fR, \fB\-\-ignore\fR \fIpattern\fR
|
\fB\-i\fR, \fB\-\-ignore\fR \fIpattern\fR
|
||||||
Ignores modifications from paths that match \fIpattern\fR\. This option can be specified multiple times, and a match on any pattern causes the path to be ignored\.
|
Ignores modifications from paths that match \fIpattern\fR\. This option can be specified multiple times, and a match on any pattern causes the path to be ignored\.
|
||||||
.
|
.
|
||||||
|
@ -89,6 +89,7 @@
|
|||||||
<dt><code>-e</code>, <code>--exts</code> <var>extensions</var></dt><dd><p>Comma-separated list of file extensions to filter by. Leading dots are allowed (.rs) are allowed. (This is a shorthand for <code>-f</code>).</p></dd>
|
<dt><code>-e</code>, <code>--exts</code> <var>extensions</var></dt><dd><p>Comma-separated list of file extensions to filter by. Leading dots are allowed (.rs) are allowed. (This is a shorthand for <code>-f</code>).</p></dd>
|
||||||
<dt><code>-f</code>, <code>--filter</code> <var>pattern</var></dt><dd><p>Ignores modifications from paths that do not match <var>pattern</var>. This option can be specified multiple times, where a match on any given pattern causes the path to trigger <var>command</var>.</p></dd>
|
<dt><code>-f</code>, <code>--filter</code> <var>pattern</var></dt><dd><p>Ignores modifications from paths that do not match <var>pattern</var>. This option can be specified multiple times, where a match on any given pattern causes the path to trigger <var>command</var>.</p></dd>
|
||||||
<dt><code>-s</code>, <code>--signal</code> <var>SIGNAL</var></dt><dd><p>Sends the specified signal (e.g. <code>SIGKILl</code>) to the child process. Defaults to <code>SIGTERM</code>.</p></dd>
|
<dt><code>-s</code>, <code>--signal</code> <var>SIGNAL</var></dt><dd><p>Sends the specified signal (e.g. <code>SIGKILl</code>) to the child process. Defaults to <code>SIGTERM</code>.</p></dd>
|
||||||
|
<dt><code>-n</code>, <code>--no-shell</code></dt><dd><p>Execute command directly, do not wrap it in <CODE>sh -c</code> resp. <code>cmd.exec /C</code>. This is especially useful in combination with <code>--signal</code>, as the signal is then send directly to the specified command. While <code>--no-shell</code> is a little more performant than the default, it prevents using shell-features like pipes and redirects.</p></dd>
|
||||||
<dt><code>-i</code>, <code>--ignore</code> <var>pattern</var></dt><dd><p>Ignores modifications from paths that match <var>pattern</var>. This option can be specified multiple times, and a match on any pattern causes the path to be ignored.</p></dd>
|
<dt><code>-i</code>, <code>--ignore</code> <var>pattern</var></dt><dd><p>Ignores modifications from paths that match <var>pattern</var>. This option can be specified multiple times, and a match on any pattern causes the path to be ignored.</p></dd>
|
||||||
<dt><code>-w</code>, <code>--watch</code> <var>path</var></dt><dd><p>Monitor a specific path for changes. By default, the current working directory is watched. This may be specified multiple times, where a change in any watched directory (and subdirectories) causes <var>command</var> to be executed.</p></dd>
|
<dt><code>-w</code>, <code>--watch</code> <var>path</var></dt><dd><p>Monitor a specific path for changes. By default, the current working directory is watched. This may be specified multiple times, where a change in any watched directory (and subdirectories) causes <var>command</var> to be executed.</p></dd>
|
||||||
<dt><code>-r</code>, <code>--restart</code></dt><dd><p>Terminates the child process group if it is still running when subsequent file modifications are detected. By default, sends <code>SIGTERM</code>; use <code>--kill</code> to send <code>SIGKILL</code>.</p></dd>
|
<dt><code>-r</code>, <code>--restart</code></dt><dd><p>Terminates the child process group if it is still running when subsequent file modifications are detected. By default, sends <code>SIGTERM</code>; use <code>--kill</code> to send <code>SIGKILL</code>.</p></dd>
|
||||||
|
@ -25,6 +25,9 @@ Ignores modifications from paths that do not match <pattern>. This option can be
|
|||||||
* `-s`, `--signal`:
|
* `-s`, `--signal`:
|
||||||
Sends the specified signal (e.g. `SIGKILL`) to the child process. Defaults to `SIGTERM`.
|
Sends the specified signal (e.g. `SIGKILL`) to the child process. Defaults to `SIGTERM`.
|
||||||
|
|
||||||
|
* `-n`, `--no-shell`:
|
||||||
|
Execute command directly, do not wrap it in `sh -c` resp. `cmd.exe /C`. This is especially useful in combination with `--signal`, as the signal is then send directly to the specified command. While `--no-shell` is a little more performant than the default, it prevents using shell-features like pipes and redirects.
|
||||||
|
|
||||||
* `-i`, `--ignore` <pattern>:
|
* `-i`, `--ignore` <pattern>:
|
||||||
Ignores modifications from paths that match <pattern>. This option can be specified multiple times, and a match on any pattern causes the path to be ignored.
|
Ignores modifications from paths that match <pattern>. This option can be specified multiple times, and a match on any pattern causes the path to be ignored.
|
||||||
|
|
||||||
|
18
src/cli.rs
18
src/cli.rs
@ -14,6 +14,7 @@ pub struct Args {
|
|||||||
pub restart: bool,
|
pub restart: bool,
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
pub run_initially: bool,
|
pub run_initially: bool,
|
||||||
|
pub no_shell: bool,
|
||||||
pub no_vcs_ignore: bool,
|
pub no_vcs_ignore: bool,
|
||||||
pub once: bool,
|
pub once: bool,
|
||||||
pub poll: bool,
|
pub poll: bool,
|
||||||
@ -102,6 +103,10 @@ pub fn get_args() -> Args {
|
|||||||
.help("Forces polling mode")
|
.help("Forces polling mode")
|
||||||
.long("force-poll")
|
.long("force-poll")
|
||||||
.value_name("interval"))
|
.value_name("interval"))
|
||||||
|
.arg(Arg::with_name("no-shell")
|
||||||
|
.help("Do not wrap command in 'sh -c' resp. 'cmd.exe /C'")
|
||||||
|
.short("n")
|
||||||
|
.long("no-shell"))
|
||||||
.arg(Arg::with_name("once").short("1").hidden(true))
|
.arg(Arg::with_name("once").short("1").hidden(true))
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
@ -111,9 +116,11 @@ pub fn get_args() -> Args {
|
|||||||
let paths = values_t!(args.values_of("path"), String).unwrap_or(vec![String::from(".")]);
|
let paths = values_t!(args.values_of("path"), String).unwrap_or(vec![String::from(".")]);
|
||||||
|
|
||||||
// Treat --kill as --signal SIGKILL (for compatibility with older syntax)
|
// Treat --kill as --signal SIGKILL (for compatibility with older syntax)
|
||||||
let signal = match args.is_present("kill") {
|
let signal = if args.is_present("kill") {
|
||||||
true => Some("SIGKILL".to_string()),
|
Some("SIGKILL".to_string())
|
||||||
false => args.value_of("signal").map(str::to_string), // Convert Option<&str> to Option<String>
|
} else {
|
||||||
|
// Convert Option<&str> to Option<String>
|
||||||
|
args.value_of("signal").map(str::to_string)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut filters = values_t!(args.values_of("filter"), String).unwrap_or(vec![]);
|
let mut filters = values_t!(args.values_of("filter"), String).unwrap_or(vec![]);
|
||||||
@ -144,13 +151,13 @@ pub fn get_args() -> Args {
|
|||||||
|
|
||||||
if signal.is_some() && args.is_present("postpone") {
|
if signal.is_some() && args.is_present("postpone") {
|
||||||
// TODO: Error::argument_conflict() might be the better fit, usage was unclear, though
|
// TODO: Error::argument_conflict() might be the better fit, usage was unclear, though
|
||||||
Error::value_validation_auto(format!("--postpone and --signal are mutually exclusive"))
|
Error::value_validation_auto("--postpone and --signal are mutually exclusive".to_string())
|
||||||
.exit();
|
.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
if signal.is_some() && args.is_present("kill") {
|
if signal.is_some() && args.is_present("kill") {
|
||||||
// TODO: Error::argument_conflict() might be the better fit, usage was unclear, though
|
// TODO: Error::argument_conflict() might be the better fit, usage was unclear, though
|
||||||
Error::value_validation_auto(format!("--kill and --signal is ambiguous.\n Hint: Use only '--signal SIGKILL' without --kill"))
|
Error::value_validation_auto("--kill and --signal is ambiguous.\n Hint: Use only '--signal SIGKILL' without --kill".to_string())
|
||||||
.exit();
|
.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,6 +171,7 @@ pub fn get_args() -> Args {
|
|||||||
restart: args.is_present("restart"),
|
restart: args.is_present("restart"),
|
||||||
debug: args.is_present("debug"),
|
debug: args.is_present("debug"),
|
||||||
run_initially: !args.is_present("postpone"),
|
run_initially: !args.is_present("postpone"),
|
||||||
|
no_shell: args.is_present("no-shell"),
|
||||||
no_vcs_ignore: args.is_present("no-vcs-ignore"),
|
no_vcs_ignore: args.is_present("no-vcs-ignore"),
|
||||||
once: args.is_present("once"),
|
once: args.is_present("once"),
|
||||||
poll: args.occurrences_of("poll") > 0,
|
poll: args.occurrences_of("poll") > 0,
|
||||||
|
@ -107,7 +107,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut guard = child_process.write().unwrap();
|
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 {
|
loop {
|
||||||
@ -140,7 +140,7 @@ fn main() {
|
|||||||
debug!("Launching child process");
|
debug!("Launching child process");
|
||||||
{
|
{
|
||||||
let mut guard = child_process.write().unwrap();
|
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");
|
debug!("Launching child process");
|
||||||
{
|
{
|
||||||
let mut guard = child_process.write().unwrap();
|
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");
|
debug!("Launching child process");
|
||||||
{
|
{
|
||||||
let mut guard = child_process.write().unwrap();
|
let mut guard = child_process.write().unwrap();
|
||||||
*guard = Some(process::spawn(&args.cmd, paths));
|
*guard = Some(process::spawn(&args.cmd, paths, args.no_shell));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub fn spawn(cmd: &str, updated_paths: Vec<PathBuf>) -> Process {
|
pub fn spawn(cmd: &str, updated_paths: Vec<PathBuf>, no_shell: bool) -> Process {
|
||||||
self::imp::Process::new(cmd, updated_paths).expect("unable to spawn process")
|
self::imp::Process::new(cmd, updated_paths, no_shell).expect("unable to spawn process")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use self::imp::Process;
|
pub use self::imp::Process;
|
||||||
@ -24,13 +24,28 @@ mod imp {
|
|||||||
#[allow(unknown_lints)]
|
#[allow(unknown_lints)]
|
||||||
#[allow(mutex_atomic)]
|
#[allow(mutex_atomic)]
|
||||||
impl Process {
|
impl Process {
|
||||||
pub fn new(cmd: &str, updated_paths: Vec<PathBuf>) -> Result<Process> {
|
pub fn new(cmd: &str, updated_paths: Vec<PathBuf>, no_shell: bool) -> Result<Process> {
|
||||||
use nix::unistd::*;
|
use nix::unistd::*;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::os::unix::process::CommandExt;
|
use std::os::unix::process::CommandExt;
|
||||||
|
|
||||||
let mut command = Command::new("sh");
|
// Assemble command to run.
|
||||||
command.arg("-c").arg(cmd);
|
// 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 command = if no_shell {
|
||||||
|
let mut split = cmd.split_whitespace();
|
||||||
|
let mut command = Command::new(split.next().unwrap());
|
||||||
|
command.args(split);
|
||||||
|
command
|
||||||
|
} else {
|
||||||
|
let mut command = Command::new("sh");
|
||||||
|
command.arg("-c").arg(cmd);
|
||||||
|
command
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("Assembled command {:?}", command);
|
||||||
|
|
||||||
if let Some(single_path) = super::get_single_updated_path(&updated_paths) {
|
if let Some(single_path) = super::get_single_updated_path(&updated_paths) {
|
||||||
command.env("WATCHEXEC_UPDATED_PATH", single_path);
|
command.env("WATCHEXEC_UPDATED_PATH", single_path);
|
||||||
@ -119,7 +134,7 @@ mod imp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Process {
|
impl Process {
|
||||||
pub fn new(cmd: &str, updated_paths: Vec<PathBuf>) -> Result<Process> {
|
pub fn new(cmd: &str, updated_paths: Vec<PathBuf>, no_shell: bool) -> Result<Process> {
|
||||||
use std::os::windows::io::IntoRawHandle;
|
use std::os::windows::io::IntoRawHandle;
|
||||||
|
|
||||||
fn last_err() -> io::Error {
|
fn last_err() -> io::Error {
|
||||||
@ -143,8 +158,23 @@ mod imp {
|
|||||||
panic!("failed to set job info: {}", last_err());
|
panic!("failed to set job info: {}", last_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut command = Command::new("cmd.exe");
|
let mut iter_args = cmd.split_whitespace();
|
||||||
command.arg("/C").arg(cmd);
|
let arg0 = match no_shell {
|
||||||
|
true => iter_args.next().unwrap(),
|
||||||
|
false => "cmd.exe",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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<String> = match no_shell {
|
||||||
|
true => iter_args.map(str::to_string).collect(),
|
||||||
|
false => vec!["/C".to_string(), iter_args.collect::<Vec<&str>>().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) {
|
if let Some(single_path) = super::get_single_updated_path(&updated_paths) {
|
||||||
command.env("WATCHEXEC_UPDATED_PATH", single_path);
|
command.env("WATCHEXEC_UPDATED_PATH", single_path);
|
||||||
@ -241,7 +271,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_start() {
|
fn test_start() {
|
||||||
let _ = spawn("echo hi", vec![]);
|
let _ = spawn("echo hi", vec![], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
Loading…
Reference in New Issue
Block a user