BATsh

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

    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

Changes  view on Meta::CPAN

        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

lib/BATsh/SH.pm  view on Meta::CPAN

        $i++;
    }
    return $re;
}

# ----------------------------------------------------------------
# extglob (v0.07): ?(list) *(list) +(list) @(list) !(list) pattern-list
# operators, active only while "shopt -s extglob" is on.  Shared by
# _case_glob_to_re() (case patterns) and _glob_to_re() (${VAR%pat} and
# friends).  $convert_sub converts one pattern-list alternative (which
# may itself contain nested extglob groups) to a regex fragment.
#
# Returns ($pos_after_close_paren, $regex_fragment), or () when the
# text at $i is not a well-formed extglob group (extglob is then left
# to fall through to its ordinary, literal meaning for that character).
#
# !(list) is approximated as "any run of characters that never forms a
# complete match of one of the alternatives" via a negative lookahead
# repeated per character; this matches the common "exclude these whole
# patterns" usage (e.g. !(*.jpg|*.png)) but, unlike real extglob, is not
# exact when !(...) is combined with more pattern after it in the same
# glob -- documented as a known limitation.
# ----------------------------------------------------------------

lib/BATsh/SH.pm  view on Meta::CPAN

        if    ($cc eq '(') { $depth++; $body .= $cc; $j++ }
        elsif ($cc eq ')') { $depth--; $j++; $body .= $cc if $depth > 0 }
        elsif ($cc eq '\\' && $j+1 < $n) { $body .= $cc . $c[$j+1]; $j += 2 }
        else                { $body .= $cc; $j++ }
    }
    return () if $depth != 0;

    my @alts    = _extglob_split_alts($body);
    my @re_alts = map { $convert_sub->($_) } @alts;
    my $inner   = '(?:' . join('|', @re_alts) . ')';
    my $frag;
    if    ($op eq '?') { $frag = $inner . '?' }
    elsif ($op eq '*') { $frag = $inner . '*' }
    elsif ($op eq '+') { $frag = $inner . '+' }
    elsif ($op eq '@') { $frag = $inner }
    elsif ($op eq '!') { $frag = '(?:(?!' . $inner . ').)*' }
    else                { return () }
    return ($j, $frag);
}

# _extglob_split_alts: split an extglob pattern-list body on top-level
# '|' (respecting nested parens and backslash escapes).
sub _extglob_split_alts {
    my ($body) = @_;
    my @out;
    my $cur   = '';
    my @c     = split //, $body;
    my $n     = scalar @c;

lib/BATsh/SH.pm  view on Meta::CPAN

        }
    }
    return @out;
}

# _strip_sh_comment: remove a trailing "# ..." comment from one SH
# physical line.  A '#' introduces a comment only when it is unquoted,
# outside any $(...)/${...}/`...` region, and begins a word (preceded by
# the start of line or by whitespace / ; / & / | / '(' ).  This leaves
# parameter forms such as $#, ${#var}, ${var#pat} and an in-word '#'
# (echo a#b, http://h#frag) untouched, matching POSIX shells.  Pure Perl
# 5.005_03 (hand-rolled scan; no regex features).
sub _strip_sh_comment {
    my ($line) = @_;
    return $line unless defined $line && index($line, '#') >= 0;
    my @c     = split //, $line;
    my $n     = scalar @c;
    my $in_sq = 0;
    my $in_dq = 0;
    my $in_bt = 0;
    my $pdep  = 0;

lib/BATsh/SH.pm  view on Meta::CPAN

# ----------------------------------------------------------------
# Shell function registry  { name => \@body_lines }
# ----------------------------------------------------------------

# ----------------------------------------------------------------
# _inline_body_has_control: true when a single-line function body
# (the text between the braces of "name() { ... }") contains a shell
# control-structure keyword in command position -- if/for/while/until/
# case/select as the first word, or after a ';', '&&', '||' or '|'.
# Such a body must not be torn apart on ';' (that would split
# "while C; do B; done" into unusable fragments); the caller keeps it
# as one line so _run_lines()'s inline-control handling parses it.
# Quotes, $(...), `...` and ${...} are skipped so a keyword appearing
# only inside them (echo "done", VAR=$(case ...)) does not count.
# Perl 5.005_03 compatible: character scan, no regex features beyond
# \A and \b.
# ----------------------------------------------------------------
sub _inline_body_has_control {
    my ($body) = @_;
    return 0 unless defined $body && $body =~ /\S/;
    my @c    = split //, $body;

lib/BATsh/SH.pm  view on Meta::CPAN


Extglob operators are recognised in case patterns and in the
C<${VAR#pat}>-family parameter-expansion patterns only.  Pathname
(filename) globbing (C<echo *.@(jpg|png)>) does not expand them: an
unquoted C<(> C<)> is read by the line parser as a subshell command
group long before the word reaches the pathname matcher.

=item *

C<!(list)> is approximated with a repeated negative-lookahead regex
fragment ("any run of characters that never forms a complete match of
one of the alternatives").  This matches the common "exclude these whole
patterns" usage exactly, but is not a byte-for-byte reimplementation of
bash's extglob matcher when C<!(...)> is combined with further pattern
text after it in the same glob.

=back

=head2 Here-Strings

  cat <<< "$greeting"

t/0007-extcmd-env.t  view on Meta::CPAN

#   once.  Two distinct foot-guns were observed:
#
#   (A) Unix vector -- a dollar default-variable token inside the
#       one-liner is expanded by /bin/sh using the environment variable
#       "_" (the path/last-arg the shell exports), which is
#       unpredictable on CPAN smokers.  This produced random failures
#       such as "Bareword found where operator expected ... 1EERDtQcrK".
#
#   (B) Windows vector -- cmd.exe does NOT treat single quotes as
#       quoting.  A one-liner wrapped in single quotes is split on
#       whitespace, so Perl receives a broken fragment and dies with
#       "Can't find string terminator".
#
#   The portable form satisfies both: wrap the code in DOUBLE quotes
#   and use NO dollar sign the shell would expand, e.g.
#       perl -ne "print uc"
#   (the default variable is used implicitly by uc, so no token leaks).
#
# THIS TEST
#   EE01/EE02 run the pipeline and here-document patterns under several
#             hostile values of the environment variable "_" (the Unix

t/0031-set-positional.t  view on Meta::CPAN

######################################################################
#
# 0031-set-positional.t  set [--] ARG ... sets $1..$9 / $@ / $# (v0.09)
#
# BACKGROUND
#   Until v0.09 the "set" builtin understood only its option letters
#   (-e -u -x, -o NAME) and silently ignored every operand, so the
#   standard way of feeding an argument list to a script fragment
#
#       set -- -f value
#       while getopts f: opt ; do ... done
#
#   saw an empty argument list: getopts falls back to the positional
#   parameters, and those were never set.  "set" now implements the POSIX
#   operand rules -- "set -- [ARG ...]" replaces the positional
#   parameters (clearing them when no ARG follows), and so does a first
#   operand that is not an option, as in "set a b c".  The parameters are
#   kept in the interpreter's existing %1..%9 / %* representation, the



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