diff --git a/examples/command-substitutions.ion b/examples/command-substitutions.ion index 20f7da67f0bc581a543d0ef80296ef6eff2a816c..866c4031b41410a262d39da9cf71a54ecda83788 100644 --- a/examples/command-substitutions.ion +++ b/examples/command-substitutions.ion @@ -1 +1,5 @@ echo $(echo "one two three" "four 'five' six") +echo 0 "$(echo -e ' one two three ')" 1 +echo 0 "$(echo -e ' one\ntwo\nthree ')" 1 +echo 0 $(echo -e ' one two three ') 1 +echo 0 $(echo -e ' one\ntwo\nthree ') 1 diff --git a/examples/command-substitutions.out b/examples/command-substitutions.out index 2159bdc89e9508b16606166a30b0b05452ea6c02..76ca69dd938838cc9e13bacd529d0e325ac9199a 100644 --- a/examples/command-substitutions.out +++ b/examples/command-substitutions.out @@ -1 +1,7 @@ -one two three four 'five' six +one two three four 'five' six +0 one two three 1 +0 one +two +three 1 +0 one two three 1 +0 one two three 1 diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 4303bb34a4e4d82faeaf27224bf10e2ccd73cc57..4746b825374271103e52641bb9ca2f1afea30ff7 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -20,8 +20,8 @@ use self::source::source; use self::test::test; use self::variables::{alias, drop_alias, drop_array, drop_variable}; -use std::error::Error; use std::env; +use std::error::Error; use std::io::{self, Write}; use std::path::Path; @@ -396,14 +396,12 @@ fn builtin_which(args: &[&str], shell: &mut Shell) -> i32 { println!("{}: function", command); SUCCESS } else { - for path in env::var("PATH").unwrap_or("/bin".to_string()) - .split(sys::PATH_SEPARATOR) { + for path in env::var("PATH").unwrap_or("/bin".to_string()).split(sys::PATH_SEPARATOR) { let executable = Path::new(path).join(command); if executable.is_file() { println!("{}", executable.display()); return SUCCESS; } - } println!("{} not found", command); diff --git a/src/parser/shell_expand/mod.rs b/src/parser/shell_expand/mod.rs index ab8892f887884e898db5d5a75011a4ee7050a43f..9ea965e0747efb51014bece74dbb52ee7f721e8b 100644 --- a/src/parser/shell_expand/mod.rs +++ b/src/parser/shell_expand/mod.rs @@ -10,6 +10,7 @@ use self::braces::BraceToken; use self::ranges::parse_range; pub(crate) use self::words::{Index, Range, Select, WordIterator, WordToken}; use glob::glob; +use std::str; use types::*; use unicode_segmentation::UnicodeSegmentation; @@ -37,22 +38,28 @@ fn expand_process<E: Expander>( current: &mut String, command: &str, selection: Select, - expand_func: &E, + expander: &E, + quoted: bool, ) { - let mut tokens = Vec::new(); - let mut contains_brace = false; - - for token in WordIterator::new(command, false, expand_func) { - if let WordToken::Brace(_) = token { - contains_brace = true; + if let Some(output) = expander.command(command) { + if quoted { + let output: &str = if let Some(pos) = output.rfind(|x| x != '\n') { + &output[..pos + 1] + } else { + &output + }; + slice(current, output, selection) + } else { + // TODO: Complete this so that we don't need any heap allocations. + // All that we need is to shift bytes to the left when extra spaces are found. + // + // unsafe { + // let bytes: &mut [u8] = output.as_bytes_mut(); + // bytes.iter_mut().filter(|b| **b == b'\n').for_each(|b| *b = b' '); + // slice(current, str::from_utf8_unchecked(&bytes).trim(), selection) + // } + slice(current, &output.split_whitespace().collect::<Vec<&str>>().join(" "), selection) } - tokens.push(token); - } - - let expanded = expand_tokens(&tokens, expand_func, false, contains_brace).join(" "); - - if let Some(result) = expand_func.command(&expanded) { - slice(current, result, selection); } } @@ -220,24 +227,24 @@ pub(crate) fn expand_tokens<E: Expander>( Select::None => (), Select::All => { let mut temp = String::new(); - expand_process(&mut temp, command, Select::All, expand_func); + expand_process(&mut temp, command, Select::All, expand_func, false); let temp = temp.split_whitespace().collect::<Vec<&str>>(); output.push_str(&temp.join(" ")); } Select::Index(Index::Forward(id)) => { let mut temp = String::new(); - expand_process(&mut temp, command, Select::All, expand_func); + expand_process(&mut temp, command, Select::All, expand_func, false); output.push_str(temp.split_whitespace().nth(id).unwrap_or_default()); } Select::Index(Index::Backward(id)) => { let mut temp = String::new(); - expand_process(&mut temp, command, Select::All, expand_func); + expand_process(&mut temp, command, Select::All, expand_func, false); output .push_str(temp.split_whitespace().rev().nth(id).unwrap_or_default()); } Select::Range(range) => { let mut temp = String::new(); - expand_process(&mut temp, command, Select::All, expand_func); + expand_process(&mut temp, command, Select::All, expand_func, false); let len = temp.split_whitespace().count(); if let Some((start, length)) = range.bounds(len) { let res = temp.split_whitespace() @@ -264,8 +271,9 @@ pub(crate) fn expand_tokens<E: Expander>( reverse_quoting, ), WordToken::Whitespace(whitespace) => output.push_str(whitespace), - WordToken::Process(command, _, ref index) => { - expand_process(&mut output, command, index.clone(), expand_func); + WordToken::Process(command, quoted, ref index) => { + let quoted = if reverse_quoting { !quoted } else { quoted }; + expand_process(&mut output, command, index.clone(), expand_func, quoted); } WordToken::Variable(text, quoted, ref index) => { let quoted = if reverse_quoting { !quoted } else { quoted }; @@ -312,11 +320,11 @@ pub(crate) fn expand_tokens<E: Expander>( WordToken::ArrayProcess(command, _, ref index) => match *index { Select::None => return Array::new(), Select::All => { - expand_process(&mut output, command, Select::All, expand_func); + expand_process(&mut output, command, Select::All, expand_func, false); return output.split_whitespace().map(From::from).collect::<Array>(); } Select::Index(Index::Forward(id)) => { - expand_process(&mut output, command, Select::All, expand_func); + expand_process(&mut output, command, Select::All, expand_func, false); return output .split_whitespace() .nth(id) @@ -325,7 +333,7 @@ pub(crate) fn expand_tokens<E: Expander>( .collect(); } Select::Index(Index::Backward(id)) => { - expand_process(&mut output, command, Select::All, expand_func); + expand_process(&mut output, command, Select::All, expand_func, false); return output .split_whitespace() .rev() @@ -335,7 +343,7 @@ pub(crate) fn expand_tokens<E: Expander>( .collect(); } Select::Range(range) => { - expand_process(&mut output, command, Select::All, expand_func); + expand_process(&mut output, command, Select::All, expand_func, false); if let Some((start, length)) = range.bounds(output.split_whitespace().count()) { @@ -372,23 +380,23 @@ pub(crate) fn expand_tokens<E: Expander>( Select::None => (), Select::All => { let mut temp = String::new(); - expand_process(&mut temp, command, Select::All, expand_func); + expand_process(&mut temp, command, Select::All, expand_func, false); let temp = temp.split_whitespace().collect::<Vec<&str>>(); output.push_str(&temp.join(" ")); } Select::Index(Index::Forward(id)) => { let mut temp = String::new(); - expand_process(&mut temp, command, Select::All, expand_func); + expand_process(&mut temp, command, Select::All, expand_func, false); output.push_str(temp.split_whitespace().nth(id).unwrap_or_default()); } Select::Index(Index::Backward(id)) => { let mut temp = String::new(); - expand_process(&mut temp, command, Select::All, expand_func); + expand_process(&mut temp, command, Select::All, expand_func, false); output.push_str(temp.split_whitespace().rev().nth(id).unwrap_or_default()); } Select::Range(range) => { let mut temp = String::new(); - expand_process(&mut temp, command, Select::All, expand_func); + expand_process(&mut temp, command, Select::All, expand_func, false); if let Some((start, length)) = range.bounds(temp.split_whitespace().count()) { let temp = temp.split_whitespace() @@ -413,8 +421,9 @@ pub(crate) fn expand_tokens<E: Expander>( WordToken::Whitespace(text) => { output.push_str(text); } - WordToken::Process(command, _, ref index) => { - expand_process(&mut output, command, index.clone(), expand_func); + WordToken::Process(command, quoted, ref index) => { + let quoted = if reverse_quoting { !quoted } else { quoted }; + expand_process(&mut output, command, index.clone(), expand_func, quoted); } WordToken::Variable(text, quoted, ref index) => { let quoted = if reverse_quoting { !quoted } else { quoted }; diff --git a/src/shell/job.rs b/src/shell/job.rs index 89858e9299b7007ef7ec6efd796b18041875fcef..1114933d13910b9234024bdf642b0f5d448b7fc7 100644 --- a/src/shell/job.rs +++ b/src/shell/job.rs @@ -321,11 +321,6 @@ impl RefinedJob { #[cfg(test)] mod tests { use super::*; - use parser::Expander; - - struct Empty; - - impl Expander for Empty {} #[test] fn preserve_empty_arg() { diff --git a/src/shell/mod.rs b/src/shell/mod.rs index ee7e8e617e198d0efdfc7f43b8d1f316e9c33419..40ec69f97b5e471342cac6b5423648bd4c782ad7 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -42,6 +42,7 @@ use std::fs::File; use std::io::{self, Write}; use std::ops::Deref; use std::process; +use std::ptr; use std::sync::{Arc, Mutex}; use std::sync::atomic::Ordering; use std::time::SystemTime; @@ -89,6 +90,8 @@ pub struct Shell { /// Stores the patterns used to determine whether a command should be saved in the history /// or not ignore_setting: IgnoreSetting, + /// A pointer to itself which should only be used when performing a subshell expansion. + pointer: *mut Shell, } impl<'a> Shell { @@ -112,6 +115,7 @@ impl<'a> Shell { break_flow: false, foreground_signals: Arc::new(ForegroundSignals::new()), ignore_setting: IgnoreSetting::default(), + pointer: ptr::null_mut(), } } @@ -134,6 +138,7 @@ impl<'a> Shell { break_flow: false, foreground_signals: Arc::new(ForegroundSignals::new()), ignore_setting: IgnoreSetting::default(), + pointer: ptr::null_mut(), } } @@ -207,6 +212,14 @@ impl<'a> Shell { /// To avoid infinite recursion when using aliases, the noalias boolean will be set the true /// if an alias branch was executed. fn run_pipeline(&mut self, pipeline: &mut Pipeline) -> Option<i32> { + // TODO: Find a way to only need to execute this once, without + // complicating our public API. + // + // Ensure that the shell pointer is set before executing. + // This is needed for subprocess expansions to function. + let pointer = self as *mut Shell; + self.pointer = pointer; + let command_start_time = SystemTime::now(); let builtins = self.builtins; @@ -367,6 +380,56 @@ impl<'a> Expander for Shell { self.variables.get_var(variable).map(|x| x.ascii_replace('\n', ' ').into()) } } - /// Expand a subshell expression - fn command(&self, command: &str) -> Option<Value> { self.variables.command_expansion(command) } + /// Uses a subshell to expand a given command. + fn command(&self, command: &str) -> Option<Value> { + use std::io::Read; + use std::os::unix::io::{AsRawFd, FromRawFd}; + use std::process::exit; + use sys; + + let (mut out_read, out_write) = match sys::pipe2(sys::O_CLOEXEC) { + Ok(fds) => unsafe { (File::from_raw_fd(fds.0), File::from_raw_fd(fds.1)) }, + Err(why) => { + eprintln!("ion: unable to create pipe: {}", why); + return None; + } + }; + + match unsafe { sys::fork() } { + Ok(0) => { + // TODO: Figure out how to properly enable stdin in the child. + // Without this line, the parent will hang. Can test with: + // echo $(read x) + sys::close_stdin(); + + // Redirect stdout in the child to the write end of the pipe. + // Also close the read end of the pipe because we don't need it. + let _ = sys::dup2(out_write.as_raw_fd(), sys::STDOUT_FILENO); + drop(out_write); + drop(out_read); + + // Now obtain ownership of the child's shell through a mutable pointer, + // and then use that shell to execute the command. + let shell: &mut Shell = unsafe { &mut *self.pointer }; + shell.on_command(command); + + // Reap the child, enabling the parent to get EOF from the read end of the pipe. + exit(0); + } + Ok(_pid) => { + // Drop the write end of the pipe, because the parent will not use it. + drop(out_write); + + // Read from the read end of the pipe into a String. + let mut output = String::new(); + let _ = out_read.read_to_string(&mut output); + + Some(output) + } + Err(why) => { + eprintln!("ion: fork error: {}", why); + None + } + } + } } diff --git a/src/shell/variables/mod.rs b/src/shell/variables/mod.rs index aca03d0a93f2932d59dff639b0031a440582f383..5df7d4486ceeef61c4afd843fffe3cee21c93295 100644 --- a/src/shell/variables/mod.rs +++ b/src/shell/variables/mod.rs @@ -8,7 +8,6 @@ use fnv::FnvHashMap; use liner::Context; use std::env; use std::io::{self, BufRead}; -use std::process; use sys::{self, getpid, is_root}; use sys::variables as self_sys; use types::{ @@ -336,23 +335,6 @@ impl Variables { None } - #[allow(dead_code)] - pub(crate) fn command_expansion(&self, command: &str) -> Option<Value> { - if let Ok(exe) = env::current_exe() { - if let Ok(output) = process::Command::new(exe).arg("-c").arg(command).output() { - if let Ok(mut stdout) = String::from_utf8(output.stdout) { - if stdout.ends_with('\n') { - stdout.pop(); - } - - return Some(stdout.into()); - } - } - } - - None - } - #[allow(dead_code)] pub(crate) fn is_hashmap_reference(key: &str) -> Option<(Identifier, Key)> { let mut key_iter = key.split('[');