Skip to content

feat: implement $_ special parameter for last command argument#1030

Open
lu-zero wants to merge 1 commit intoreubeno:mainfrom
lu-zero:dollar_underscore
Open

feat: implement $_ special parameter for last command argument#1030
lu-zero wants to merge 1 commit intoreubeno:mainfrom
lu-zero:dollar_underscore

Conversation

@lu-zero
Copy link
Copy Markdown
Contributor

@lu-zero lu-zero commented Feb 28, 2026

Add support for the $_ special parameter, which expands to the last argument of the previous command.

  • Add LastCommandArgument variant to SpecialParameter enum
  • Update PEG parser to match $_ only when not followed by variable-name chars (so $__phase is correctly parsed as variable reference)
  • Add last_command_argument field to Shell struct
  • Set last command argument before each command execution
  • Add test cases for $_ behavior

@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 28, 2026

Public API changes for crate: brush-core

Added items

+pub fn brush_core::Shell<SE>::update_last_arg_variable(&mut self, last_arg: core::option::Option<&str>)
+pub fn brush_core::Shell<SE>::update_last_arg_variable(&mut self, last_arg: core::option::Option<&str>)
+pub fn brush_core::ShellState::update_last_arg_variable(&mut self, last_arg: core::option::Option<&str>)

Performance Benchmark Report

Benchmark name Baseline (μs) Test/PR (μs) Delta (μs) Delta %
clone_shell_object 17.15 μs 17.28 μs 0.13 μs ⚪ Unchanged
eval_arithmetic 0.15 μs 0.14 μs -0.00 μs ⚪ Unchanged
expand_one_string 1.52 μs 1.67 μs 0.15 μs 🟠 +10.07%
for_loop 25.51 μs 29.04 μs 3.52 μs 🟠 +13.81%
full_peg_complex 58.44 μs 58.07 μs -0.37 μs ⚪ Unchanged
full_peg_for_loop 6.20 μs 6.23 μs 0.03 μs ⚪ Unchanged
full_peg_nested_expansions 16.73 μs 16.88 μs 0.15 μs 🟠 +0.89%
full_peg_pipeline 4.33 μs 4.23 μs -0.10 μs ⚪ Unchanged
full_peg_simple 1.83 μs 1.77 μs -0.06 μs 🟢 -3.17%
function_call 2.70 μs 3.00 μs 0.30 μs 🟠 +10.98%
instantiate_shell 54.50 μs 54.37 μs -0.13 μs ⚪ Unchanged
instantiate_shell_with_init_scripts 25811.02 μs 26574.85 μs 763.83 μs 🟠 +2.96%
parse_peg_bash_completion 2057.07 μs 2061.50 μs 4.43 μs ⚪ Unchanged
parse_peg_complex 19.01 μs 18.75 μs -0.26 μs ⚪ Unchanged
parse_peg_for_loop 1.98 μs 1.95 μs -0.03 μs ⚪ Unchanged
parse_peg_pipeline 2.10 μs 2.12 μs 0.02 μs ⚪ Unchanged
parse_peg_simple 1.09 μs 1.07 μs -0.02 μs ⚪ Unchanged
run_echo_builtin_command 17.95 μs 18.62 μs 0.67 μs ⚪ Unchanged
tokenize_sample_script 3.49 μs 3.44 μs -0.06 μs 🟢 -1.58%

Code Coverage Report: Only Changed Files listed

Package Base Coverage New Coverage Difference
brush-core/src/commands.rs 🟢 91.74% 🟢 91.91% 🟢 0.17%
brush-core/src/results.rs 🟢 83.33% 🟢 80.16% 🔴 -3.17%
brush-core/src/shell/state.rs 🔴 0% 🟢 100% 🟢 100%
brush-core/src/wellknownvars.rs 🟢 84.63% 🟢 84.67% 🟢 0.04%
brush-parser/src/ast.rs 🔴 45.21% 🔴 46.44% 🟢 1.23%
Overall Coverage 🟢 75.01% 🟢 75.06% 🟢 0.05%

Minimum allowed coverage is 70%, this run produced 75.06%

Test Summary: bash-completion test suite

Outcome Count Percentage
✅ Pass 1580 74.92
❗️ Error 17 0.81
❌ Fail 158 7.49
⏩ Skip 339 16.07
❎ Expected Fail 13 0.62
✔️ Unexpected Pass 2 0.09
📊 Total 2109 100.00

@lu-zero lu-zero force-pushed the dollar_underscore branch from 690b602 to 1581976 Compare March 1, 2026 07:25
@reubeno reubeno added the area: compat Related to compatibility with standard shells label Mar 1, 2026
@lu-zero lu-zero force-pushed the dollar_underscore branch from 8810085 to e6f83fa Compare March 1, 2026 15:26
@lu-zero
Copy link
Copy Markdown
Contributor Author

