Skip to content

fix(interpreter): extglob, nocasematch, globstar, substrings and $'...' quoting like bash - #2632

Merged
chaliy merged 12 commits into
mainfrom
claude/project-thread-eafupr-bb-expand
Oct 8, 2026
Merged

chaliy merged 12 commits into
mainfrom
claude/project-thread-eafupr-bb-expand

Conversation

@chaliy

@chaliy chaliy commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Requested by Михайло · project thread

What changed

  • Pattern matching: [[ == ]] always matches with extglob (bash 5.2). shopt nocasematch ignores case for case, [[ == / =~ ]] and ${v/pat/rep}. !(..) can match an empty prefix; escaped \| and quoted text inside an extglob group are literal; an unbalanced group is plain text.
  • Substrings: ${s:1:-1} counts back from the end, an out-of-range offset selects nothing, a negative array/$@ length aborts with substring expression < 0, and ${#@}/${#*} count positional parameters.
  • Globbing: globstar ** matches component by component with relative output (d/** gives d/), duplicates removed; d/*/ keeps only directories with the slash.
  • $'...': stays literal inside double quotes and here-documents; type/declare -f print it back correctly.
  • Marker bytes: words mixing quoted and unquoted parts no longer use in-band \x01/\x02 markers, so values containing those bytes survive.
  • Process substitution: <(..)/>(..) run in a subshell, so variables, cwd, options and $? no longer leak into the parent.
  • shopt: unknown names are reported while valid names in the same call still apply.

Why

BashBox corpus batch: 24 of 29 skipped cases across shopt-pattern, word-expander, shopt, shopt-glob, ansi-c-quoting, marker-bytes and process-substitution now pass. The 5 left keep specific skip reasons: 4 expect /dev/fd/63 (bashkit uses per-tenant /dev/fd/proc_sub_<n> on purpose, see limitations), 1 needs read-time alias expansion.

Before / After

x=abc; [[ $x == +([a-c]) ]] && echo extglob-in-cond
shopt -s nocasematch; case ABC in a*) echo nocase;; esac; shopt -u nocasematch
s=hello; set -- a b; echo ${s:1:-1} "[${s: -10}]" ${#@}
echo "$'q'"; v=$(printf 'a\001b'); echo ${#v}
before after (= bash 5.2)
(nothing) / (nothing) / ello [hello] 3 / q / 2 extglob-in-cond / nocase / ell [] 2 / $'q' / 3

Risk

  • Medium. [[ ]] now does extglob matching without shopt, which exposed exponential backtracking (+(a|aa) against 60 a's plus b hung). Every glob/extglob match now draws from a shared MAX_GLOB_STEPS budget (100k); TM-DOS-031 updated with a new test.
  • Two integration tests changed expectations to bash's: /skills/*/ prints trailing slashes, and ${arr[@]:1:-1} exits 1 with bash's error.

Checklist

  • Tests added or updated
  • Backward compatibility considered

Generated by Claude Code

chaliy added 10 commits October 8, 2026 04:01
[[ == ]] always matches with extglob (bash 5.2). shopt nocasematch folds
case for case, [[ ==/=~ ]] and ${v/pat/rep}. !(..) may consume an empty
prefix; escaped | and quotes inside a group stay literal; an unbalanced
group is literal. Extglob matching now spends a shared step budget
(TM-DOS-031).
${s:off:-n} counts back from the end, an offset before the start or past
the end selects nothing, a negative array/positional length is bash's
'substring expression < 0' line abort, and ${#@}/${#*} count the
positional parameters.
Each invalid name is reported and skipped; the valid names in the same
call still take effect. shopt -s/-u with -o keeps status 0 on a bad name,
as bash does. Document execution-time alias expansion as a limitation.
** under globstar matches zero or more directories in any component
position (files too when last), with relative output and bash's d/ vs d
spelling for the zero-directory match. A trailing / keeps only
directories and stays on each match (d/*/ -> d/e/).
A $ before ' or the closing " in double quotes starts no expansion
("$'q'" and "$"'q' print $'q' and $q); here-document bodies keep
$'a' as written. Function listings print $'...' as the single-quoted
text it decodes to and $"..." as "...", as bash does.
Mixed quoted/unquoted words now mark their quoted spans with the
parser's quote-boundary markers, which parse_word turns into per-part
quotedness, instead of in-band \x01/\x02 that expansion stripped from
every value. Field splitting picks its protected-span markers from
characters absent from the data, like the empty-field sentinel.
<(list) and >(list) no longer leak variable, cwd, option or $? changes
into the parent shell, and exit inside one ends only the substitution.
Document the remaining proc-sub gaps (/dev/fd/63 numbering, joining to
adjacent word text) as limitations.
@chaliy chaliy self-assigned this Oct 8, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bashkit 6fd50dd Commit Preview URL

Branch Preview URL
Oct 08 2026, 04:31 AM

@chaliy
chaliy merged commit 8ccdc93 into main Oct 8, 2026
49 checks passed
@chaliy
chaliy deleted the claude/project-thread-eafupr-bb-expand branch October 8, 2026 04:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant