Strip escape markers from expanded patterns in parameter expansion operations
This commit is contained in:
@@ -980,6 +980,11 @@ pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Strip ESCAPE markers from a string, leaving the characters they protect intact.
|
||||||
|
fn strip_escape_markers(s: &str) -> String {
|
||||||
|
s.replace(markers::ESCAPE, "")
|
||||||
|
}
|
||||||
|
|
||||||
/// Processes strings into intermediate representations that are more readable
|
/// Processes strings into intermediate representations that are more readable
|
||||||
/// by the program
|
/// by the program
|
||||||
///
|
///
|
||||||
@@ -1652,7 +1657,7 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
ParamExp::RemShortestPrefix(prefix) => {
|
ParamExp::RemShortestPrefix(prefix) => {
|
||||||
let value = vars.get_var(&var_name);
|
let value = vars.get_var(&var_name);
|
||||||
let unescaped = unescape_str(&prefix);
|
let unescaped = unescape_str(&prefix);
|
||||||
let expanded = expand_raw(&mut unescaped.chars().peekable()).unwrap_or(prefix);
|
let expanded = strip_escape_markers(&expand_raw(&mut unescaped.chars().peekable()).unwrap_or(prefix));
|
||||||
let pattern = Pattern::new(&expanded).unwrap();
|
let pattern = Pattern::new(&expanded).unwrap();
|
||||||
for i in 0..=value.len() {
|
for i in 0..=value.len() {
|
||||||
let sliced = &value[..i];
|
let sliced = &value[..i];
|
||||||
@@ -1665,7 +1670,7 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
ParamExp::RemLongestPrefix(prefix) => {
|
ParamExp::RemLongestPrefix(prefix) => {
|
||||||
let value = vars.get_var(&var_name);
|
let value = vars.get_var(&var_name);
|
||||||
let unescaped = unescape_str(&prefix);
|
let unescaped = unescape_str(&prefix);
|
||||||
let expanded = expand_raw(&mut unescaped.chars().peekable()).unwrap_or(prefix);
|
let expanded = strip_escape_markers(&expand_raw(&mut unescaped.chars().peekable()).unwrap_or(prefix));
|
||||||
let pattern = Pattern::new(&expanded).unwrap();
|
let pattern = Pattern::new(&expanded).unwrap();
|
||||||
for i in (0..=value.len()).rev() {
|
for i in (0..=value.len()).rev() {
|
||||||
let sliced = &value[..i];
|
let sliced = &value[..i];
|
||||||
@@ -1678,7 +1683,7 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
ParamExp::RemShortestSuffix(suffix) => {
|
ParamExp::RemShortestSuffix(suffix) => {
|
||||||
let value = vars.get_var(&var_name);
|
let value = vars.get_var(&var_name);
|
||||||
let unescaped = unescape_str(&suffix);
|
let unescaped = unescape_str(&suffix);
|
||||||
let expanded = expand_raw(&mut unescaped.chars().peekable()).unwrap_or(suffix);
|
let expanded = strip_escape_markers(&expand_raw(&mut unescaped.chars().peekable()).unwrap_or(suffix));
|
||||||
let pattern = Pattern::new(&expanded).unwrap();
|
let pattern = Pattern::new(&expanded).unwrap();
|
||||||
for i in (0..=value.len()).rev() {
|
for i in (0..=value.len()).rev() {
|
||||||
let sliced = &value[i..];
|
let sliced = &value[i..];
|
||||||
@@ -1692,7 +1697,7 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
let value = vars.get_var(&var_name);
|
let value = vars.get_var(&var_name);
|
||||||
let unescaped = unescape_str(&suffix);
|
let unescaped = unescape_str(&suffix);
|
||||||
let expanded_suffix =
|
let expanded_suffix =
|
||||||
expand_raw(&mut unescaped.chars().peekable()).unwrap_or(suffix.clone());
|
strip_escape_markers(&expand_raw(&mut unescaped.chars().peekable()).unwrap_or(suffix.clone()));
|
||||||
let pattern = Pattern::new(&expanded_suffix).unwrap();
|
let pattern = Pattern::new(&expanded_suffix).unwrap();
|
||||||
for i in 0..=value.len() {
|
for i in 0..=value.len() {
|
||||||
let sliced = &value[i..];
|
let sliced = &value[i..];
|
||||||
@@ -1706,8 +1711,8 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
let value = vars.get_var(&var_name);
|
let value = vars.get_var(&var_name);
|
||||||
let search = unescape_str(&search);
|
let search = unescape_str(&search);
|
||||||
let replace = unescape_str(&replace);
|
let replace = unescape_str(&replace);
|
||||||
let expanded_search = expand_raw(&mut search.chars().peekable()).unwrap_or(search);
|
let expanded_search = strip_escape_markers(&expand_raw(&mut search.chars().peekable()).unwrap_or(search));
|
||||||
let expanded_replace = expand_raw(&mut replace.chars().peekable()).unwrap_or(replace);
|
let expanded_replace = strip_escape_markers(&expand_raw(&mut replace.chars().peekable()).unwrap_or(replace));
|
||||||
let regex = glob_to_regex(&expanded_search, false); // unanchored pattern
|
let regex = glob_to_regex(&expanded_search, false); // unanchored pattern
|
||||||
|
|
||||||
if let Some(mat) = regex.find(&value) {
|
if let Some(mat) = regex.find(&value) {
|
||||||
@@ -1723,8 +1728,8 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
let value = vars.get_var(&var_name);
|
let value = vars.get_var(&var_name);
|
||||||
let search = unescape_str(&search);
|
let search = unescape_str(&search);
|
||||||
let replace = unescape_str(&replace);
|
let replace = unescape_str(&replace);
|
||||||
let expanded_search = expand_raw(&mut search.chars().peekable()).unwrap_or(search);
|
let expanded_search = strip_escape_markers(&expand_raw(&mut search.chars().peekable()).unwrap_or(search));
|
||||||
let expanded_replace = expand_raw(&mut replace.chars().peekable()).unwrap_or(replace);
|
let expanded_replace = strip_escape_markers(&expand_raw(&mut replace.chars().peekable()).unwrap_or(replace));
|
||||||
let regex = glob_to_regex(&expanded_search, false);
|
let regex = glob_to_regex(&expanded_search, false);
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
let mut last_match_end = 0;
|
let mut last_match_end = 0;
|
||||||
@@ -1743,8 +1748,8 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
let value = vars.get_var(&var_name);
|
let value = vars.get_var(&var_name);
|
||||||
let search = unescape_str(&search);
|
let search = unescape_str(&search);
|
||||||
let replace = unescape_str(&replace);
|
let replace = unescape_str(&replace);
|
||||||
let expanded_search = expand_raw(&mut search.chars().peekable()).unwrap_or(search);
|
let expanded_search = strip_escape_markers(&expand_raw(&mut search.chars().peekable()).unwrap_or(search));
|
||||||
let expanded_replace = expand_raw(&mut replace.chars().peekable()).unwrap_or(replace);
|
let expanded_replace = strip_escape_markers(&expand_raw(&mut replace.chars().peekable()).unwrap_or(replace));
|
||||||
let pattern = Pattern::new(&expanded_search).unwrap();
|
let pattern = Pattern::new(&expanded_search).unwrap();
|
||||||
for i in (0..=value.len()).rev() {
|
for i in (0..=value.len()).rev() {
|
||||||
let sliced = &value[..i];
|
let sliced = &value[..i];
|
||||||
@@ -1758,8 +1763,8 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
let value = vars.get_var(&var_name);
|
let value = vars.get_var(&var_name);
|
||||||
let search = unescape_str(&search);
|
let search = unescape_str(&search);
|
||||||
let replace = unescape_str(&replace);
|
let replace = unescape_str(&replace);
|
||||||
let expanded_search = expand_raw(&mut search.chars().peekable()).unwrap_or(search);
|
let expanded_search = strip_escape_markers(&expand_raw(&mut search.chars().peekable()).unwrap_or(search));
|
||||||
let expanded_replace = expand_raw(&mut replace.chars().peekable()).unwrap_or(replace);
|
let expanded_replace = strip_escape_markers(&expand_raw(&mut replace.chars().peekable()).unwrap_or(replace));
|
||||||
let pattern = Pattern::new(&expanded_search).unwrap();
|
let pattern = Pattern::new(&expanded_search).unwrap();
|
||||||
for i in (0..=value.len()).rev() {
|
for i in (0..=value.len()).rev() {
|
||||||
let sliced = &value[i..];
|
let sliced = &value[i..];
|
||||||
@@ -1804,6 +1809,11 @@ pub fn expand_case_pattern(raw: &str) -> ShResult<String> {
|
|||||||
markers::DUB_QUOTE | markers::SNG_QUOTE => {
|
markers::DUB_QUOTE | markers::SNG_QUOTE => {
|
||||||
in_quote = !in_quote;
|
in_quote = !in_quote;
|
||||||
}
|
}
|
||||||
|
markers::ESCAPE => {
|
||||||
|
if let Some(next_ch) = chars.next() {
|
||||||
|
result.push(next_ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
'*' | '?' | '[' | ']' if in_quote => {
|
'*' | '?' | '[' | ']' if in_quote => {
|
||||||
result.push('\\');
|
result.push('\\');
|
||||||
result.push(ch);
|
result.push(ch);
|
||||||
@@ -3519,6 +3529,63 @@ mod tests {
|
|||||||
assert_eq!(words, vec!["hello world"]);
|
assert_eq!(words, vec!["hello world"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===================== Escaped Word Splitting =====================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn word_split_escaped_space() {
|
||||||
|
let _guard = TestGuard::new();
|
||||||
|
|
||||||
|
let raw = format!("hello{}world", unescape_str("\\ "));
|
||||||
|
let mut exp = Expander { raw };
|
||||||
|
let words = exp.split_words();
|
||||||
|
assert_eq!(words, vec!["hello world"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn word_split_escaped_tab() {
|
||||||
|
let _guard = TestGuard::new();
|
||||||
|
|
||||||
|
let raw = format!("hello{}world", unescape_str("\\\t"));
|
||||||
|
let mut exp = Expander { raw };
|
||||||
|
let words = exp.split_words();
|
||||||
|
assert_eq!(words, vec!["hello\tworld"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn word_split_escaped_custom_ifs() {
|
||||||
|
let _guard = TestGuard::new();
|
||||||
|
unsafe { std::env::set_var("IFS", ":"); }
|
||||||
|
|
||||||
|
let raw = format!("a{}b:c", unescape_str("\\:"));
|
||||||
|
let mut exp = Expander { raw };
|
||||||
|
let words = exp.split_words();
|
||||||
|
assert_eq!(words, vec!["a:b", "c"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== Parameter Expansion with Escapes (TestGuard) =====================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn param_exp_prefix_removal_escaped() {
|
||||||
|
let guard = TestGuard::new();
|
||||||
|
write_vars(|v| v.set_var("branch", VarKind::Str("## main".into()), VarFlags::NONE)).unwrap();
|
||||||
|
|
||||||
|
test_input("echo \"${branch#\\#\\# }\"").unwrap();
|
||||||
|
|
||||||
|
let out = guard.read_output();
|
||||||
|
assert_eq!(out, "main\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn param_exp_suffix_removal_escaped() {
|
||||||
|
let guard = TestGuard::new();
|
||||||
|
write_vars(|v| v.set_var("val", VarKind::Str("hello world!!".into()), VarFlags::NONE)).unwrap();
|
||||||
|
|
||||||
|
test_input("echo \"${val%\\!\\!}\"").unwrap();
|
||||||
|
|
||||||
|
let out = guard.read_output();
|
||||||
|
assert_eq!(out, "hello world\n");
|
||||||
|
}
|
||||||
|
|
||||||
// ===================== Arithmetic with Variables (TestGuard) =====================
|
// ===================== Arithmetic with Variables (TestGuard) =====================
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user