BATsh
view release on metacpan or search on metacpan
lib/BATsh/SH.pm view on Meta::CPAN
# substitution below: POSIX expands ~word from the literal source
# text only, once, at the start of a word (or right after the '='
# of a leading NAME= assignment). A ~ that only appears because a
# $VAR happened to hold a string starting with "~" must NOT be
# re-expanded -- doing tilde expansion after $VAR substitution
# cannot tell the two cases apart, so it must happen first here.
$str = _tilde_prepass($str);
# Protect backslash escapes before any expansion. In POSIX shells the
# backslash inside double quotes keeps its special meaning only before
# $ ` " \ and newline, so "\$" is a literal dollar (NO expansion), "\`"
# is a literal backtick (NO command substitution) and "\\" is a literal
# backslash. Earlier releases performed the $-/`-substitutions globally
# with no escape awareness, so "\$_" expanded $_ to empty and left the
# stray backslash (giving e.g. perl -e "...uc(\)..." -> syntax error).
# We stash the escaped specials under NUL-delimited sentinels (a NUL can
# never occur in shell source) and restore them as literals at the end.
# Order matters: "\\" first so that "\\$" means backslash + expansion.
$str =~ s/\\\\/\x00BATSH_BS\x00/g; # \\ -> literal backslash
$str =~ s/\\\$/\x00BATSH_DL\x00/g; # \$ -> literal dollar (no expand)
$str =~ s/\\`/\x00BATSH_BT\x00/g; # \` -> literal backtick (no subst)
# $(( arithmetic ))
$str =~ s/\$\(\(\s*(.*?)\s*\)\)/_eval_arith($1)/ge;
# <(cmd) / >(cmd) process substitution (v0.07) -- MUST run before
# $(...) below, and before backtick substitution, so that <(...) is
# never mistaken for a stray "<" redirection operator by the later
# redirect-stripping stage.
$str = _replace_process_subst($class, $str);
# $( command ) substitution
# Use _extract_cmd_subst to correctly handle nested () and quoted ) chars.
$str = _replace_cmd_subst($class, $str);
# backtick command substitution: `cmd`
$str =~ s/`([^`]*)`/_protect_lit(_cmd_subst($class, $1))/ge;
# ---- Array expansions (v0.06) ----------------------------------
# These MUST precede the scalar ${#VAR} / ${VAR} rules below so that a
# subscripted reference is never mis-parsed as a plain variable.
# ${#NAME[@]} / ${#NAME[*]} -- number of set elements
$str =~ s/\$\{#([A-Za-z_][A-Za-z0-9_]*)\[[\@*]\]\}/
_arr_count($1)
/ge;
# ${#NAME[SUB]} -- length of one element
$str =~ s/\$\{#([A-Za-z_][A-Za-z0-9_]*)\[([^\]]*)\]\}/
do {
my $v = _arr_get_element($1, _arr_expand_sub($class, $2));
defined $v ? length($v) : 0
}
/ge;
# ${!NAME[@]} / ${!NAME[*]} -- list of indices / keys
$str =~ s/\$\{!([A-Za-z_][A-Za-z0-9_]*)\[[\@*]\]\}/
_protect_lit(join(' ', _arr_ordered_keys($1)))
/ge;
# ${NAME[@]} / ${NAME[*]} -- all elements (space-joined word-split model)
$str =~ s/\$\{([A-Za-z_][A-Za-z0-9_]*)\[[\@*]\]\}/
_protect_lit(join(' ', _arr_values($1)))
/ge;
# ${NAME[SUB]} -- single element
$str =~ s/\$\{([A-Za-z_][A-Za-z0-9_]*)\[([^\]]*)\]\}/
do {
my $v = _arr_get_element($1, _arr_expand_sub($class, $2));
defined $v ? _protect_lit($v) : ''
}
/ge;
# ----------------------------------------------------------------
# ${#VAR} -- length of value (characters, not bytes, under DBCS guard)
$str =~ s/\$\{#([A-Za-z_][A-Za-z0-9_]*)\}/
do { my $v = BATsh::Env->get($1);
defined $v ? BATsh::MB::mb_length($v) : 0 }
/ge;
# ${VAR%%pattern} -- remove longest suffix (MUST be before single %)
$str =~ s/\$\{([A-Za-z_][A-Za-z0-9_]*)%%([^}]*)\}/
do { my $v = BATsh::Env->get($1); $v = defined $v ? $v : ''; _protect_lit(_sh_remove_suffix($v, $2, 1)) }
/ge;
# ${VAR%pattern} -- remove shortest suffix (single %, not %%)
$str =~ s/\$\{([A-Za-z_][A-Za-z0-9_]*)%(?!%)([^}]*)\}/
do { my $v = BATsh::Env->get($1); $v = defined $v ? $v : ''; _protect_lit(_sh_remove_suffix($v, $2, 0)) }
/ge;
# ${VAR##pattern} -- remove longest prefix (MUST be before single #)
$str =~ s/\$\{([A-Za-z_][A-Za-z0-9_]*)##([^}]*)\}/
do { my $v = BATsh::Env->get($1); $v = defined $v ? $v : ''; _protect_lit(_sh_remove_prefix($v, $2, 1)) }
/ge;
# ${VAR#pattern} -- remove shortest prefix (single #, not ##)
$str =~ s/\$\{([A-Za-z_][A-Za-z0-9_]*)#(?!#)([^}]*)\}/
do { my $v = BATsh::Env->get($1); $v = defined $v ? $v : ''; _protect_lit(_sh_remove_prefix($v, $2, 0)) }
/ge;
# ${VAR//pat/rep} -- replace all occurrences (MUST be before single /)
$str =~ s/\$\{([A-Za-z_][A-Za-z0-9_]*)\/\/([^\/}]*)\/([^}]*)\}/
do { my $v = BATsh::Env->get($1); $v = defined $v ? $v : ''; _protect_lit(_sh_replace($v, $2, $3, 1)) }
/ge;
# ${VAR/pat/rep} -- replace first occurrence (single /, not //)
$str =~ s/\$\{([A-Za-z_][A-Za-z0-9_]*)\/(?!\/)([^\/}]*)\/([^}]*)\}/
do { my $v = BATsh::Env->get($1); $v = defined $v ? $v : ''; _protect_lit(_sh_replace($v, $2, $3, 0)) }
/ge;
# ${VAR^^} -- uppercase all
$str =~ s/\$\{([A-Za-z_][A-Za-z0-9_]*)\^\^\}/
do { my $v = BATsh::Env->get($1); defined $v ? _protect_lit(uc($v)) : '' }
/ge;
# ${VAR^} -- uppercase first char
$str =~ s/\$\{([A-Za-z_][A-Za-z0-9_]*)\^\}/
do { my $v = BATsh::Env->get($1); $v = defined $v ? $v : ''; _protect_lit(ucfirst($v)) }
/ge;
# ${VAR,,} -- lowercase all
( run in 1.135 second using v1.01-cache-2.11-cpan-af0e5977854 )