lu-zero commented Mar 1, 2026

Now it should be cleaner

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds bash-compatible handling for $_ by initializing and updating the _ shell variable to track the last argument of the previous command, and updates compatibility tests to cover/enable this behavior (notably #479).

Changes:

  • Initialize _ to $0 during well-known variable setup and update _ from command execution paths.
  • Set child-process _ env var for external commands to the resolved executable path (bash-like behavior).
  • Add/adjust compat tests for $_ and remove the known-failure flag for the existing dollar-_ case.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
xtask/src/test.rs Comment wrapping (no functional change).
brush-shell/tests/cases/compat/well_known_vars.yaml Removes known_failure for the dollar-_ compat case.
brush-shell/tests/cases/compat/special_parameters.yaml Adds a basic compat case covering $_ updates across sequential commands.
brush-parser/src/tokenizer.rs Doc comment wrapping (no functional change).
brush-core/src/wellknownvars.rs Initializes _ to the current shell name ($0).
brush-core/src/shell/state.rs Adds ShellState::update_last_arg_variable helper to update _.
brush-core/src/shell.rs Doc comment wrapping (no functional change).
brush-core/src/interp.rs Comment wrapping/formatting (no functional change).
brush-core/src/commands.rs Updates _ after builtin/function/external execution; sets child _ env var for external commands.
brush-builtins/src/read.rs Doc comment wrapping (no functional change).

Comment on lines 537 to +542
// Strip the function name off args.
let result = invoke_shell_function(func_registration, cmd_context, &self.args[1..]).await;

// Update $_ after command execution.
shell.update_last_arg_variable(last_arg.as_deref());

Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$_ (via _ variable) is updated only after invoke_shell_function returns, which means the function body will see the previous command’s value for $_ instead of the last argument of the function invocation (bash updates _ for the duration of the command being executed, i.e., after argument expansion but before running the builtin/function body). Consider moving the _ update to just before invoking the function (and likewise for the builtin paths) so $_ is correct inside the executed command, not just for the next command.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +4
- name: "$_ - last command argument"
stdin: |
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description mentions adding a SpecialParameter variant / PEG changes for parsing $_, but in the current codebase $_ is still parsed as a normal named variable (_) (no SpecialParameter addition found). If the intent is to implement this via the regular _ variable (as this PR does by setting _ in the environment), the PR description should be updated to reflect that; otherwise the missing parser/AST changes need to be added.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +10
- name: "$_ - last command argument"
stdin: |
: 0
echo "$_"
: 0 1 2
echo "$_"
echo "first command"
echo "$_"
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new $_ case covers basic sequential commands, but it doesn’t exercise the tricky scoping/update boundaries where bash behavior tends to differ (e.g., inside a function body immediately after invocation, inside a subshell/command substitution, and across pipelines/backgrounded commands). Adding a few compat cases covering those would help prevent regressions and confirm the update timing matches bash.

Copilot generated this review using guidance from repository custom instructions.
Copy link
Copy Markdown
Owner

@reubeno reubeno left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking pretty good! Just a few comments.

);

// Update $_ after command execution.
shell.update_last_arg_variable(last_arg.as_deref());
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In each of these cases, we have an owned String of the last argument (or at least an Option of one). Couldn't we save a string clone by having the setter in Shell require the owned String?

: 0 1 2
echo "$_"
echo "first command"
echo "$_"
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add a few test cases that cover other scenarios? e.g.:

  • Pipeline with multiple commands
  • Commands with && / ||
  • Command substitution / subshell
  • Builtin invocation
  • Function invocation
  • Commands that fail

We typically don't need to go so broad but because there are multiple code paths for handling different kinds of commands here, I think these will be important to ensure compatibility (and prevent us from regressing it in the future).

@lu-zero lu-zero force-pushed the dollar_underscore branch 2 times, most recently from 32ceb25 to 8629cdd Compare March 1, 2026 21:36
Add support for $_, which expands to the last argument of the previous
command. This is implemented as a regular shell variable, not a special
parameter, matching bash semantics.

- Initialize $_ to $0 (shell name) at startup in wellknownvars.rs
- Update $_ to the last argument after each command execution
- Set _ env var to the resolved command path for external commands
- Add test cases for $_ behavior

fix: properly clear $_ after assignment statements

Assignment-only statements should clear $_ (set to empty string) to
match bash behavior. Also use the update_last_arg_variable helper
consistently throughout the codebase.
@lu-zero lu-zero force-pushed the dollar_underscore branch from 8629cdd to aa48d0b Compare March 2, 2026 17:17
@reubeno reubeno added the state: needs review PR or issue author is waiting for a review label Mar 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: compat Related to compatibility with standard shells state: needs review PR or issue author is waiting for a review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants