summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Aaron Murphy <mmstickman@gmail.com>2016-09-01 23:28:07 -0400
committerMichael Aaron Murphy <mmstickman@gmail.com>2016-09-01 23:28:07 -0400
commit1468e2f935a39b042f5eceb42597a84e4eb915c0 (patch)
treed2927d913bc27c3ead9b7c206c57a832e85bd778
parent7309739c41cfbc80c42eb037efdad82143867860 (diff)
0.2.1: Parse Syntax with --no-shell0.2.1
This change will allow the --no-shell mode to correctly parse arguments that contain spaces, so long as the argument is inbetween double quotes or spaces follow a backslash.
-rw-r--r--README.md15
-rw-r--r--src/command.rs113
2 files changed, 111 insertions, 17 deletions
diff --git a/README.md b/README.md
index aed18bd..fe4e114 100644
--- a/README.md
+++ b/README.md
@@ -92,6 +92,7 @@ In addition to the command syntax, there are also some options that you can use
- **-j**, **--jobs**: Defines the number of jobs/threads to run in parallel.
- **-u**, **--ungroup**: By default, stdout/stderr buffers are grouped in the order that they are received.
- **-n**, **--no-shell**: Disables executing commands within the platform's shell for a performance boost.
+ - Double quotes and backslashes are used to allow spaces in inputs, similar to standard shells.
- **-v**, **--verbose**: Prints information about running processes.
- **--num-cpu-cores**: Prints the number of CPU cores in the system and exits.
@@ -161,18 +162,20 @@ parallel 'echo {}' ::: *
There are a number of methods that you can use to install the application. I provide binary packages for AMD64 systems
that are available for download:
+### Gentoo
+
+I have a [personal Gentoo layman overlay](https://github.com/mmstick/mmstick-overlay) that provides this application for installation.
+
### Ubuntu
-```sh
-wget https://github.com/mmstick/parallel/releases/download/0.2.0/parallel_0.2.0_amd64.deb
-sudo dpkg -i parallel_0.2.0_amd64.deb
-```
+Debian packages are provided on the [releases page](https://github.com/mmstick/parallel/releases).
+If a release is not available, it's because I haven't built it yet with cargo deb.
### Everyone Else
```sh
-wget https://github.com/mmstick/parallel/releases/download/0.2.0/parallel_0.2.0_amd64.tar.xz
-tar xf parallel.tar.xz
+wget https://github.com/mmstick/parallel/releases/download/0.2.1/parallel_0.2.1_amd64.tar.xz
+tar xf parallel_0.2.1.tar.xz
sudo install parallel /usr/local/bin
```
diff --git a/src/command.rs b/src/command.rs
index 926e11e..b713bc6 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -69,10 +69,8 @@ pub fn get_command_output(command: &str, uses_shell: bool, child: &mut Child)
if uses_shell {
shell_output(command, child)
} else {
- let mut iter = command.split_whitespace();
- let command = iter.next().unwrap();
- let args = iter.collect::<Vec<&str>>();
- Command::new(command).args(&args)
+ let arguments = split_into_args(command);
+ Command::new(&arguments[0]).args(&arguments[1..])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn().map(|process| {
@@ -82,14 +80,95 @@ pub fn get_command_output(command: &str, uses_shell: bool, child: &mut Child)
}
}
+/// Handles quoting of arguments to prevent arguments with spaces as being read as
+/// multiple separate arguments. This is only executed when `--no-shell` is in use.
+fn split_into_args(command: &str) -> Vec<String> {
+ let mut output = Vec::new();
+ let mut buffer = String::new();
+ let mut quoted = false;
+ let mut prev_char_was_a_backslash = false;
+ for character in command.chars() {
+ if quoted {
+ match character {
+ '\\' => {
+ if prev_char_was_a_backslash {
+ buffer.push('\\');
+ prev_char_was_a_backslash = false;
+ } else {
+ prev_char_was_a_backslash = true;
+ }
+ },
+ '"' => {
+ if prev_char_was_a_backslash {
+ buffer.push('\\');
+ prev_char_was_a_backslash = false;
+ } else {
+ if !buffer.is_empty() {
+ output.push(buffer.clone());
+ buffer.clear();
+ }
+ quoted = false;
+ }
+ },
+ _ => {
+ if prev_char_was_a_backslash {
+ buffer.push('\\');
+ prev_char_was_a_backslash = false;
+ }
+ buffer.push(character);
+ }
+ }
+ } else {
+ match character {
+ ' ' => {
+ if prev_char_was_a_backslash {
+ buffer.push(' ');
+ prev_char_was_a_backslash = false;
+ } else {
+ if !buffer.is_empty() {
+ output.push(buffer.clone());
+ buffer.clear();
+ }
+ }
+ },
+ '\\' => {
+ if prev_char_was_a_backslash {
+ buffer.push('\\');
+ prev_char_was_a_backslash = false;
+ } else {
+ prev_char_was_a_backslash = true;
+ }
+ },
+ '"' => {
+ if prev_char_was_a_backslash {
+ buffer.push('"');
+ prev_char_was_a_backslash = false;
+ } else {
+ quoted = true;
+ }
+ },
+ _ => {
+ if prev_char_was_a_backslash {
+ buffer.push('\\');
+ prev_char_was_a_backslash = false;
+ } else {
+ buffer.push(character);
+ }
+ }
+ }
+ }
+ }
+ output
+}
+
pub fn get_command_status(command: &str, uses_shell: bool) -> io::Result<ExitStatus> {
if uses_shell {
shell_status(command)
} else {
let mut iter = command.split_whitespace();
let command = iter.next().unwrap();
- let args = iter.collect::<Vec<&str>>();
- Command::new(command).args(&args).status()
+ let arguments = split_into_args(command);
+ Command::new(&arguments[0]).args(&arguments[1..]).status()
}
}
@@ -202,14 +281,26 @@ fn path_dirname_empty() {
#[test]
fn build_arguments_test() {
- let input = "applesauce.mp4";
- let job = "1";
- let slot = "1";
- let total = "1";
let tokens = vec![Token::Argument("-i ".to_owned()), Token::Placeholder,
Token::Argument(" ".to_owned()), Token::RemoveExtension,
Token::Argument(".mkv".to_owned())];
+
+ let command = ParallelCommand {
+ slot_no: "1",
+ job_no: "1",
+ job_total: "1",
+ input: "applesauce.mp4",
+ command_template: &tokens,
+ };
+
let mut arguments = String::new();
- build_arguments(&mut arguments, &tokens, input, slot, job, total);
+ command.build_arguments(&mut arguments);
assert_eq!(arguments, String::from("-i applesauce.mp4 applesauce.mkv"))
}
+
+#[test]
+fn test_split_args() {
+ let argument = "ffmpeg -i \"file with spaces\" \"output with spaces\"";
+ let expected = vec!["ffmpeg", "-i", "file with spaces", "output with spaces"];
+ assert_eq!(split_into_args(argument), expected)
+}