1use crate::error::RustBashError;
5use crate::interpreter::pattern;
6use crate::interpreter::walker::{clone_commands, execute_program};
7use crate::interpreter::{
8 ExecutionCounters, InterpreterState, next_random, parse, parser_options, set_variable,
9};
10
11use crate::vfs::GlobOptions;
12use brush_parser::ast;
13use brush_parser::word::{
14 Parameter, ParameterExpr, ParameterTestType, SpecialParameter, SubstringMatchKind, WordPiece,
15};
16use std::collections::HashMap;
17
18#[derive(Debug, Clone)]
22struct Segment {
23 text: String,
24 quoted: bool,
27 glob_protected: bool,
31}
32
33type WordInProgress = Vec<Segment>;
35
36pub fn expand_word(
45 word: &ast::Word,
46 state: &InterpreterState,
47) -> Result<Vec<String>, RustBashError> {
48 let brace_expanded =
50 crate::interpreter::brace::brace_expand(&word.value, state.limits.max_brace_expansion)?;
51
52 let mut all_results = Vec::new();
53 for raw in &brace_expanded {
54 let sub_word = ast::Word {
55 value: raw.clone(),
56 loc: word.loc.clone(),
57 };
58 let words = expand_word_segments(&sub_word, state)?;
59 let split = finalize_with_ifs_split(words, state);
60 let expanded = glob_expand_words(split, state)?;
61 all_results.extend(expanded);
62 }
63 Ok(all_results)
64}
65
66pub(crate) fn expand_word_mut(
68 word: &ast::Word,
69 state: &mut InterpreterState,
70) -> Result<Vec<String>, RustBashError> {
71 let brace_expanded =
72 crate::interpreter::brace::brace_expand(&word.value, state.limits.max_brace_expansion)?;
73
74 let mut all_results = Vec::new();
75 for raw in &brace_expanded {
76 let sub_word = ast::Word {
77 value: raw.clone(),
78 loc: word.loc.clone(),
79 };
80 let words = expand_word_segments_mut(&sub_word, state)?;
81 let split = finalize_with_ifs_split(words, state);
82 let expanded = glob_expand_words(split, state)?;
83 all_results.extend(expanded);
84 }
85 Ok(all_results)
86}
87
88pub(crate) fn expand_word_to_string_mut(
95 word: &ast::Word,
96 state: &mut InterpreterState,
97) -> Result<String, RustBashError> {
98 let words = expand_word_segments_mut(word, state)?;
99 let result = finalize_no_split(words);
100 let joined = result.join(" ");
101 if joined.len() > state.limits.max_string_length {
102 return Err(RustBashError::LimitExceeded {
103 limit_name: "max_string_length",
104 limit_value: state.limits.max_string_length,
105 actual_value: joined.len(),
106 });
107 }
108 Ok(joined)
109}
110
111fn expand_word_segments(
114 word: &ast::Word,
115 state: &InterpreterState,
116) -> Result<Vec<WordInProgress>, RustBashError> {
117 let options = parser_options();
118 let pieces = brush_parser::word::parse(&word.value, &options)
119 .map_err(|e| RustBashError::Parse(e.to_string()))?;
120
121 let mut words: Vec<WordInProgress> = vec![Vec::new()];
122 for piece_ws in &pieces {
123 expand_word_piece(&piece_ws.piece, &mut words, state, false)?;
124 }
125 Ok(words)
126}
127
128fn expand_word_segments_mut(
129 word: &ast::Word,
130 state: &mut InterpreterState,
131) -> Result<Vec<WordInProgress>, RustBashError> {
132 let options = parser_options();
133 let pieces = brush_parser::word::parse(&word.value, &options)
134 .map_err(|e| RustBashError::Parse(e.to_string()))?;
135
136 let mut words: Vec<WordInProgress> = vec![Vec::new()];
137 for piece_ws in &pieces {
138 expand_word_piece_mut(&piece_ws.piece, &mut words, state, false)?;
139 }
140 Ok(words)
141}
142
143fn push_segment(words: &mut Vec<WordInProgress>, text: &str, quoted: bool, glob_protected: bool) {
150 if text.is_empty() && !quoted {
151 return;
152 }
153 if words.is_empty() {
154 words.push(Vec::new());
155 }
156 let word = words.last_mut().unwrap();
157 if let Some(last) = word.last_mut()
158 && last.quoted == quoted
159 && last.glob_protected == glob_protected
160 {
161 last.text.push_str(text);
162 return;
163 }
164 word.push(Segment {
165 text: text.to_string(),
166 quoted,
167 glob_protected,
168 });
169}
170
171fn start_new_word(words: &mut Vec<WordInProgress>) {
173 words.push(Vec::new());
174}
175
176fn execute_command_substitution(
179 cmd_str: &str,
180 state: &mut InterpreterState,
181) -> Result<String, RustBashError> {
182 state.counters.substitution_depth += 1;
183 if state.counters.substitution_depth > state.limits.max_substitution_depth {
184 let actual = state.counters.substitution_depth;
185 state.counters.substitution_depth -= 1;
186 return Err(RustBashError::LimitExceeded {
187 limit_name: "max_substitution_depth",
188 limit_value: state.limits.max_substitution_depth,
189 actual_value: actual,
190 });
191 }
192
193 let program = match parse(cmd_str) {
194 Ok(p) => p,
195 Err(e) => {
196 state.counters.substitution_depth -= 1;
197 return Err(e);
198 }
199 };
200
201 let cloned_fs = state.fs.deep_clone();
203
204 let mut sub_state = InterpreterState {
205 fs: cloned_fs,
206 env: state.env.clone(),
207 cwd: state.cwd.clone(),
208 functions: state.functions.clone(),
209 last_exit_code: state.last_exit_code,
210 commands: clone_commands(&state.commands),
211 shell_opts: state.shell_opts.clone(),
212 shopt_opts: state.shopt_opts.clone(),
213 limits: state.limits.clone(),
214 counters: ExecutionCounters {
215 command_count: state.counters.command_count,
216 output_size: state.counters.output_size,
217 start_time: state.counters.start_time,
218 substitution_depth: state.counters.substitution_depth,
219 call_depth: 0,
220 },
221 network_policy: state.network_policy.clone(),
222 should_exit: false,
223 loop_depth: 0,
224 control_flow: None,
225 positional_params: state.positional_params.clone(),
226 shell_name: state.shell_name.clone(),
227 random_seed: state.random_seed,
228 local_scopes: Vec::new(),
229 in_function_depth: 0,
230 traps: HashMap::new(),
231 in_trap: false,
232 errexit_suppressed: 0,
233 stdin_offset: 0,
234 dir_stack: state.dir_stack.clone(),
235 command_hash: state.command_hash.clone(),
236 aliases: state.aliases.clone(),
237 current_lineno: state.current_lineno,
238 shell_start_time: state.shell_start_time,
239 last_argument: state.last_argument.clone(),
240 call_stack: state.call_stack.clone(),
241 machtype: state.machtype.clone(),
242 hosttype: state.hosttype.clone(),
243 persistent_fds: state.persistent_fds.clone(),
244 next_auto_fd: state.next_auto_fd,
245 proc_sub_counter: state.proc_sub_counter,
246 proc_sub_prealloc: HashMap::new(),
247 pipe_stdin_bytes: None,
248 pending_cmdsub_stderr: String::new(),
249 };
250
251 let result = execute_program(&program, &mut sub_state);
252
253 state.counters.command_count = sub_state.counters.command_count;
255 state.counters.output_size = sub_state.counters.output_size;
256 state.counters.substitution_depth -= 1;
257
258 let result = result?;
259
260 state.last_exit_code = result.exit_code;
262
263 if !result.stderr.is_empty() {
266 state.pending_cmdsub_stderr.push_str(&result.stderr);
267 }
268
269 let mut output = result.stdout;
271 let trimmed_len = output.trim_end_matches('\n').len();
272 output.truncate(trimmed_len);
273
274 Ok(output)
275}
276
277fn expand_word_piece(
283 piece: &WordPiece,
284 words: &mut Vec<WordInProgress>,
285 state: &InterpreterState,
286 in_dq: bool,
287) -> Result<bool, RustBashError> {
288 let mut at_empty = false;
289 match piece {
290 WordPiece::Text(s) => {
291 push_segment(words, s, true, in_dq);
294 }
295 WordPiece::SingleQuotedText(s) => {
296 push_segment(words, s, true, true);
297 }
298 WordPiece::AnsiCQuotedText(s) => {
299 let expanded = expand_escape_sequences(s);
300 push_segment(words, &expanded, true, true);
301 }
302 WordPiece::DoubleQuotedSequence(pieces)
303 | WordPiece::GettextDoubleQuotedSequence(pieces) => {
304 let word_count_before = words.len();
305 let seg_count_before = words.last().map_or(0, Vec::len);
306 let mut saw_at_empty = false;
307 for inner in pieces {
308 if expand_word_piece(&inner.piece, words, state, true)? {
309 saw_at_empty = true;
310 }
311 }
312 if words.len() == word_count_before
316 && words.last().map_or(0, Vec::len) == seg_count_before
317 && !saw_at_empty
318 {
319 push_segment(words, "", true, true);
320 }
321 }
322 WordPiece::EscapeSequence(s) => {
323 if let Some(c) = s.strip_prefix('\\') {
324 if in_dq {
327 match c {
328 "$" | "`" | "\"" | "\\" | "\n" => {
329 push_segment(words, c, true, true);
330 }
331 _ => {
332 push_segment(words, s, true, true);
333 }
334 }
335 } else {
336 push_segment(words, c, true, true);
337 }
338 } else {
339 push_segment(words, s, true, true);
340 }
341 }
342 WordPiece::TildePrefix(user) => {
343 expand_tilde(user, words, state);
344 }
345 WordPiece::ParameterExpansion(expr) => {
346 at_empty = expand_parameter(expr, words, state, in_dq)?;
347 }
348 WordPiece::CommandSubstitution(_) | WordPiece::BackquotedCommandSubstitution(_) => {}
350 WordPiece::ArithmeticExpression(_) => {
351 }
355 }
356 Ok(at_empty)
357}
358
359fn expand_word_piece_mut(
361 piece: &WordPiece,
362 words: &mut Vec<WordInProgress>,
363 state: &mut InterpreterState,
364 in_dq: bool,
365) -> Result<bool, RustBashError> {
366 match piece {
367 WordPiece::ParameterExpansion(expr) => {
368 let at_empty = expand_parameter_mut(expr, words, state, in_dq)?;
369 Ok(at_empty)
370 }
371 WordPiece::DoubleQuotedSequence(pieces)
372 | WordPiece::GettextDoubleQuotedSequence(pieces) => {
373 let word_count_before = words.len();
374 let seg_count_before = words.last().map_or(0, Vec::len);
375 let mut saw_at_empty = false;
376 for inner in pieces {
377 if expand_word_piece_mut(&inner.piece, words, state, true)? {
378 saw_at_empty = true;
379 }
380 }
381 if words.len() == word_count_before
382 && words.last().map_or(0, Vec::len) == seg_count_before
383 && !saw_at_empty
384 {
385 push_segment(words, "", true, true);
386 }
387 Ok(false)
388 }
389 WordPiece::CommandSubstitution(cmd_str)
390 | WordPiece::BackquotedCommandSubstitution(cmd_str) => {
391 let output = execute_command_substitution(cmd_str, state)?;
392 push_segment(words, &output, in_dq, in_dq);
393 Ok(false)
394 }
395 WordPiece::ArithmeticExpression(expr) => {
396 let expanded = expand_arith_expression(&expr.value, state)?;
398 let val = crate::interpreter::arithmetic::eval_arithmetic(&expanded, state)?;
399 push_segment(words, &val.to_string(), in_dq, in_dq);
400 Ok(false)
401 }
402 other => expand_word_piece(other, words, state, in_dq),
404 }
405}
406
407fn expand_tilde(user: &str, words: &mut Vec<WordInProgress>, state: &InterpreterState) {
410 if user.is_empty() {
411 let home = get_var(state, "HOME").unwrap_or_default();
413 push_segment(words, &home, true, true);
414 } else {
415 push_segment(words, "~", true, true);
417 push_segment(words, user, true, true);
418 }
419}
420
421fn expand_parameter(
426 expr: &ParameterExpr,
427 words: &mut Vec<WordInProgress>,
428 state: &InterpreterState,
429 in_dq: bool,
430) -> Result<bool, RustBashError> {
431 validate_expr_parameter(expr)?;
432 let mut at_empty = false;
433 let ext = state.shopt_opts.extglob;
434 match expr {
435 ParameterExpr::Parameter {
436 parameter,
437 indirect,
438 } => {
439 check_nounset(parameter, state)?;
440 let val = resolve_parameter(parameter, state, *indirect);
441 at_empty = expand_param_value(&val, words, state, in_dq, parameter);
442 }
443 ParameterExpr::ParameterLength {
444 parameter,
445 indirect,
446 } => {
447 match parameter {
449 Parameter::Special(SpecialParameter::AllPositionalParameters {
450 concatenate: _,
451 }) => {
452 push_segment(
453 words,
454 &state.positional_params.len().to_string(),
455 in_dq,
456 in_dq,
457 );
458 }
459 Parameter::NamedWithAllIndices { name, .. } => {
460 let values = get_array_values(name, state);
461 push_segment(words, &values.len().to_string(), in_dq, in_dq);
462 }
463 _ => {
464 let val = resolve_parameter(parameter, state, *indirect);
465 push_segment(words, &val.len().to_string(), in_dq, in_dq);
466 }
467 }
468 }
469 ParameterExpr::UseDefaultValues {
470 parameter,
471 indirect,
472 test_type,
473 default_value,
474 } => {
475 let val = resolve_parameter(parameter, state, *indirect);
476 let use_default = if *indirect {
477 let target_name = resolve_parameter(parameter, state, false);
479 should_use_indirect_default(&val, &target_name, test_type, state)
480 } else {
481 should_use_default(&val, test_type, state, parameter)
482 };
483 if use_default {
484 if let Some(dv) = default_value {
485 let expanded = expand_raw_string_ctx(dv, state, in_dq)?;
486 push_segment(words, &expanded, in_dq, in_dq);
487 }
488 } else {
489 push_segment(words, &val, in_dq, in_dq);
490 }
491 }
492 ParameterExpr::AssignDefaultValues {
494 parameter,
495 indirect,
496 test_type,
497 default_value,
498 } => {
499 let val = resolve_parameter(parameter, state, *indirect);
500 let use_default = if *indirect {
501 let target_name = resolve_parameter(parameter, state, false);
502 should_use_indirect_default(&val, &target_name, test_type, state)
503 } else {
504 should_use_default(&val, test_type, state, parameter)
505 };
506 if use_default {
507 if let Some(dv) = default_value {
508 let expanded = expand_raw_string_ctx(dv, state, in_dq)?;
509 push_segment(words, &expanded, in_dq, in_dq);
510 }
511 } else {
512 push_segment(words, &val, in_dq, in_dq);
513 }
514 }
515 ParameterExpr::IndicateErrorIfNullOrUnset {
516 parameter,
517 indirect,
518 test_type,
519 error_message,
520 } => {
521 let val = resolve_parameter(parameter, state, *indirect);
522 let use_default = if *indirect {
523 let target_name = resolve_parameter(parameter, state, false);
524 should_use_indirect_default(&val, &target_name, test_type, state)
525 } else {
526 should_use_default(&val, test_type, state, parameter)
527 };
528 if use_default {
529 let param_name = parameter_name(parameter);
530 let msg = if let Some(raw) = error_message {
531 expand_raw_string_ctx(raw, state, in_dq)?
532 } else {
533 "parameter null or not set".to_string()
534 };
535 return Err(RustBashError::ExpansionError {
536 message: format!("{param_name}: {msg}"),
537 exit_code: 127,
538 should_exit: true,
539 });
540 }
541 push_segment(words, &val, in_dq, in_dq);
542 }
543 ParameterExpr::UseAlternativeValue {
544 parameter,
545 indirect,
546 test_type,
547 alternative_value,
548 } => {
549 let val = resolve_parameter(parameter, state, *indirect);
550 let use_default = if *indirect {
551 let target_name = resolve_parameter(parameter, state, false);
552 should_use_indirect_default(&val, &target_name, test_type, state)
553 } else {
554 should_use_default(&val, test_type, state, parameter)
555 };
556 if !use_default && let Some(av) = alternative_value {
557 let expanded = expand_raw_string_ctx(av, state, in_dq)?;
558 push_segment(words, &expanded, in_dq, in_dq);
559 }
560 }
562 ParameterExpr::RemoveSmallestSuffixPattern {
563 parameter,
564 indirect,
565 pattern,
566 } => {
567 if let Some((values, concatenate)) = get_vectorized_values(parameter, state, *indirect)
568 {
569 let results: Vec<String> = values
570 .iter()
571 .map(|v| {
572 if let Some(pat) = pattern
573 && let Some(idx) = pattern::shortest_suffix_match_ext(v, pat, ext)
574 {
575 v[..idx].to_string()
576 } else {
577 v.clone()
578 }
579 })
580 .collect();
581 push_vectorized(results, concatenate, words, state, in_dq);
582 } else {
583 let val = resolve_parameter(parameter, state, *indirect);
584 let result = if let Some(pat) = pattern {
585 if let Some(idx) = pattern::shortest_suffix_match_ext(&val, pat, ext) {
586 val[..idx].to_string()
587 } else {
588 val
589 }
590 } else {
591 val
592 };
593 push_segment(words, &result, in_dq, in_dq);
594 }
595 }
596 ParameterExpr::RemoveLargestSuffixPattern {
597 parameter,
598 indirect,
599 pattern,
600 } => {
601 if let Some((values, concatenate)) = get_vectorized_values(parameter, state, *indirect)
602 {
603 let results: Vec<String> = values
604 .iter()
605 .map(|v| {
606 if let Some(pat) = pattern
607 && let Some(idx) = pattern::longest_suffix_match_ext(v, pat, ext)
608 {
609 v[..idx].to_string()
610 } else {
611 v.clone()
612 }
613 })
614 .collect();
615 push_vectorized(results, concatenate, words, state, in_dq);
616 } else {
617 let val = resolve_parameter(parameter, state, *indirect);
618 let result = if let Some(pat) = pattern {
619 if let Some(idx) = pattern::longest_suffix_match_ext(&val, pat, ext) {
620 val[..idx].to_string()
621 } else {
622 val
623 }
624 } else {
625 val
626 };
627 push_segment(words, &result, in_dq, in_dq);
628 }
629 }
630 ParameterExpr::RemoveSmallestPrefixPattern {
631 parameter,
632 indirect,
633 pattern,
634 } => {
635 if let Some((values, concatenate)) = get_vectorized_values(parameter, state, *indirect)
636 {
637 let results: Vec<String> = values
638 .iter()
639 .map(|v| {
640 if let Some(pat) = pattern
641 && let Some(len) = pattern::shortest_prefix_match_ext(v, pat, ext)
642 {
643 v[len..].to_string()
644 } else {
645 v.clone()
646 }
647 })
648 .collect();
649 push_vectorized(results, concatenate, words, state, in_dq);
650 } else {
651 let val = resolve_parameter(parameter, state, *indirect);
652 let result = if let Some(pat) = pattern {
653 if let Some(len) = pattern::shortest_prefix_match_ext(&val, pat, ext) {
654 val[len..].to_string()
655 } else {
656 val
657 }
658 } else {
659 val
660 };
661 push_segment(words, &result, in_dq, in_dq);
662 }
663 }
664 ParameterExpr::RemoveLargestPrefixPattern {
665 parameter,
666 indirect,
667 pattern,
668 } => {
669 if let Some((values, concatenate)) = get_vectorized_values(parameter, state, *indirect)
670 {
671 let results: Vec<String> = values
672 .iter()
673 .map(|v| {
674 if let Some(pat) = pattern
675 && let Some(len) = pattern::longest_prefix_match_ext(v, pat, ext)
676 {
677 v[len..].to_string()
678 } else {
679 v.clone()
680 }
681 })
682 .collect();
683 push_vectorized(results, concatenate, words, state, in_dq);
684 } else {
685 let val = resolve_parameter(parameter, state, *indirect);
686 let result = if let Some(pat) = pattern {
687 if let Some(len) = pattern::longest_prefix_match_ext(&val, pat, ext) {
688 val[len..].to_string()
689 } else {
690 val
691 }
692 } else {
693 val
694 };
695 push_segment(words, &result, in_dq, in_dq);
696 }
697 }
698 ParameterExpr::Substring {
699 parameter,
700 indirect,
701 offset,
702 length,
703 } => {
704 if let Some((values, concatenate)) = get_vectorized_values(parameter, state, *indirect)
706 {
707 let elem_count = values.len() as i64;
708 let is_positional = matches!(
711 parameter,
712 Parameter::Special(SpecialParameter::AllPositionalParameters { .. })
713 );
714 let off_raw = parse_arithmetic_value(&offset.value);
715 let off = if is_positional && off_raw > 0 {
716 (off_raw - 1) as usize
718 } else if off_raw < 0 {
719 (elem_count + off_raw).max(0) as usize
720 } else {
721 off_raw as usize
722 };
723 let sliced: Vec<String> = if let Some(len_expr) = length {
724 let len = parse_arithmetic_value(&len_expr.value);
725 let len = if len < 0 {
726 (elem_count - off as i64 + len).max(0) as usize
727 } else {
728 len as usize
729 };
730 values.into_iter().skip(off).take(len).collect()
731 } else {
732 values.into_iter().skip(off).collect()
733 };
734 push_vectorized(sliced, concatenate, words, state, in_dq);
735 } else {
736 let val = resolve_parameter(parameter, state, *indirect);
737 let char_count = val.chars().count();
738 let off = parse_arithmetic_value(&offset.value);
739 let off = if off < 0 {
740 (char_count as i64 + off).max(0) as usize
741 } else {
742 off as usize
743 };
744 let substr: String = if let Some(len_expr) = length {
745 let len = parse_arithmetic_value(&len_expr.value);
746 let len = if len < 0 {
747 ((char_count as i64) - (off as i64) + len).max(0) as usize
748 } else {
749 len as usize
750 };
751 if off <= char_count {
752 val.chars().skip(off).take(len).collect()
753 } else {
754 String::new()
755 }
756 } else if off <= char_count {
757 val.chars().skip(off).collect()
758 } else {
759 String::new()
760 };
761 push_segment(words, &substr, in_dq, in_dq);
762 }
763 }
764 ParameterExpr::ReplaceSubstring {
765 parameter,
766 indirect,
767 pattern: pat,
768 replacement,
769 match_kind,
770 } => {
771 let repl = replacement.as_deref().unwrap_or("");
772 let do_replace = |val: &str| -> String {
773 match match_kind {
774 SubstringMatchKind::FirstOccurrence => {
775 if let Some((start, end)) = pattern::first_match_ext(val, pat, ext) {
776 format!("{}{}{}", &val[..start], repl, &val[end..])
777 } else {
778 val.to_string()
779 }
780 }
781 SubstringMatchKind::Anywhere => pattern::replace_all_ext(val, pat, repl, ext),
782 SubstringMatchKind::Prefix => {
783 if let Some(len) = pattern::longest_prefix_match_ext(val, pat, ext) {
784 format!("{repl}{}", &val[len..])
785 } else {
786 val.to_string()
787 }
788 }
789 SubstringMatchKind::Suffix => {
790 if let Some(idx) = pattern::longest_suffix_match_ext(val, pat, ext) {
791 format!("{}{repl}", &val[..idx])
792 } else {
793 val.to_string()
794 }
795 }
796 }
797 };
798 if let Some((values, concatenate)) = get_vectorized_values(parameter, state, *indirect)
799 {
800 let results: Vec<String> = values.iter().map(|v| do_replace(v)).collect();
801 push_vectorized(results, concatenate, words, state, in_dq);
802 } else {
803 let val = resolve_parameter(parameter, state, *indirect);
804 let result = do_replace(&val);
805 push_segment(words, &result, in_dq, in_dq);
806 }
807 }
808 ParameterExpr::UppercaseFirstChar {
809 parameter,
810 indirect,
811 ..
812 } => {
813 if let Some((values, concatenate)) = get_vectorized_values(parameter, state, *indirect)
814 {
815 let results: Vec<String> = values.iter().map(|v| uppercase_first(v)).collect();
816 push_vectorized(results, concatenate, words, state, in_dq);
817 } else {
818 let val = resolve_parameter(parameter, state, *indirect);
819 let result = uppercase_first(&val);
820 push_segment(words, &result, in_dq, in_dq);
821 }
822 }
823 ParameterExpr::UppercasePattern {
824 parameter,
825 indirect,
826 ..
827 } => {
828 if let Some((values, concatenate)) = get_vectorized_values(parameter, state, *indirect)
829 {
830 let results: Vec<String> = values.iter().map(|v| v.to_uppercase()).collect();
831 push_vectorized(results, concatenate, words, state, in_dq);
832 } else {
833 let val = resolve_parameter(parameter, state, *indirect);
834 push_segment(words, &val.to_uppercase(), in_dq, in_dq);
835 }
836 }
837 ParameterExpr::LowercaseFirstChar {
838 parameter,
839 indirect,
840 ..
841 } => {
842 if let Some((values, concatenate)) = get_vectorized_values(parameter, state, *indirect)
843 {
844 let results: Vec<String> = values.iter().map(|v| lowercase_first(v)).collect();
845 push_vectorized(results, concatenate, words, state, in_dq);
846 } else {
847 let val = resolve_parameter(parameter, state, *indirect);
848 let result = lowercase_first(&val);
849 push_segment(words, &result, in_dq, in_dq);
850 }
851 }
852 ParameterExpr::LowercasePattern {
853 parameter,
854 indirect,
855 ..
856 } => {
857 if let Some((values, concatenate)) = get_vectorized_values(parameter, state, *indirect)
858 {
859 let results: Vec<String> = values.iter().map(|v| v.to_lowercase()).collect();
860 push_vectorized(results, concatenate, words, state, in_dq);
861 } else {
862 let val = resolve_parameter(parameter, state, *indirect);
863 push_segment(words, &val.to_lowercase(), in_dq, in_dq);
864 }
865 }
866 ParameterExpr::Transform {
867 parameter,
868 indirect,
869 op,
870 } => {
871 let var_name = parameter_name(parameter);
872 if let Some((values, concatenate)) = get_vectorized_values(parameter, state, *indirect)
873 {
874 let results: Vec<String> = values
875 .iter()
876 .map(|v| apply_transform(v, op, &var_name, state))
877 .collect();
878 push_vectorized(results, concatenate, words, state, in_dq);
879 } else {
880 let val = resolve_parameter(parameter, state, *indirect);
881 let result = apply_transform(&val, op, &var_name, state);
882 push_segment(words, &result, in_dq, in_dq);
883 }
884 }
885 ParameterExpr::VariableNames { prefix, .. } => {
886 let mut names: Vec<String> = state
887 .env
888 .keys()
889 .filter(|k| k.starts_with(prefix.as_str()))
890 .cloned()
891 .collect();
892 names.sort();
893 push_segment(words, &names.join(" "), in_dq, in_dq);
894 }
895 ParameterExpr::MemberKeys {
896 variable_name,
897 concatenate,
898 } => {
899 let keys = get_array_keys(variable_name, state);
900 if *concatenate {
901 let sep = match get_var(state, "IFS") {
903 Some(s) => s.chars().next().map(|c| c.to_string()).unwrap_or_default(),
904 None => " ".to_string(),
905 };
906 push_segment(words, &keys.join(&sep), in_dq, in_dq);
907 } else if keys.is_empty() {
908 at_empty = true;
909 } else {
910 for (i, k) in keys.iter().enumerate() {
912 if i > 0 {
913 start_new_word(words);
914 }
915 push_segment(words, k, in_dq, in_dq);
916 }
917 }
918 }
919 }
920 Ok(at_empty)
921}
922
923fn expand_parameter_mut(
925 expr: &ParameterExpr,
926 words: &mut Vec<WordInProgress>,
927 state: &mut InterpreterState,
928 in_dq: bool,
929) -> Result<bool, RustBashError> {
930 validate_expr_parameter(expr)?;
931 match expr {
932 ParameterExpr::AssignDefaultValues {
933 parameter,
934 indirect,
935 test_type,
936 default_value,
937 } => {
938 let val = resolve_parameter_maybe_mut(parameter, state, *indirect)?;
939 let use_default = if *indirect {
940 let target_name = resolve_parameter_maybe_mut(parameter, state, false)?;
941 should_use_indirect_default(&val, &target_name, test_type, state)
942 } else {
943 should_use_default(&val, test_type, state, parameter)
944 };
945 if use_default {
946 let dv = if let Some(raw) = default_value {
947 expand_raw_string_mut_ctx(raw, state, in_dq)?
948 } else {
949 String::new()
950 };
951 if *indirect {
952 if let Parameter::Named(_) = parameter {
954 let target_name = resolve_parameter_maybe_mut(parameter, state, false)?;
955 if !target_name.is_empty() {
956 set_variable(state, &target_name, dv.clone())?;
957 }
958 }
959 } else if let Parameter::Named(name) = parameter {
960 set_variable(state, name, dv.clone())?;
961 }
962 push_segment(words, &dv, in_dq, in_dq);
963 } else {
964 push_segment(words, &val, in_dq, in_dq);
965 }
966 Ok(false)
967 }
968 ParameterExpr::Parameter {
969 parameter,
970 indirect,
971 } => {
972 check_nounset(parameter, state)?;
973 let val = resolve_parameter_maybe_mut(parameter, state, *indirect)?;
974 let at_empty = expand_param_value(&val, words, state, in_dq, parameter);
975 Ok(at_empty)
976 }
977 ParameterExpr::Substring {
978 parameter,
979 indirect,
980 offset,
981 length,
982 } => {
983 if let Some((_, concatenate)) = get_vectorized_values(parameter, state, *indirect) {
984 let is_positional = matches!(
986 parameter,
987 Parameter::Special(SpecialParameter::AllPositionalParameters { .. })
988 );
989
990 let kv_pairs = get_array_kv_pairs(parameter, state);
992 let elem_count = kv_pairs.len() as i64;
993 let max_key = kv_pairs.last().map(|(k, _)| *k).unwrap_or(0) as i64;
994
995 let expanded_off = expand_arith_expression(&offset.value, state)?;
997 let off_raw =
998 crate::interpreter::arithmetic::eval_arithmetic(&expanded_off, state)?;
999
1000 let compute_threshold = |raw: i64| -> Option<usize> {
1004 if is_positional {
1005 if raw > 0 {
1006 Some((raw - 1) as usize)
1007 } else if raw < 0 {
1008 let t = elem_count + raw;
1009 if t < 0 { None } else { Some(t as usize) }
1010 } else {
1011 Some(0)
1012 }
1013 } else if raw < 0 {
1014 let t = max_key.checked_add(1).and_then(|v| v.checked_add(raw));
1015 match t {
1016 Some(v) if v >= 0 => Some(v as usize),
1017 _ => None,
1018 }
1019 } else {
1020 Some(raw as usize)
1021 }
1022 };
1023
1024 let sliced: Vec<String> = if let Some(len_expr) = length {
1025 let expanded_len = expand_arith_expression(&len_expr.value, state)?;
1026 let len_raw =
1027 crate::interpreter::arithmetic::eval_arithmetic(&expanded_len, state)?;
1028 if len_raw < 0 {
1029 return Err(RustBashError::ExpansionError {
1030 message: format!("{}: substring expression < 0", offset.value),
1031 exit_code: 1,
1032 should_exit: false,
1033 });
1034 }
1035 let len = len_raw as usize;
1036 match compute_threshold(off_raw) {
1037 None => Vec::new(),
1038 Some(threshold) if is_positional => kv_pairs
1039 .into_iter()
1040 .map(|(_, v)| v)
1041 .skip(threshold)
1042 .take(len)
1043 .collect(),
1044 Some(threshold) => kv_pairs
1045 .into_iter()
1046 .filter(|(k, _)| *k >= threshold)
1047 .map(|(_, v)| v)
1048 .take(len)
1049 .collect(),
1050 }
1051 } else {
1052 match compute_threshold(off_raw) {
1054 None => Vec::new(),
1055 Some(threshold) if is_positional => kv_pairs
1056 .into_iter()
1057 .map(|(_, v)| v)
1058 .skip(threshold)
1059 .collect(),
1060 Some(threshold) => kv_pairs
1061 .into_iter()
1062 .filter(|(k, _)| *k >= threshold)
1063 .map(|(_, v)| v)
1064 .collect(),
1065 }
1066 };
1067 push_vectorized(sliced, concatenate, words, state, in_dq);
1068 } else {
1069 let val = resolve_parameter_maybe_mut(parameter, state, *indirect)?;
1071 let char_count = val.chars().count();
1072 let expanded_off = expand_arith_expression(&offset.value, state)?;
1073 let off = crate::interpreter::arithmetic::eval_arithmetic(&expanded_off, state)?;
1074 let off = if off < 0 {
1075 (char_count as i64 + off).max(0) as usize
1076 } else {
1077 off as usize
1078 };
1079 let substr: String = if let Some(len_expr) = length {
1080 let expanded_len = expand_arith_expression(&len_expr.value, state)?;
1081 let len =
1082 crate::interpreter::arithmetic::eval_arithmetic(&expanded_len, state)?;
1083 let len = if len < 0 {
1084 ((char_count as i64) - (off as i64) + len).max(0) as usize
1085 } else {
1086 len as usize
1087 };
1088 if off <= char_count {
1089 val.chars().skip(off).take(len).collect()
1090 } else {
1091 String::new()
1092 }
1093 } else if off <= char_count {
1094 val.chars().skip(off).collect()
1095 } else {
1096 String::new()
1097 };
1098 push_segment(words, &substr, in_dq, in_dq);
1099 }
1100 Ok(false)
1101 }
1102 other => expand_parameter(other, words, state, in_dq),
1104 }
1105}
1106
1107fn resolve_parameter_maybe_mut(
1110 parameter: &Parameter,
1111 state: &mut InterpreterState,
1112 indirect: bool,
1113) -> Result<String, RustBashError> {
1114 if let Parameter::Named(name) = parameter
1116 && let Err(_) = crate::interpreter::resolve_nameref(name, state)
1117 {
1118 state.last_exit_code = 1;
1122 return Ok(String::new());
1123 }
1124 let val = match parameter {
1125 Parameter::Named(name) if name == "RANDOM" => next_random(state).to_string(),
1126 Parameter::NamedWithIndex { name, index } => resolve_array_element_mut(name, index, state)?,
1127 _ => resolve_parameter_direct(parameter, state),
1128 };
1129 if indirect {
1130 Ok(resolve_indirect_value(&val, state))
1131 } else {
1132 Ok(val)
1133 }
1134}
1135
1136fn expand_param_value(
1141 val: &str,
1142 words: &mut Vec<WordInProgress>,
1143 state: &InterpreterState,
1144 in_dq: bool,
1145 parameter: &Parameter,
1146) -> bool {
1147 match parameter {
1148 Parameter::Special(SpecialParameter::AllPositionalParameters { concatenate }) => {
1149 if *concatenate {
1150 let ifs_val = get_var(state, "IFS");
1153 let ifs_empty = matches!(&ifs_val, Some(s) if s.is_empty());
1154 if !in_dq && ifs_empty {
1155 if state.positional_params.is_empty() {
1157 return true;
1158 }
1159 for (i, param) in state.positional_params.iter().enumerate() {
1160 if i > 0 {
1161 start_new_word(words);
1162 }
1163 push_segment(words, param, false, false);
1164 }
1165 return false;
1166 }
1167 let sep = match ifs_val {
1168 Some(s) => s.chars().next().map(|c| c.to_string()).unwrap_or_default(),
1169 None => " ".to_string(),
1170 };
1171 let joined = state.positional_params.join(&sep);
1172 push_segment(words, &joined, in_dq, in_dq);
1173 false
1174 } else if state.positional_params.is_empty() {
1175 true
1177 } else {
1178 for (i, param) in state.positional_params.iter().enumerate() {
1182 if i > 0 {
1183 start_new_word(words);
1184 }
1185 push_segment(words, param, in_dq, in_dq);
1186 }
1187 false
1188 }
1189 }
1190 Parameter::NamedWithAllIndices { name, concatenate } => {
1191 let values = get_array_values(name, state);
1192 if *concatenate {
1193 let ifs_val = get_var(state, "IFS");
1195 let ifs_empty = matches!(&ifs_val, Some(s) if s.is_empty());
1196 if !in_dq && ifs_empty {
1197 if values.is_empty() {
1199 return true;
1200 }
1201 for (i, v) in values.iter().enumerate() {
1202 if i > 0 {
1203 start_new_word(words);
1204 }
1205 push_segment(words, v, false, false);
1206 }
1207 return false;
1208 }
1209 let sep = match ifs_val {
1210 Some(s) => s.chars().next().map(|c| c.to_string()).unwrap_or_default(),
1211 None => " ".to_string(),
1212 };
1213 let joined = values.join(&sep);
1214 push_segment(words, &joined, in_dq, in_dq);
1215 false
1216 } else if values.is_empty() {
1217 true
1219 } else {
1220 for (i, v) in values.iter().enumerate() {
1222 if i > 0 {
1223 start_new_word(words);
1224 }
1225 push_segment(words, v, in_dq, in_dq);
1226 }
1227 false
1228 }
1229 }
1230 _ => {
1231 push_segment(words, val, in_dq, in_dq);
1232 false
1233 }
1234 }
1235}
1236
1237fn get_ifs(state: &InterpreterState) -> String {
1241 get_var(state, "IFS").unwrap_or_else(|| " \t\n".to_string())
1242}
1243
1244struct SplitWord {
1246 text: String,
1247 may_glob: bool,
1249}
1250
1251fn finalize_with_ifs_split(words: Vec<WordInProgress>, state: &InterpreterState) -> Vec<SplitWord> {
1253 let ifs = get_ifs(state);
1254 let extglob = state.shopt_opts.extglob;
1255 let mut result = Vec::new();
1256 for word in words {
1257 ifs_split_word(&word, &ifs, &mut result);
1258 }
1259 if extglob {
1261 for w in &mut result {
1262 if !w.may_glob && has_extglob_pattern(&w.text) {
1263 w.may_glob = true;
1264 }
1265 }
1266 }
1267 result
1268}
1269
1270fn finalize_no_split(words: Vec<WordInProgress>) -> Vec<String> {
1272 words
1273 .into_iter()
1274 .map(|segments| segments.into_iter().map(|s| s.text).collect::<String>())
1275 .collect()
1276}
1277
1278fn is_glob_meta(c: char) -> bool {
1280 matches!(c, '*' | '?' | '[')
1281}
1282
1283fn has_extglob_pattern(s: &str) -> bool {
1285 let b = s.as_bytes();
1286 let mut i = 0;
1287 while i + 1 < b.len() {
1288 if b[i] == b'\\' {
1289 i += 2;
1290 continue;
1291 }
1292 if matches!(b[i], b'@' | b'+' | b'*' | b'?' | b'!') && b[i + 1] == b'(' {
1293 return true;
1294 }
1295 i += 1;
1296 }
1297 false
1298}
1299
1300fn ifs_split_word(word: &[Segment], ifs: &str, result: &mut Vec<SplitWord>) {
1305 let chars: Vec<(char, bool, bool)> = word
1307 .iter()
1308 .flat_map(|s| s.text.chars().map(move |c| (c, s.quoted, s.glob_protected)))
1309 .collect();
1310
1311 if chars.is_empty() {
1312 if word.iter().any(|s| s.quoted) {
1314 result.push(SplitWord {
1315 text: String::new(),
1316 may_glob: false,
1317 });
1318 }
1319 return;
1320 }
1321
1322 if chars.iter().all(|(_, q, _)| *q) {
1324 let s: String = chars.iter().map(|(c, _, _)| c).collect();
1325 let may_glob = chars.iter().any(|(c, _, gp)| !gp && is_glob_meta(*c));
1326 result.push(SplitWord { text: s, may_glob });
1327 return;
1328 }
1329
1330 let ifs_ws: Vec<char> = ifs
1332 .chars()
1333 .filter(|c| matches!(c, ' ' | '\t' | '\n'))
1334 .collect();
1335 let ifs_non_ws: Vec<char> = ifs
1336 .chars()
1337 .filter(|c| !matches!(c, ' ' | '\t' | '\n'))
1338 .collect();
1339
1340 let is_ifs_ws = |c: char| ifs_ws.contains(&c);
1341 let is_ifs_nw = |c: char| ifs_non_ws.contains(&c);
1342
1343 let len = chars.len();
1344 let mut current = String::new();
1345 let mut current_may_glob = false;
1346 let mut has_content = false;
1347 let mut i = 0;
1348
1349 while i < len {
1351 let (c, quoted, _) = chars[i];
1352 if !quoted && is_ifs_ws(c) {
1353 i += 1;
1354 } else {
1355 break;
1356 }
1357 }
1358
1359 while i < len {
1360 let (c, quoted, glob_protected) = chars[i];
1361 if quoted {
1362 current.push(c);
1363 if !glob_protected && is_glob_meta(c) {
1364 current_may_glob = true;
1365 }
1366 has_content = true;
1367 i += 1;
1368 } else if is_ifs_nw(c) {
1369 result.push(SplitWord {
1371 text: std::mem::take(&mut current),
1372 may_glob: current_may_glob,
1373 });
1374 current_may_glob = false;
1375 has_content = false;
1376 i += 1;
1377 while i < len && !chars[i].1 && is_ifs_ws(chars[i].0) {
1379 i += 1;
1380 }
1381 } else if is_ifs_ws(c) {
1382 while i < len && !chars[i].1 && is_ifs_ws(chars[i].0) {
1384 i += 1;
1385 }
1386 if i < len && !chars[i].1 && is_ifs_nw(chars[i].0) {
1388 continue;
1389 }
1390 if has_content || !current.is_empty() {
1392 result.push(SplitWord {
1393 text: std::mem::take(&mut current),
1394 may_glob: current_may_glob,
1395 });
1396 current_may_glob = false;
1397 has_content = false;
1398 }
1399 } else {
1400 current.push(c);
1402 if !glob_protected && is_glob_meta(c) {
1403 current_may_glob = true;
1404 }
1405 has_content = true;
1406 i += 1;
1407 }
1408 }
1409
1410 if has_content || !current.is_empty() {
1413 result.push(SplitWord {
1414 text: current,
1415 may_glob: current_may_glob,
1416 });
1417 }
1418}
1419
1420use std::path::PathBuf;
1423
1424fn glob_expand_words(
1431 words: Vec<SplitWord>,
1432 state: &InterpreterState,
1433) -> Result<Vec<String>, RustBashError> {
1434 if state.shell_opts.noglob {
1436 return Ok(words.into_iter().map(|w| w.text).collect());
1437 }
1438
1439 let cwd = PathBuf::from(&state.cwd);
1440 let max = state.limits.max_glob_results;
1441 let opts = GlobOptions {
1442 dotglob: state.shopt_opts.dotglob,
1443 nocaseglob: state.shopt_opts.nocaseglob,
1444 globstar: state.shopt_opts.globstar,
1445 extglob: state.shopt_opts.extglob,
1446 };
1447
1448 let globignore_patterns: Vec<String> = get_var(state, "GLOBIGNORE")
1450 .filter(|s| !s.is_empty())
1451 .map(|s| s.split(':').map(String::from).collect())
1452 .unwrap_or_default();
1453 let has_globignore = !globignore_patterns.is_empty();
1454
1455 let mut result = Vec::new();
1456
1457 for w in words {
1458 if !w.may_glob {
1459 result.push(w.text);
1460 continue;
1461 }
1462
1463 match state.fs.glob_with_opts(&w.text, &cwd, &opts) {
1464 Ok(matches) if !matches.is_empty() => {
1465 if matches.len() > max {
1466 return Err(RustBashError::LimitExceeded {
1467 limit_name: "max_glob_results",
1468 limit_value: max,
1469 actual_value: matches.len(),
1470 });
1471 }
1472 let before_len = result.len();
1473 for p in &matches {
1474 let s = p.to_string_lossy().into_owned();
1475 if has_globignore {
1477 let basename = s.rsplit('/').next().unwrap_or(&s);
1478 if basename == "." || basename == ".." {
1480 continue;
1481 }
1482 if globignore_patterns
1484 .iter()
1485 .any(|pat| pattern::glob_match_path(pat, &s))
1486 {
1487 continue;
1488 }
1489 }
1490 result.push(s);
1491 }
1492 if has_globignore && result.len() == before_len {
1494 if state.shopt_opts.failglob {
1495 return Err(RustBashError::FailGlob {
1496 pattern: w.text.clone(),
1497 });
1498 }
1499 if state.shopt_opts.nullglob {
1500 continue;
1501 }
1502 result.push(w.text.clone());
1503 }
1504 }
1505 _ => {
1506 if state.shopt_opts.failglob {
1507 return Err(RustBashError::FailGlob {
1508 pattern: w.text.clone(),
1509 });
1510 }
1511 if state.shopt_opts.nullglob {
1512 continue;
1514 }
1515 result.push(w.text);
1517 }
1518 }
1519 }
1520
1521 Ok(result)
1522}
1523
1524use brush_parser::word::ParameterTransformOp;
1527
1528fn apply_transform(
1529 val: &str,
1530 op: &ParameterTransformOp,
1531 var_name: &str,
1532 state: &InterpreterState,
1533) -> String {
1534 match op {
1535 ParameterTransformOp::ToUpperCase => val.to_uppercase(),
1536 ParameterTransformOp::ToLowerCase => val.to_lowercase(),
1537 ParameterTransformOp::CapitalizeInitial => uppercase_first(val),
1538 ParameterTransformOp::Quoted => shell_quote(val),
1539 ParameterTransformOp::ExpandEscapeSequences => expand_escape_sequences(val),
1540 ParameterTransformOp::PromptExpand => expand_prompt_sequences(val, state),
1541 ParameterTransformOp::PossiblyQuoteWithArraysExpanded { .. } => shell_quote(val),
1542 ParameterTransformOp::ToAssignmentLogic => format_assignment(var_name, state),
1543 ParameterTransformOp::ToAttributeFlags => format_attribute_flags(var_name, state),
1544 }
1545}
1546
1547fn shell_quote(val: &str) -> String {
1551 if val.is_empty() {
1552 return "''".to_string();
1553 }
1554 let needs_dollar_quote = val.chars().any(|c| c == '\'' || c.is_ascii_control());
1556 if !needs_dollar_quote {
1557 return format!("'{val}'");
1558 }
1559 let mut out = String::from("$'");
1561 for ch in val.chars() {
1562 match ch {
1563 '\'' => out.push_str("\\'"),
1564 '\\' => out.push_str("\\\\"),
1565 '\n' => out.push_str("\\n"),
1566 '\t' => out.push_str("\\t"),
1567 '\r' => out.push_str("\\r"),
1568 '\x07' => out.push_str("\\a"),
1569 '\x08' => out.push_str("\\b"),
1570 '\x0C' => out.push_str("\\f"),
1571 '\x0B' => out.push_str("\\v"),
1572 '\x1B' => out.push_str("\\E"),
1573 c if c.is_ascii_control() => {
1574 out.push_str(&format!("\\x{:02x}", c as u32));
1575 }
1576 c => out.push(c),
1577 }
1578 }
1579 out.push('\'');
1580 out
1581}
1582
1583fn expand_escape_sequences(val: &str) -> String {
1585 let mut result = String::new();
1586 let chars: Vec<char> = val.chars().collect();
1587 let mut i = 0;
1588 while i < chars.len() {
1589 if chars[i] == '\\' && i + 1 < chars.len() {
1590 i += 1;
1591 match chars[i] {
1592 'n' => result.push('\n'),
1593 't' => result.push('\t'),
1594 'r' => result.push('\r'),
1595 'a' => result.push('\x07'),
1596 'b' => result.push('\x08'),
1597 'f' => result.push('\x0C'),
1598 'v' => result.push('\x0B'),
1599 'e' | 'E' => result.push('\x1B'),
1600 '\\' => result.push('\\'),
1601 '\'' => result.push('\''),
1602 '"' => result.push('"'),
1603 'x' => {
1604 let mut hex = String::new();
1606 while hex.len() < 2 && i + 1 < chars.len() && chars[i + 1].is_ascii_hexdigit() {
1607 i += 1;
1608 hex.push(chars[i]);
1609 }
1610 if hex.is_empty() {
1611 result.push('\\');
1613 result.push('x');
1614 } else if let Ok(n) = u32::from_str_radix(&hex, 16)
1615 && let Some(c) = char::from_u32(n)
1616 {
1617 result.push(c);
1618 }
1619 }
1621 'u' => {
1622 let mut hex = String::new();
1624 while hex.len() < 4 && i + 1 < chars.len() && chars[i + 1].is_ascii_hexdigit() {
1625 i += 1;
1626 hex.push(chars[i]);
1627 }
1628 if hex.is_empty() {
1629 result.push('\\');
1630 result.push('u');
1631 } else if let Ok(n) = u32::from_str_radix(&hex, 16)
1632 && let Some(c) = char::from_u32(n)
1633 {
1634 result.push(c);
1635 }
1636 }
1637 'U' => {
1638 let mut hex = String::new();
1640 while hex.len() < 8 && i + 1 < chars.len() && chars[i + 1].is_ascii_hexdigit() {
1641 i += 1;
1642 hex.push(chars[i]);
1643 }
1644 if hex.is_empty() {
1645 result.push('\\');
1646 result.push('U');
1647 } else if let Ok(n) = u32::from_str_radix(&hex, 16)
1648 && let Some(c) = char::from_u32(n)
1649 {
1650 result.push(c);
1651 }
1652 }
1653 '0'..='7' => {
1654 let first_digit = chars[i].to_digit(8).unwrap_or(0);
1657 let max_extra = if chars[i] == '0' { 3 } else { 2 };
1658 let mut val_octal = first_digit;
1659 let mut count = 0;
1660 while count < max_extra
1661 && i + 1 < chars.len()
1662 && chars[i + 1] >= '0'
1663 && chars[i + 1] <= '7'
1664 {
1665 i += 1;
1666 val_octal = val_octal * 8 + chars[i].to_digit(8).unwrap_or(0);
1667 count += 1;
1668 }
1669 if let Some(c) = char::from_u32(val_octal) {
1670 result.push(c);
1671 }
1672 }
1673 other => {
1674 result.push('\\');
1675 result.push(other);
1676 }
1677 }
1678 } else {
1679 result.push(chars[i]);
1680 }
1681 i += 1;
1682 }
1683 result
1684}
1685
1686fn expand_prompt_sequences(val: &str, state: &InterpreterState) -> String {
1688 let mut result = String::new();
1689 let chars: Vec<char> = val.chars().collect();
1690 let mut i = 0;
1691 while i < chars.len() {
1692 if chars[i] == '\\' && i + 1 < chars.len() {
1693 i += 1;
1694 match chars[i] {
1695 'u' => {
1696 result.push_str(&get_var(state, "USER").unwrap_or_else(|| "user".to_string()));
1697 }
1698 'h' => {
1699 let hostname =
1700 get_var(state, "HOSTNAME").unwrap_or_else(|| "localhost".to_string());
1701 result.push_str(hostname.split('.').next().unwrap_or(&hostname));
1703 }
1704 'H' => {
1705 result.push_str(
1706 &get_var(state, "HOSTNAME").unwrap_or_else(|| "localhost".to_string()),
1707 );
1708 }
1709 'w' => {
1710 let cwd = &state.cwd;
1711 let home = get_var(state, "HOME").unwrap_or_default();
1712 if !home.is_empty() && cwd.starts_with(&home) {
1713 result.push('~');
1714 result.push_str(&cwd[home.len()..]);
1715 } else {
1716 result.push_str(cwd);
1717 }
1718 }
1719 'W' => {
1720 let cwd = &state.cwd;
1721 if cwd == "/" {
1722 result.push('/');
1723 } else {
1724 result.push_str(cwd.rsplit('/').next().unwrap_or(cwd));
1725 }
1726 }
1727 'd' => {
1728 result.push_str("Mon Jan 01");
1730 }
1731 't' => {
1732 result.push_str("00:00:00");
1734 }
1735 'T' => {
1736 result.push_str("12:00:00");
1738 }
1739 '@' => {
1740 result.push_str("12:00 AM");
1742 }
1743 'A' => {
1744 result.push_str("00:00");
1746 }
1747 'n' => result.push('\n'),
1748 'r' => result.push('\r'),
1749 'a' => result.push('\x07'),
1750 'e' => result.push('\x1B'),
1751 's' => {
1752 result.push_str(&state.shell_name);
1753 }
1754 'v' | 'V' => {
1755 result.push_str("5.0");
1756 }
1757 '#' => {
1758 result.push_str(&state.counters.command_count.to_string());
1759 }
1760 '$' => {
1761 result.push('$');
1763 }
1764 '[' | ']' => {
1765 }
1767 '\\' => result.push('\\'),
1768 other => {
1769 result.push('\\');
1770 result.push(other);
1771 }
1772 }
1773 } else {
1774 result.push(chars[i]);
1775 }
1776 i += 1;
1777 }
1778 result
1779}
1780
1781fn format_assignment(name: &str, state: &InterpreterState) -> String {
1783 use crate::interpreter::{VariableAttrs, VariableValue};
1784 let resolved = crate::interpreter::resolve_nameref_or_self(name, state);
1785 let var = match state.env.get(&resolved) {
1786 Some(v) => v,
1787 None => return String::new(),
1788 };
1789
1790 let mut flags = String::from("declare ");
1791 let mut flag_chars = String::new();
1792 match &var.value {
1793 VariableValue::IndexedArray(_) => flag_chars.push('a'),
1794 VariableValue::AssociativeArray(_) => flag_chars.push('A'),
1795 VariableValue::Scalar(_) => {}
1796 }
1797 if var.attrs.contains(VariableAttrs::INTEGER) {
1798 flag_chars.push('i');
1799 }
1800 if var.attrs.contains(VariableAttrs::LOWERCASE) {
1801 flag_chars.push('l');
1802 }
1803 if var.attrs.contains(VariableAttrs::NAMEREF) {
1804 flag_chars.push('n');
1805 }
1806 if var.attrs.contains(VariableAttrs::READONLY) {
1807 flag_chars.push('r');
1808 }
1809 if var.attrs.contains(VariableAttrs::UPPERCASE) {
1810 flag_chars.push('u');
1811 }
1812 if var.attrs.contains(VariableAttrs::EXPORTED) {
1813 flag_chars.push('x');
1814 }
1815
1816 if flag_chars.is_empty() {
1817 flags.push_str("-- ");
1818 } else {
1819 flags.push('-');
1820 flags.push_str(&flag_chars);
1821 flags.push(' ');
1822 }
1823
1824 match &var.value {
1825 VariableValue::Scalar(s) => {
1826 format!("{flags}{resolved}='{s}'")
1827 }
1828 VariableValue::IndexedArray(map) => {
1829 let elements: Vec<String> = map.iter().map(|(k, v)| format!("[{k}]=\"{v}\"")).collect();
1830 format!("{flags}{resolved}=({})", elements.join(" "))
1831 }
1832 VariableValue::AssociativeArray(map) => {
1833 let mut keys: Vec<&String> = map.keys().collect();
1834 keys.sort();
1835 let elements: Vec<String> = keys
1836 .iter()
1837 .map(|k| format!("[{k}]=\"{}\"", map[*k]))
1838 .collect();
1839 format!("{flags}{resolved}=({})", elements.join(" "))
1840 }
1841 }
1842}
1843
1844fn format_attribute_flags(name: &str, state: &InterpreterState) -> String {
1846 use crate::interpreter::{VariableAttrs, VariableValue};
1847 let resolved = crate::interpreter::resolve_nameref_or_self(name, state);
1848 let var = match state.env.get(&resolved) {
1849 Some(v) => v,
1850 None => return String::new(),
1851 };
1852 let mut flags = String::new();
1853 match &var.value {
1854 VariableValue::IndexedArray(_) => flags.push('a'),
1855 VariableValue::AssociativeArray(_) => flags.push('A'),
1856 VariableValue::Scalar(_) => {}
1857 }
1858 if var.attrs.contains(VariableAttrs::INTEGER) {
1859 flags.push('i');
1860 }
1861 if var.attrs.contains(VariableAttrs::LOWERCASE) {
1862 flags.push('l');
1863 }
1864 if var.attrs.contains(VariableAttrs::NAMEREF) {
1865 flags.push('n');
1866 }
1867 if var.attrs.contains(VariableAttrs::READONLY) {
1868 flags.push('r');
1869 }
1870 if var.attrs.contains(VariableAttrs::UPPERCASE) {
1871 flags.push('u');
1872 }
1873 if var.attrs.contains(VariableAttrs::EXPORTED) {
1874 flags.push('x');
1875 }
1876 flags
1877}
1878
1879fn uppercase_first(s: &str) -> String {
1880 let mut chars = s.chars();
1881 match chars.next() {
1882 None => String::new(),
1883 Some(c) => {
1884 let mut result = c.to_uppercase().to_string();
1885 result.extend(chars);
1886 result
1887 }
1888 }
1889}
1890
1891fn lowercase_first(s: &str) -> String {
1892 let mut chars = s.chars();
1893 match chars.next() {
1894 None => String::new(),
1895 Some(c) => {
1896 let mut result = c.to_lowercase().to_string();
1897 result.extend(chars);
1898 result
1899 }
1900 }
1901}
1902
1903fn check_nounset(parameter: &Parameter, state: &InterpreterState) -> Result<(), RustBashError> {
1909 if !state.shell_opts.nounset {
1910 return Ok(());
1911 }
1912 if matches!(parameter, Parameter::Special(_)) {
1914 return Ok(());
1915 }
1916 if is_unset(state, parameter) {
1917 let name = parameter_name(parameter);
1918 return Err(RustBashError::Execution(format!(
1919 "{name}: unbound variable"
1920 )));
1921 }
1922 Ok(())
1923}
1924
1925fn validate_parameter_name(parameter: &Parameter) -> Result<(), RustBashError> {
1928 if let Parameter::Named(name) = parameter
1929 && (name.is_empty()
1930 || !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
1931 || name.starts_with(|c: char| c.is_ascii_digit()))
1932 {
1933 return Err(RustBashError::Execution(format!(
1934 "${{{name}}}: bad substitution"
1935 )));
1936 }
1937 Ok(())
1938}
1939
1940fn validate_expr_parameter(expr: &ParameterExpr) -> Result<(), RustBashError> {
1942 let param = match expr {
1943 ParameterExpr::Parameter { parameter, .. }
1944 | ParameterExpr::UseDefaultValues { parameter, .. }
1945 | ParameterExpr::AssignDefaultValues { parameter, .. }
1946 | ParameterExpr::IndicateErrorIfNullOrUnset { parameter, .. }
1947 | ParameterExpr::UseAlternativeValue { parameter, .. }
1948 | ParameterExpr::ParameterLength { parameter, .. }
1949 | ParameterExpr::RemoveSmallestSuffixPattern { parameter, .. }
1950 | ParameterExpr::RemoveLargestSuffixPattern { parameter, .. }
1951 | ParameterExpr::RemoveSmallestPrefixPattern { parameter, .. }
1952 | ParameterExpr::RemoveLargestPrefixPattern { parameter, .. }
1953 | ParameterExpr::Substring { parameter, .. }
1954 | ParameterExpr::UppercaseFirstChar { parameter, .. }
1955 | ParameterExpr::UppercasePattern { parameter, .. }
1956 | ParameterExpr::LowercaseFirstChar { parameter, .. }
1957 | ParameterExpr::LowercasePattern { parameter, .. }
1958 | ParameterExpr::ReplaceSubstring { parameter, .. }
1959 | ParameterExpr::Transform { parameter, .. } => parameter,
1960 ParameterExpr::VariableNames { .. } | ParameterExpr::MemberKeys { .. } => return Ok(()),
1961 };
1962 validate_parameter_name(param)
1963}
1964
1965fn resolve_parameter(parameter: &Parameter, state: &InterpreterState, indirect: bool) -> String {
1966 let val = resolve_parameter_direct(parameter, state);
1967 if indirect {
1968 resolve_indirect_value(&val, state)
1969 } else {
1970 val
1971 }
1972}
1973
1974fn resolve_indirect_value(target: &str, state: &InterpreterState) -> String {
1977 if target.is_empty() {
1978 return String::new();
1979 }
1980 if let Some(bracket_pos) = target.find('[')
1982 && target.ends_with(']')
1983 {
1984 let name = &target[..bracket_pos];
1985 let index_raw = &target[bracket_pos + 1..target.len() - 1];
1986 if index_raw == "@" || index_raw == "*" {
1987 let concatenate = index_raw == "*";
1989 return resolve_all_elements(name, concatenate, state);
1990 }
1991 let index = expand_simple_dollar_vars(index_raw, state);
1993 return resolve_array_element(name, &index, state);
1994 }
1995 if let Ok(n) = target.parse::<u32>() {
1997 if n == 0 {
1998 return state.shell_name.clone();
1999 }
2000 return state
2001 .positional_params
2002 .get(n as usize - 1)
2003 .cloned()
2004 .unwrap_or_default();
2005 }
2006 match target {
2008 "@" => state.positional_params.join(" "),
2009 "*" => {
2010 let sep = match get_var(state, "IFS") {
2011 Some(s) => s.chars().next().map(|c| c.to_string()).unwrap_or_default(),
2012 None => " ".to_string(),
2013 };
2014 state.positional_params.join(&sep)
2015 }
2016 "#" => state.positional_params.len().to_string(),
2017 "?" => state.last_exit_code.to_string(),
2018 "-" => String::new(),
2019 "$" => "1".to_string(),
2020 "!" => String::new(),
2021 _ => get_var(state, target).unwrap_or_default(),
2022 }
2023}
2024
2025fn resolve_parameter_direct(parameter: &Parameter, state: &InterpreterState) -> String {
2026 match parameter {
2027 Parameter::Named(name) => resolve_named_var(name, state),
2028 Parameter::Positional(n) => {
2029 if *n == 0 {
2030 state.shell_name.clone()
2031 } else {
2032 state
2033 .positional_params
2034 .get(*n as usize - 1)
2035 .cloned()
2036 .unwrap_or_default()
2037 }
2038 }
2039 Parameter::Special(sp) => resolve_special(sp, state),
2040 Parameter::NamedWithIndex { name, index } => resolve_array_element(name, index, state),
2041 Parameter::NamedWithAllIndices { name, concatenate } => {
2042 resolve_all_elements(name, *concatenate, state)
2045 }
2046 }
2047}
2048
2049fn strip_quotes(s: &str) -> String {
2052 let s = s.trim();
2053 if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
2054 s[1..s.len() - 1].to_string()
2055 } else {
2056 s.to_string()
2057 }
2058}
2059
2060fn resolve_array_element(name: &str, index: &str, state: &InterpreterState) -> String {
2062 if let Some(val) = resolve_call_stack_element(name, index, state) {
2064 return val;
2065 }
2066 use crate::interpreter::VariableValue;
2067 let resolved = crate::interpreter::resolve_nameref_or_self(name, state);
2068 let Some(var) = state.env.get(&resolved) else {
2069 return String::new();
2070 };
2071 match &var.value {
2072 VariableValue::IndexedArray(map) => {
2073 let idx = simple_arith_eval(index, state);
2074 let actual_idx = if idx < 0 {
2075 let max_key = map.keys().next_back().copied().unwrap_or(0);
2076 let resolved = max_key as i64 + 1 + idx;
2077 if resolved < 0 {
2078 return String::new();
2079 }
2080 resolved as usize
2081 } else {
2082 idx as usize
2083 };
2084 map.get(&actual_idx).cloned().unwrap_or_default()
2085 }
2086 VariableValue::AssociativeArray(map) => {
2087 let key = strip_quotes(index);
2088 map.get(&key).cloned().unwrap_or_default()
2089 }
2090 VariableValue::Scalar(s) => {
2091 let idx = simple_arith_eval(index, state);
2092 if idx == 0 || idx == -1 {
2093 s.clone()
2094 } else {
2095 String::new()
2096 }
2097 }
2098 }
2099}
2100
2101fn resolve_array_element_mut(
2105 name: &str,
2106 index: &str,
2107 state: &mut InterpreterState,
2108) -> Result<String, RustBashError> {
2109 if let Some(val) = resolve_call_stack_element(name, index, state) {
2111 return Ok(val);
2112 }
2113 use crate::interpreter::VariableValue;
2114 let resolved = crate::interpreter::resolve_nameref_or_self(name, state);
2115
2116 let is_assoc = state
2118 .env
2119 .get(&resolved)
2120 .is_some_and(|v| matches!(&v.value, VariableValue::AssociativeArray(_)));
2121
2122 if is_assoc {
2123 let expanded = expand_arith_expression(index, state)?;
2125 let key = strip_quotes(&expanded);
2126 let val = state
2127 .env
2128 .get(&resolved)
2129 .and_then(|v| {
2130 if let VariableValue::AssociativeArray(map) = &v.value {
2131 map.get(&key).cloned()
2132 } else {
2133 None
2134 }
2135 })
2136 .unwrap_or_default();
2137 return Ok(val);
2138 }
2139
2140 let expanded = expand_arith_expression(index, state)?;
2142 let idx = crate::interpreter::arithmetic::eval_arithmetic(&expanded, state)?;
2143
2144 let val = state
2145 .env
2146 .get(&resolved)
2147 .map(|var| match &var.value {
2148 VariableValue::IndexedArray(map) => {
2149 let actual_idx = if idx < 0 {
2150 let max_key = map.keys().next_back().copied().unwrap_or(0);
2151 let resolved_idx = max_key as i64 + 1 + idx;
2152 if resolved_idx < 0 {
2153 return String::new();
2154 }
2155 resolved_idx as usize
2156 } else {
2157 idx as usize
2158 };
2159 map.get(&actual_idx).cloned().unwrap_or_default()
2160 }
2161 VariableValue::Scalar(s) => {
2162 if idx == 0 || idx == -1 {
2163 s.clone()
2164 } else {
2165 String::new()
2166 }
2167 }
2168 _ => String::new(),
2169 })
2170 .unwrap_or_default();
2171 Ok(val)
2172}
2173
2174fn resolve_call_stack_element(name: &str, index: &str, state: &InterpreterState) -> Option<String> {
2177 match name {
2178 "FUNCNAME" | "BASH_SOURCE" | "BASH_LINENO" => {}
2179 _ => return None,
2180 }
2181 let raw_idx = simple_arith_eval(index, state);
2182 let len = state.call_stack.len();
2185 let idx = if raw_idx < 0 {
2186 let resolved = len as i64 + raw_idx;
2187 if resolved < 0 {
2188 return Some(String::new());
2189 }
2190 resolved as usize
2191 } else {
2192 raw_idx as usize
2193 };
2194 if idx >= len {
2195 return Some(String::new());
2196 }
2197 let frame_idx = len - 1 - idx;
2198 let frame = &state.call_stack[frame_idx];
2199 Some(match name {
2200 "FUNCNAME" => frame.func_name.clone(),
2201 "BASH_SOURCE" => frame.source.clone(),
2202 "BASH_LINENO" => frame.lineno.to_string(),
2203 _ => String::new(),
2204 })
2205}
2206
2207pub(crate) fn simple_arith_eval(expr: &str, state: &InterpreterState) -> i64 {
2210 let trimmed = expr.trim();
2211 if let Ok(n) = trimmed.parse::<i64>() {
2213 return n;
2214 }
2215 if trimmed
2217 .chars()
2218 .all(|c| c.is_ascii_alphanumeric() || c == '_')
2219 {
2220 return read_var_immutable(state, trimmed);
2221 }
2222 0
2224}
2225
2226fn read_var_immutable(state: &InterpreterState, name: &str) -> i64 {
2228 let resolved = crate::interpreter::resolve_nameref_or_self(name, state);
2229 state
2230 .env
2231 .get(&resolved)
2232 .map(|v| v.value.as_scalar().parse::<i64>().unwrap_or(0))
2233 .unwrap_or(0)
2234}
2235
2236fn resolve_all_elements(name: &str, concatenate: bool, state: &InterpreterState) -> String {
2239 if let Some(vals) = get_call_stack_values(name, state) {
2241 let sep = if concatenate {
2242 match get_var(state, "IFS") {
2243 Some(s) => s.chars().next().map(|c| c.to_string()).unwrap_or_default(),
2244 None => " ".to_string(),
2245 }
2246 } else {
2247 " ".to_string()
2248 };
2249 return vals.join(&sep);
2250 }
2251 use crate::interpreter::VariableValue;
2252 let resolved = crate::interpreter::resolve_nameref_or_self(name, state);
2253 let Some(var) = state.env.get(&resolved) else {
2254 return String::new();
2255 };
2256 let values: Vec<&str> = match &var.value {
2257 VariableValue::IndexedArray(map) => map.values().map(|s| s.as_str()).collect(),
2258 VariableValue::AssociativeArray(map) => map.values().map(|s| s.as_str()).collect(),
2259 VariableValue::Scalar(s) => {
2260 if s.is_empty() {
2261 vec![]
2262 } else {
2263 vec![s.as_str()]
2264 }
2265 }
2266 };
2267 if concatenate {
2268 let sep = match get_var(state, "IFS") {
2269 Some(s) => s.chars().next().map(|c| c.to_string()).unwrap_or_default(),
2270 None => " ".to_string(),
2271 };
2272 values.join(&sep)
2273 } else {
2274 values.join(" ")
2275 }
2276}
2277
2278fn get_call_stack_values(name: &str, state: &InterpreterState) -> Option<Vec<String>> {
2281 match name {
2282 "FUNCNAME" => Some(
2283 state
2284 .call_stack
2285 .iter()
2286 .rev()
2287 .map(|f| f.func_name.clone())
2288 .collect(),
2289 ),
2290 "BASH_SOURCE" => Some(
2291 state
2292 .call_stack
2293 .iter()
2294 .rev()
2295 .map(|f| f.source.clone())
2296 .collect(),
2297 ),
2298 "BASH_LINENO" => Some(
2299 state
2300 .call_stack
2301 .iter()
2302 .rev()
2303 .map(|f| f.lineno.to_string())
2304 .collect(),
2305 ),
2306 _ => None,
2307 }
2308}
2309
2310fn get_vectorized_values(
2316 parameter: &Parameter,
2317 state: &InterpreterState,
2318 indirect: bool,
2319) -> Option<(Vec<String>, bool)> {
2320 let _ = indirect; match parameter {
2322 Parameter::NamedWithAllIndices { name, concatenate } => {
2323 Some((get_array_values(name, state), *concatenate))
2324 }
2325 Parameter::Special(SpecialParameter::AllPositionalParameters { concatenate }) => {
2326 Some((state.positional_params.clone(), *concatenate))
2327 }
2328 _ => None,
2329 }
2330}
2331
2332fn push_vectorized(
2335 results: Vec<String>,
2336 concatenate: bool,
2337 words: &mut Vec<WordInProgress>,
2338 state: &InterpreterState,
2339 in_dq: bool,
2340) {
2341 if concatenate {
2342 let sep = match get_var(state, "IFS") {
2343 Some(s) => s.chars().next().map(|c| c.to_string()).unwrap_or_default(),
2344 None => " ".to_string(),
2345 };
2346 let joined = results.join(&sep);
2347 push_segment(words, &joined, in_dq, in_dq);
2348 } else {
2349 for (i, v) in results.iter().enumerate() {
2350 if i > 0 {
2351 start_new_word(words);
2352 }
2353 push_segment(words, v, in_dq, in_dq);
2354 }
2355 }
2356}
2357
2358fn get_array_values(name: &str, state: &InterpreterState) -> Vec<String> {
2360 if let Some(vals) = get_call_stack_values(name, state) {
2362 return vals;
2363 }
2364 use crate::interpreter::VariableValue;
2365 let resolved = crate::interpreter::resolve_nameref_or_self(name, state);
2366 let Some(var) = state.env.get(&resolved) else {
2367 return Vec::new();
2368 };
2369 match &var.value {
2370 VariableValue::IndexedArray(map) => map.values().cloned().collect(),
2371 VariableValue::AssociativeArray(map) => map.values().cloned().collect(),
2372 VariableValue::Scalar(s) => {
2373 if s.is_empty() {
2374 vec![]
2375 } else {
2376 vec![s.clone()]
2377 }
2378 }
2379 }
2380}
2381
2382fn get_array_kv_pairs(parameter: &Parameter, state: &InterpreterState) -> Vec<(usize, String)> {
2386 match parameter {
2387 Parameter::NamedWithAllIndices { name, .. } => {
2388 if let Some(vals) = get_call_stack_values(name, state) {
2389 return vals.into_iter().enumerate().collect();
2390 }
2391 use crate::interpreter::VariableValue;
2392 let resolved = crate::interpreter::resolve_nameref_or_self(name, state);
2393 let Some(var) = state.env.get(&resolved) else {
2394 return Vec::new();
2395 };
2396 match &var.value {
2397 VariableValue::IndexedArray(map) => {
2398 map.iter().map(|(&k, v)| (k, v.clone())).collect()
2399 }
2400 VariableValue::AssociativeArray(map) => {
2401 map.values()
2404 .enumerate()
2405 .map(|(i, v)| (i, v.clone()))
2406 .collect()
2407 }
2408 VariableValue::Scalar(s) => {
2409 if s.is_empty() {
2410 vec![]
2411 } else {
2412 vec![(0, s.clone())]
2413 }
2414 }
2415 }
2416 }
2417 Parameter::Special(SpecialParameter::AllPositionalParameters { .. }) => state
2418 .positional_params
2419 .iter()
2420 .enumerate()
2421 .map(|(i, v)| (i, v.clone()))
2422 .collect(),
2423 _ => Vec::new(),
2424 }
2425}
2426
2427fn get_array_keys(name: &str, state: &InterpreterState) -> Vec<String> {
2429 if let Some(vals) = get_call_stack_values(name, state) {
2431 return (0..vals.len()).map(|i| i.to_string()).collect();
2432 }
2433 use crate::interpreter::VariableValue;
2434 let resolved = crate::interpreter::resolve_nameref_or_self(name, state);
2435 let Some(var) = state.env.get(&resolved) else {
2436 return Vec::new();
2437 };
2438 match &var.value {
2439 VariableValue::IndexedArray(map) => map.keys().map(|k| k.to_string()).collect(),
2440 VariableValue::AssociativeArray(map) => map.keys().cloned().collect(),
2441 VariableValue::Scalar(s) => {
2442 if s.is_empty() {
2443 vec![]
2444 } else {
2445 vec!["0".to_string()]
2446 }
2447 }
2448 }
2449}
2450
2451fn resolve_named_var(name: &str, state: &InterpreterState) -> String {
2452 match name {
2455 "LINENO" => return state.current_lineno.to_string(),
2456 "SECONDS" => return state.shell_start_time.elapsed().as_secs().to_string(),
2457 "_" => return state.last_argument.clone(),
2458 "PPID" => {
2459 return get_var(state, "PPID").unwrap_or_else(|| "1".to_string());
2460 }
2461 "UID" => {
2462 return get_var(state, "UID").unwrap_or_else(|| "1000".to_string());
2463 }
2464 "EUID" => {
2465 return get_var(state, "EUID").unwrap_or_else(|| "1000".to_string());
2466 }
2467 "BASHPID" => {
2468 return get_var(state, "BASHPID").unwrap_or_else(|| "1".to_string());
2469 }
2470 "SHELLOPTS" => return compute_shellopts(state),
2471 "BASHOPTS" => return compute_bashopts(state),
2472 "MACHTYPE" => return state.machtype.clone(),
2473 "HOSTTYPE" => return state.hosttype.clone(),
2474 "FUNCNAME" | "BASH_SOURCE" | "BASH_LINENO" => {
2475 return resolve_call_stack_scalar(name, state);
2476 }
2477 _ => {}
2478 }
2479 get_var(state, name).unwrap_or_default()
2480}
2481
2482fn compute_shellopts(state: &InterpreterState) -> String {
2484 let mut opts = Vec::new();
2485 if state.shell_opts.allexport {
2486 opts.push("allexport");
2487 }
2488 opts.push("braceexpand");
2490 if state.shell_opts.emacs_mode {
2491 opts.push("emacs");
2492 }
2493 if state.shell_opts.errexit {
2494 opts.push("errexit");
2495 }
2496 opts.push("hashall");
2498 if state.shell_opts.noclobber {
2499 opts.push("noclobber");
2500 }
2501 if state.shell_opts.noexec {
2502 opts.push("noexec");
2503 }
2504 if state.shell_opts.noglob {
2505 opts.push("noglob");
2506 }
2507 if state.shell_opts.nounset {
2508 opts.push("nounset");
2509 }
2510 if state.shell_opts.pipefail {
2511 opts.push("pipefail");
2512 }
2513 if state.shell_opts.posix {
2514 opts.push("posix");
2515 }
2516 if state.shell_opts.verbose {
2517 opts.push("verbose");
2518 }
2519 if state.shell_opts.vi_mode {
2520 opts.push("vi");
2521 }
2522 if state.shell_opts.xtrace {
2523 opts.push("xtrace");
2524 }
2525 opts.join(":")
2527}
2528
2529fn compute_bashopts(state: &InterpreterState) -> String {
2531 let o = &state.shopt_opts;
2532 let mut opts = Vec::new();
2533 if o.assoc_expand_once {
2535 opts.push("assoc_expand_once");
2536 }
2537 if o.autocd {
2538 opts.push("autocd");
2539 }
2540 if o.cdable_vars {
2541 opts.push("cdable_vars");
2542 }
2543 if o.cdspell {
2544 opts.push("cdspell");
2545 }
2546 if o.checkhash {
2547 opts.push("checkhash");
2548 }
2549 if o.checkjobs {
2550 opts.push("checkjobs");
2551 }
2552 if o.checkwinsize {
2553 opts.push("checkwinsize");
2554 }
2555 if o.cmdhist {
2556 opts.push("cmdhist");
2557 }
2558 if o.complete_fullquote {
2559 opts.push("complete_fullquote");
2560 }
2561 if o.direxpand {
2562 opts.push("direxpand");
2563 }
2564 if o.dirspell {
2565 opts.push("dirspell");
2566 }
2567 if o.dotglob {
2568 opts.push("dotglob");
2569 }
2570 if o.execfail {
2571 opts.push("execfail");
2572 }
2573 if o.expand_aliases {
2574 opts.push("expand_aliases");
2575 }
2576 if o.extdebug {
2577 opts.push("extdebug");
2578 }
2579 if o.extglob {
2580 opts.push("extglob");
2581 }
2582 if o.extquote {
2583 opts.push("extquote");
2584 }
2585 if o.failglob {
2586 opts.push("failglob");
2587 }
2588 if o.force_fignore {
2589 opts.push("force_fignore");
2590 }
2591 if o.globasciiranges {
2592 opts.push("globasciiranges");
2593 }
2594 if o.globskipdots {
2595 opts.push("globskipdots");
2596 }
2597 if o.globstar {
2598 opts.push("globstar");
2599 }
2600 if o.gnu_errfmt {
2601 opts.push("gnu_errfmt");
2602 }
2603 if o.histappend {
2604 opts.push("histappend");
2605 }
2606 if o.histreedit {
2607 opts.push("histreedit");
2608 }
2609 if o.histverify {
2610 opts.push("histverify");
2611 }
2612 if o.hostcomplete {
2613 opts.push("hostcomplete");
2614 }
2615 if o.huponexit {
2616 opts.push("huponexit");
2617 }
2618 if o.inherit_errexit {
2619 opts.push("inherit_errexit");
2620 }
2621 if o.interactive_comments {
2622 opts.push("interactive_comments");
2623 }
2624 if o.lastpipe {
2625 opts.push("lastpipe");
2626 }
2627 if o.lithist {
2628 opts.push("lithist");
2629 }
2630 if o.localvar_inherit {
2631 opts.push("localvar_inherit");
2632 }
2633 if o.localvar_unset {
2634 opts.push("localvar_unset");
2635 }
2636 if o.login_shell {
2637 opts.push("login_shell");
2638 }
2639 if o.mailwarn {
2640 opts.push("mailwarn");
2641 }
2642 if o.no_empty_cmd_completion {
2643 opts.push("no_empty_cmd_completion");
2644 }
2645 if o.nocaseglob {
2646 opts.push("nocaseglob");
2647 }
2648 if o.nocasematch {
2649 opts.push("nocasematch");
2650 }
2651 if o.nullglob {
2652 opts.push("nullglob");
2653 }
2654 if o.patsub_replacement {
2655 opts.push("patsub_replacement");
2656 }
2657 if o.progcomp {
2658 opts.push("progcomp");
2659 }
2660 if o.progcomp_alias {
2661 opts.push("progcomp_alias");
2662 }
2663 if o.promptvars {
2664 opts.push("promptvars");
2665 }
2666 if o.shift_verbose {
2667 opts.push("shift_verbose");
2668 }
2669 if o.sourcepath {
2670 opts.push("sourcepath");
2671 }
2672 if o.varredir_close {
2673 opts.push("varredir_close");
2674 }
2675 if o.xpg_echo {
2676 opts.push("xpg_echo");
2677 }
2678 opts.join(":")
2679}
2680
2681fn resolve_call_stack_scalar(name: &str, state: &InterpreterState) -> String {
2684 if state.call_stack.is_empty() {
2685 return String::new();
2686 }
2687 let frame = &state.call_stack[state.call_stack.len() - 1];
2688 match name {
2689 "FUNCNAME" => frame.func_name.clone(),
2690 "BASH_SOURCE" => frame.source.clone(),
2691 "BASH_LINENO" => frame.lineno.to_string(),
2692 _ => String::new(),
2693 }
2694}
2695
2696fn resolve_special(sp: &SpecialParameter, state: &InterpreterState) -> String {
2697 match sp {
2698 SpecialParameter::LastExitStatus => state.last_exit_code.to_string(),
2699 SpecialParameter::PositionalParameterCount => state.positional_params.len().to_string(),
2700 SpecialParameter::AllPositionalParameters { concatenate } => {
2701 if *concatenate {
2702 let sep = match get_var(state, "IFS") {
2704 Some(s) => s.chars().next().map(|c| c.to_string()).unwrap_or_default(),
2705 None => " ".to_string(),
2706 };
2707 state.positional_params.join(&sep)
2708 } else {
2709 state.positional_params.join(" ")
2710 }
2711 }
2712 SpecialParameter::ProcessId => "1".to_string(),
2713 SpecialParameter::LastBackgroundProcessId => String::new(),
2714 SpecialParameter::ShellName => state.shell_name.clone(),
2715 SpecialParameter::CurrentOptionFlags => {
2716 let mut flags = String::new();
2718 if state.shell_opts.allexport {
2719 flags.push('a');
2720 }
2721 if state.shell_opts.errexit {
2722 flags.push('e');
2723 }
2724 if state.shell_opts.noglob {
2725 flags.push('f');
2726 }
2727 flags.push('h');
2729 if state.shell_opts.noexec {
2730 flags.push('n');
2731 }
2732 if state.shell_opts.nounset {
2733 flags.push('u');
2734 }
2735 if state.shell_opts.verbose {
2736 flags.push('v');
2737 }
2738 if state.shell_opts.xtrace {
2739 flags.push('x');
2740 }
2741 flags.push('B');
2743 if state.shell_opts.noclobber {
2744 flags.push('C');
2745 }
2746 flags.push('s');
2748 flags
2749 }
2750 }
2751}
2752
2753fn get_var(state: &InterpreterState, name: &str) -> Option<String> {
2754 let resolved = crate::interpreter::resolve_nameref_or_self(name, state);
2755 if let Some(bracket_pos) = resolved.find('[')
2758 && resolved.ends_with(']')
2759 {
2760 let arr_name = &resolved[..bracket_pos];
2761 let index_raw = &resolved[bracket_pos + 1..resolved.len() - 1];
2762 let index = expand_simple_dollar_vars(index_raw, state);
2764 return Some(resolve_array_element(arr_name, &index, state));
2765 }
2766 state
2767 .env
2768 .get(&resolved)
2769 .map(|v| v.value.as_scalar().to_string())
2770}
2771
2772fn expand_simple_dollar_vars(s: &str, state: &InterpreterState) -> String {
2775 if !s.contains('$') {
2776 return s.to_string();
2777 }
2778 let mut result = String::new();
2779 let chars: Vec<char> = s.chars().collect();
2780 let mut i = 0;
2781 while i < chars.len() {
2782 if chars[i] == '$' && i + 1 < chars.len() {
2783 i += 1;
2784 let mut var_name = String::new();
2785 while i < chars.len() && (chars[i].is_ascii_alphanumeric() || chars[i] == '_') {
2786 var_name.push(chars[i]);
2787 i += 1;
2788 }
2789 if !var_name.is_empty() {
2790 let resolved_var = crate::interpreter::resolve_nameref_or_self(&var_name, state);
2791 let val = state
2792 .env
2793 .get(&resolved_var)
2794 .map(|v| v.value.as_scalar().to_string())
2795 .unwrap_or_default();
2796 result.push_str(&val);
2797 } else {
2798 result.push('$');
2799 }
2800 } else {
2801 result.push(chars[i]);
2802 i += 1;
2803 }
2804 }
2805 result
2806}
2807
2808fn should_use_default(
2809 val: &str,
2810 test_type: &ParameterTestType,
2811 state: &InterpreterState,
2812 parameter: &Parameter,
2813) -> bool {
2814 match test_type {
2815 ParameterTestType::UnsetOrNull => val.is_empty() || is_unset(state, parameter),
2816 ParameterTestType::Unset => is_unset(state, parameter),
2817 }
2818}
2819
2820fn should_use_indirect_default(
2823 val: &str,
2824 target_name: &str,
2825 test_type: &ParameterTestType,
2826 state: &InterpreterState,
2827) -> bool {
2828 if target_name.is_empty() {
2829 return true;
2831 }
2832 let is_target_unset = is_unset(state, &Parameter::Named(target_name.to_string()));
2833 match test_type {
2834 ParameterTestType::UnsetOrNull => val.is_empty() || is_target_unset,
2835 ParameterTestType::Unset => is_target_unset,
2836 }
2837}
2838
2839fn is_dynamic_special(name: &str) -> bool {
2841 matches!(
2842 name,
2843 "LINENO"
2844 | "SECONDS"
2845 | "_"
2846 | "PPID"
2847 | "UID"
2848 | "EUID"
2849 | "BASHPID"
2850 | "SHELLOPTS"
2851 | "BASHOPTS"
2852 | "MACHTYPE"
2853 | "HOSTTYPE"
2854 | "FUNCNAME"
2855 | "BASH_SOURCE"
2856 | "BASH_LINENO"
2857 )
2858}
2859
2860fn is_unset(state: &InterpreterState, parameter: &Parameter) -> bool {
2861 match parameter {
2862 Parameter::Named(name) => {
2863 if is_dynamic_special(name) {
2864 return false;
2865 }
2866 let resolved = crate::interpreter::resolve_nameref_or_self(name, state);
2867 match state.env.get(&resolved) {
2868 None => true,
2869 Some(var) => {
2870 use crate::interpreter::VariableValue;
2873 match &var.value {
2874 VariableValue::IndexedArray(map) => !map.contains_key(&0),
2875 _ => false,
2876 }
2877 }
2878 }
2879 }
2880 Parameter::Positional(n) => {
2881 if *n == 0 {
2882 false
2883 } else {
2884 state.positional_params.get(*n as usize - 1).is_none()
2885 }
2886 }
2887 Parameter::Special(_) => false,
2888 Parameter::NamedWithIndex { name, index } => {
2889 if is_dynamic_special(name) {
2890 return false;
2891 }
2892 let resolved = crate::interpreter::resolve_nameref_or_self(name, state);
2893 match state.env.get(&resolved) {
2894 None => true,
2895 Some(var) => {
2896 use crate::interpreter::VariableValue;
2897 match &var.value {
2898 VariableValue::IndexedArray(map) => {
2899 let idx = simple_arith_eval(index, state);
2900 let actual_idx = if idx < 0 {
2901 let max_key = map.keys().next_back().copied().unwrap_or(0);
2902 let resolved_idx = max_key as i64 + 1 + idx;
2903 if resolved_idx < 0 {
2904 return true;
2905 }
2906 resolved_idx as usize
2907 } else {
2908 idx as usize
2909 };
2910 !map.contains_key(&actual_idx)
2911 }
2912 VariableValue::AssociativeArray(map) => !map.contains_key(index.as_str()),
2913 VariableValue::Scalar(_) => {
2914 let idx = simple_arith_eval(index, state);
2915 idx != 0 && idx != -1
2916 }
2917 }
2918 }
2919 }
2920 }
2921 Parameter::NamedWithAllIndices { name, .. } => {
2922 if is_dynamic_special(name) {
2923 return false;
2924 }
2925 let resolved = crate::interpreter::resolve_nameref_or_self(name, state);
2926 !state.env.contains_key(&resolved)
2927 }
2928 }
2929}
2930
2931fn parameter_name(parameter: &Parameter) -> String {
2932 match parameter {
2933 Parameter::Named(name) => name.clone(),
2934 Parameter::Positional(n) => n.to_string(),
2935 Parameter::Special(sp) => match sp {
2936 SpecialParameter::LastExitStatus => "?".to_string(),
2937 SpecialParameter::PositionalParameterCount => "#".to_string(),
2938 SpecialParameter::AllPositionalParameters { concatenate } => {
2939 if *concatenate {
2940 "*".to_string()
2941 } else {
2942 "@".to_string()
2943 }
2944 }
2945 SpecialParameter::ProcessId => "$".to_string(),
2946 SpecialParameter::LastBackgroundProcessId => "!".to_string(),
2947 SpecialParameter::ShellName => "0".to_string(),
2948 SpecialParameter::CurrentOptionFlags => "-".to_string(),
2949 },
2950 Parameter::NamedWithIndex { name, index } => format!("{name}[{index}]"),
2951 Parameter::NamedWithAllIndices { name, .. } => name.clone(),
2952 }
2953}
2954
2955fn parse_arithmetic_value(expr: &str) -> i64 {
2957 let trimmed = expr.trim();
2958 trimmed.parse::<i64>().unwrap_or(0)
2959}
2960
2961fn expand_raw_string_ctx(
2964 raw: &str,
2965 state: &InterpreterState,
2966 in_dq: bool,
2967) -> Result<String, RustBashError> {
2968 let options = parser_options();
2969 let pieces = brush_parser::word::parse(raw, &options)
2970 .map_err(|e| RustBashError::Parse(e.to_string()))?;
2971
2972 let mut words: Vec<WordInProgress> = vec![Vec::new()];
2973 for piece_ws in &pieces {
2974 expand_raw_piece(&piece_ws.piece, &mut words, state, in_dq)?;
2975 }
2976 let result = finalize_no_split(words);
2977 Ok(result.join(" "))
2978}
2979
2980fn expand_raw_string_mut_ctx(
2981 raw: &str,
2982 state: &mut InterpreterState,
2983 in_dq: bool,
2984) -> Result<String, RustBashError> {
2985 let options = parser_options();
2986 let pieces = brush_parser::word::parse(raw, &options)
2987 .map_err(|e| RustBashError::Parse(e.to_string()))?;
2988
2989 let mut words: Vec<WordInProgress> = vec![Vec::new()];
2990 for piece_ws in &pieces {
2991 expand_raw_piece_mut(&piece_ws.piece, &mut words, state, in_dq)?;
2992 }
2993 let result = finalize_no_split(words);
2994 Ok(result.join(" "))
2995}
2996
2997fn expand_raw_piece(
3001 piece: &WordPiece,
3002 words: &mut Vec<WordInProgress>,
3003 state: &InterpreterState,
3004 in_dq: bool,
3005) -> Result<bool, RustBashError> {
3006 if in_dq && let WordPiece::SingleQuotedText(s) = piece {
3007 push_segment(words, &format!("'{s}'"), true, true);
3009 return Ok(false);
3010 }
3011 expand_word_piece(piece, words, state, in_dq)
3012}
3013
3014fn expand_raw_piece_mut(
3016 piece: &WordPiece,
3017 words: &mut Vec<WordInProgress>,
3018 state: &mut InterpreterState,
3019 in_dq: bool,
3020) -> Result<bool, RustBashError> {
3021 if in_dq && let WordPiece::SingleQuotedText(s) = piece {
3022 push_segment(words, &format!("'{s}'"), true, true);
3023 return Ok(false);
3024 }
3025 expand_word_piece_mut(piece, words, state, in_dq)
3026}
3027
3028pub(crate) fn expand_arith_expression(
3031 expr: &str,
3032 state: &mut InterpreterState,
3033) -> Result<String, RustBashError> {
3034 if !expr.contains('$') && !expr.contains('`') && !expr.contains('\'') && !expr.contains('"') {
3036 return Ok(expr.to_string());
3037 }
3038 let word = ast::Word {
3040 value: expr.to_string(),
3041 loc: None,
3042 };
3043 expand_word_to_string_mut(&word, state)
3044}