BATsh

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

    directory contains spaces.

  - SH `cd` QUOTING: the cd builtin now dequotes its argument like every
    other command word, so cd "a b", cd 'dir', and cd "$VAR/x" reach
    chdir() as a plain path.  Previously the quote characters were kept
    and cd failed with "No such file or directory".  (t/0026 CD01-CD03.)

  - SH SUBSHELL REDIRECT FAILURE: a subshell whose redirection cannot be
    opened, e.g. "( read L ) < /no/such/file", now reports the error and
    returns a non-zero status WITHOUT running its body.  Previously it
    fell through and ran the body against the inherited stdin, which
    blocks forever when the body reads stdin (cat, read).  Any handle
    already redirected is restored before returning.  (t/0026 RF01; RF02
    guards that a working redirect still reads the file.)  New test
    t/0026-sh-cd-and-redir-fail.t (5 checks).  Perl 5.005_03 compatible.

  - NEW SH BUILTIN getopts: the POSIX single-character option parser,
    the last remaining builtin previously listed as unimplemented.

      getopts optstring name [arg ...]

    Called in a loop, it sets the variable named by "name" to each
    option letter in turn (and OPTARG to an option's argument), tracks
    progress through the argument list in OPTIND (1-based, reset to 1
    by the script before an independent parse), and returns non-zero
    when the options are exhausted so a "while getopts ...; do ...;
    done" loop ends by itself.  Supports: a ':' after a letter in the
    optstring to mark an argument-taking option; the separate
    (-o value) and attached (-ovalue) option-argument forms; clustered
    flags (-abc == -a -b -c); the "--" end-of-options marker (consumed)
    and a leading non-option word (stops parsing, left in place);
    parsing the positional parameters when no explicit args are given;
    and both error-reporting modes -- the default (a diagnostic to
    STDERR and name='?') and the silent mode selected by a leading ':'
    in the optstring (no message; name='?' for an unknown option or
    ':' for a missing argument, with the offending letter in OPTARG).
    The intra-argument character offset used for clustered options is
    internal state that is restarted whenever the script resets OPTIND,
    matching bash's behaviour for a fresh getopts loop; it is also
    reset between top-level runs so one script's half-finished loop
    cannot leak into the next.  All Perl 5.005_03 compatible
    (index/substr scanning, no prototypes, package-variable state).
    New test t/0023-sh-getopts.t (23 checks).

  - Fixed SH "shift N": the builtin accepted an optional count in its
    implementation (shift 1..9 positions) but the dispatcher called it
    with no argument, so "shift 3" and "shift $((OPTIND-1))" always
    shifted by exactly one -- silently corrupting any script that
    shifted more than one positional parameter at a time, including the
    idiomatic "shift $((OPTIND - 1))" that follows a getopts loop.  The
    dispatcher now passes the count through.  (t/0023: GO16/GO17.)

  - Fixed inline single-line function bodies that contain a control
    structure.  A body such as

      loop() { for x in a b c; do echo "$x"; done; }
      count() { i=0; while [ $i -lt 3 ]; do echo $i; i=$((i+1)); done; }
      chk()  { if [ "$1" = yes ]; then echo Y; else echo N; fi; }

    was split naively on ';' at definition time, tearing the loop or
    conditional into fragments ("while C" / "do B" / "done") that no
    longer reassembled into a runnable block -- the construct silently
    produced no output, or ran "done"/"fi" as an external command.
    _parse_function() now detects a control-structure keyword in
    command position in an inline body (new helper
    _inline_body_has_control(), quote/substitution aware) and, when
    present, keeps the whole body as one line so _run_lines() applies
    the same inline-control handling it already uses for a control
    structure typed directly on one physical line (including the
    "prefix; control" split, so "setup; while ...; do ...; done"
    works).  A trailing ';' before the closing brace is dropped so the
    inline "; done"/"; fi"/"; esac" terminator sits where the block
    parsers expect it.  Bodies of only simple commands
    (greet() { echo hi; echo bye; }) still split on ';' exactly as
    before.  All Perl 5.005_03 compatible.  (t/0023: GO14/GO15 exercise
    getopts inside such a function; the fix is general to all inline
    function bodies.)

  - Fixed the SH "$*" special parameter, which was never expanded and
    printed literally: the expansion pass substituted "$@" but not
    "$*", despite the code comment naming both.  "$*" now expands to
    the space-joined positional parameters, like "$@".  This is the
    parameter a script reads after "shift $((OPTIND - 1))" to see the
    operands left by a getopts loop.  (t/0023: GO19.)

  - Fixed the SH "echo" builtin's glob decision, which inspected the
    raw source text for *, ?, or [ with a plain pattern match.  That
    match also fired on the '*' of the "$*" parameter, on the '?' of
    "$?", and on any metacharacter inside quotes -- so
    echo "  x: $*" was needlessly run through word-splitting/globbing,
    collapsing its leading and internal whitespace, and a quoted
    echo "*.txt" risked being globbed against the current directory.
    A new quote- and parameter-aware scanner (_raw_has_glob) triggers
    globbing only on a genuine unquoted glob metacharacter that was
    actually written in the script, skipping quoted regions, $NAME /
    $* / $? parameter references, and ${...} expansions.  Unquoted
    "echo *.txt" still globs as before.  (t/0023: GO20/GO21.)

  - Extended shell features: extglob, brace expansion, here-strings,
    process substitution, select, alias/unalias, exec, and subshell
    command groups (v0.07).
      * shopt -s/-u extglob enables ?(),*(),+(),@(),!() pattern-list
        operators in case patterns and in the ${VAR%pat}-family
        parameter-expansion patterns (_extglob_scan/_extglob_split_alts,
        shared by _case_glob_to_re() and _glob_to_re()). Fixed two
        existing case-pattern parsers (_case_split_patterns and
        _case_parse_clause) to be paren-depth-aware so an extglob
        group's internal '|' and ')' are not mistaken for the
        pattern-separator '|' or the clause-terminator ')'.
      * {a,b,c} and {1..5}/{a..e}[..step] brace expansion runs
        lexically on the raw line (_brace_expand_line and friends),
        protecting quotes, ${...}, $(...), $((...)), `...`, and
        <(...)/>(...) as opaque regions; wired into _exec_line and
        _expand_word_list (for-loop lists).
      * cmd <<< word here-strings (_sh_strip_herestring), reusing the
        here-document temp-file machinery. Fixed a pre-existing
        _hd_detect() bug where "<<<" was only rejected as a
        here-document opener when matched starting at its FIRST '<';
        the scan then re-tried from the second '<' and matched "<<"
        there, silently mistreating "cmd <<< word" as a broken
        here-document. _hd_detect() now skips all three characters.

Changes  view on Meta::CPAN


  - BATsh::Env::sync_to_env no longer copies the batch-parameter
    pseudo keys (%0..%9, %*, %%V FOR variables) into %ENV; they are
    not legal environment variable names and polluted the child
    environment of external commands.

  - t/0015-cp932.t: new regression suite (20 checks, US-ASCII
    source with \xNN escapes) covering echo/ECHO of dame-moji,
    %VAR% and Env-bridge round-trips, pipeline/backtick/caret
    false-positive protection, uc() safety, character-based
    ${#VAR} / ${VAR:N:L} / %VAR:~n,m%, case patterns, redirects,
    CP932 filenames (test -f, IF EXIST), FOR /F over a CP932 file,
    ${VAR^^}, IF string comparison, %ENV export, auto-detection,
    UTF-8 pass-through, and enc/dec bijectivity.

  - eg/13_cp932_demo.pl: runnable demonstration (US-ASCII source)
    of a CP932 mixed CMD/SH script.

  - t/9010-encoding.t: BATsh::MB added to the US-ASCII source
    check list.

  - Fixed a pre-existing echo bug: the SH "echo" builtin decided
    whether to apply filename globbing by testing the already
    variable-expanded $rest for * ? [ characters. This meant a
    variable whose *value* happened to be a glob metacharacter
    (e.g. getopts setting $opt to "?" on an unknown option, or ":"
    on a missing argument) could be silently glob-matched against
    the current directory and replaced by whatever single-character
    filename happened to match, instead of being echoed literally.
    The glob-or-not decision is now made from the raw, pre-expansion
    source text instead, so only a glob metacharacter actually
    written in the script (e.g. "echo *.txt") triggers globbing;
    a variable's expanded value is never re-subject to globbing,
    matching the same "expansion results are not re-expanded"
    principle already used for tilde expansion. (t/0021:
    EF25/EF26 regression tests.)

  - Practical-level fixes to the pure-Perl SH and CMD interpreters
    (bugs found by exercising real scripts; all covered by the new
    t/0022-sh-compound-fixes.t regression suite, 16 checks):
      * A simple-command prefix followed on the SAME physical line by
        a control structure introduced with ';' now dispatches the
        control structure through its block parser instead of handing
        the pieces to /bin/sh -- e.g. x=""; if [ -z "$x" ]; then ...;
        fi, i=0; while ...; done, v=cat; case ...; esac
        (_run_lines/_find_control_split).
      * Inline "if COND; then A; else B; fi" (and elif) now honours
        the else/elif branch; the old single-line parser dropped it
        (_parse_if via _inline_has_terminator/_inline_expand).
      * Nested "if ... fi" is depth-aware so an inner fi no longer
        closes the outer if, both multi-line and inline
        (_parse_if body collector + _if_depth_delta).
      * Escaped double quotes inside a double-quoted word are handled
        per POSIX: echo "she said \"hi\"" -> she said "hi"
        (_arr_dequote rewritten with backslash escaping).
      * A for-loop / array list built from $(...) no longer leaks a
        stray ")" token: for f in $(echo a b c); do ...; done, and
        arr=($(echo x y z)) (_arr_split_words made substitution-aware).
      * A trailing "# comment" is stripped from SH command lines when
        the '#' begins a word and is unquoted, while $#, ${#var},
        ${var#pat}, an in-word '#' (a#b, http://h#frag) and quoted
        '#' are left intact, and here-document bodies keep their '#'
        (_strip_sh_comment, applied per physical line in _run_lines).
      * A control structure used as a pipeline element or && / ||
        operand now runs correctly: cmd | while read x; do ...; done,
        cmd | for i in ...; do ...; done, true && for ...; done.
        _split_sh_compound became control-structure-grouping aware so
        a ';' / && / || inside a while/for/if/case/until/select block
        is no longer treated as a top-level separator, and a single
        compound command reaching _exec_line_impl as a pipe/operand is
        routed through the block runner (_seg_is_control). (The
        multi-line form with "do" on its own line after a pipe remains
        a known limitation; the inline "; do" form is supported.)
      * Single-line CMD "IF cond (body) ELSE (body)" (and IF EXIST /
        IF DEFINED / negated forms) now parses both branches on one
        physical line instead of echoing the raw ") ELSE (" text
        (_parse_if_bodies via _match_paren).

  - created by INABA Hitoshi

0.06  2026-06-27 JST (Japan Standard Time)

  - SH indexed and associative arrays (BATsh::SH). The single most useful
    missing piece of bash-script compatibility is now implemented:

      arr=(a b c)            indexed array literal
      arr+=(d e)             append at the next index
      arr[i]=v, arr[i]+=v    element assignment / string append
      declare -a arr         declare an (empty) indexed array
      declare -A map         declare an associative array
      typeset -a / -A        accepted as an alias of declare
      map[key]=value         associative element assignment
      map=([k1]=v1 [k2]=v2)  associative (or sparse indexed) literal
      ${arr[i]}, ${map[key]} element access; $arr is short for ${arr[0]}
      ${arr[-1]}             negative index counts back from the last element
      ${arr[@]}, ${arr[*]}   all element values
      ${#arr[@]}             number of set elements
      ${#arr[i]}             length of one element's value
      ${!arr[@]}             list of indices (indexed) or keys (associative)
      unset arr, unset arr[i] remove whole array / single element

    Indexed subscripts are evaluated arithmetically (so ${arr[$i]} and
    ${arr[i+1]} work) and indexed arrays may be sparse; associative
    subscripts are literal strings. Array names are case-insensitive, like
    scalar variables, and a name is either a scalar or an array, never both.
    Element order for ${arr[@]} is ascending numeric index for indexed
    arrays and sorted key order for associative arrays -- bash leaves the
    associative order unspecified, so a deterministic order is used for
    portable output. In a for list, "${arr[@]}" and "${!arr[@]}" word-split
    to one item per element or key, so elements containing spaces survive.
    Array operations are detected on the raw line before expansion so the
    "(a b c)" literal and the "[sub]" subscripts are never mangled by
    variable or command substitution. All Perl 5.005_03 compatible (no
    prototypes; storage via plain package-level hashes).

  - SH for-loop word list: the list of a "for VAR in LIST" header is now
    variable- and command-substitution-expanded (previously the words were
    taken literally), with quote-aware word splitting and filename globbing.
    This fixes "for x in $LIST" and enables "for x in "${arr[@]}"".

  - SH echo: quote removal is now structural (per word) rather than only



( run in 1.354 second using v1.01-cache-2.11-cpan-364913b4093 )