BATsh
view release on metacpan or search on metacpan
lib/BATsh.pm view on Meta::CPAN
my $rc = _final_status();
BATsh::SH::fire_exit_trap("BATsh::SH") if defined &BATsh::SH::fire_exit_trap;
return $rc;
}
sub run_lines {
my ($class_or_self, @lines) = @_;
_prepare_source(\@lines, undef);
_ensure_env_init();
$_SCRIPT_EXIT = undef;
BATsh::SH::reset_sh_options() if defined &BATsh::SH::reset_sh_options;
_process_lines(@lines);
my $rc = _final_status();
BATsh::SH::fire_exit_trap("BATsh::SH") if defined &BATsh::SH::fire_exit_trap;
return $rc;
}
# ----------------------------------------------------------------
# _final_status -- the exit status of the run just finished: the code of
# an executed exit/EXIT if any, else the last command's status. After
# every section flush both interpreters hold the same value (see
# _flush_cmd/_flush_sh), so the SH side is authoritative here.
# ----------------------------------------------------------------
sub _final_status {
return $_SCRIPT_EXIT if defined $_SCRIPT_EXIT;
return BATsh::SH::get_status();
}
# ----------------------------------------------------------------
# last_status -- public accessor: the unified $? / %ERRORLEVEL% value.
# ----------------------------------------------------------------
sub last_status { return _final_status() }
sub _ensure_env_init {
# Init only once per process
BATsh::Env::init() unless %BATsh::Env::STORE;
}
###############################################################################
# set_encoding -- select the script encoding for multibyte-safe execution
# BATsh->set_encoding('cp932'); # also: sjis gbk uhc big5 utf8 none auto
# The default is 'auto': a non-UTF-8 script containing bytes >= 0x80 is
# treated as CP932 and guarded (see BATsh::MB). The environment variable
# BATSH_ENCODING, when set, overrides the default before the first run.
###############################################################################
my $_ENV_ENCODING_APPLIED = 0;
sub set_encoding {
my ($class_or_self, $enc) = @_;
$enc = $class_or_self
if !defined($enc) && defined($class_or_self)
&& $class_or_self !~ /\ABATsh\b/;
$_ENV_ENCODING_APPLIED = 1; # explicit choice beats BATSH_ENCODING
return BATsh::MB::set_encoding($enc);
}
sub encoding { return BATsh::MB::encoding() }
###############################################################################
# _prepare_source -- per-run encoding setup on the raw script lines
# 1. strip a UTF-8 BOM from the first line
# 2. apply an explicit per-run encoding, or BATSH_ENCODING (once),
# or leave the current ('auto' by default) setting in place
# 3. under 'auto', detect the source encoding and activate the guard
# 4. guard-transform every line in place (identity when inactive)
###############################################################################
sub _prepare_source {
my ($lines_ref, $encoding) = @_;
$lines_ref->[0] = BATsh::MB::strip_bom($lines_ref->[0]) if @{$lines_ref};
if (defined $encoding && $encoding ne '') {
BATsh::MB::set_encoding($encoding);
$_ENV_ENCODING_APPLIED = 1;
}
elsif (!$_ENV_ENCODING_APPLIED
&& defined $ENV{BATSH_ENCODING} && $ENV{BATSH_ENCODING} ne '') {
BATsh::MB::set_encoding($ENV{BATSH_ENCODING});
$_ENV_ENCODING_APPLIED = 1;
}
BATsh::MB::activate_for(join('', @{$lines_ref}));
if (BATsh::MB::active()) {
for my $l (@{$lines_ref}) { $l = BATsh::MB::enc($l) }
}
return 1;
}
###############################################################################
# _set_batch_args -- populate %0..%9 and %* in the Env store
# %0 = script path (as passed to run())
# %1 = first argument, ..., %9 = ninth argument
# %* = all arguments joined by single space (does not include %0)
###############################################################################
sub _set_batch_args {
my ($script, @args) = @_;
# Arguments arrive as RAW bytes from outside the interpreter
# (command line / caller); guard them before they enter the store.
@args = map { BATsh::MB::enc(defined $_ ? $_ : '') } @args;
# Normalise $0: resolve to absolute path using File::Spec
my $abs_script = defined $script ? $script : '';
if ($abs_script ne '' && !File::Spec->file_name_is_absolute($abs_script)) {
my $cwd = defined(&Cwd::cwd) ? Cwd::cwd() : '.';
$abs_script = File::Spec->catfile($cwd, $abs_script);
}
BATsh::Env->set('%0', BATsh::MB::enc($abs_script));
for my $n (1 .. 9) {
BATsh::Env->set("%$n", defined($args[$n - 1]) ? $args[$n - 1] : '');
}
BATsh::Env->set('%*', join(' ', @args));
}
###############################################################################
# classify_token
###############################################################################
sub classify_token {
my ($class_or_token, $token) = @_;
unless (defined $token) { $token = $class_or_token }
if ($token =~ /\A[A-Z0-9_\-\\\/\.:@%]+\z/ && $token =~ /[A-Z]/) {
return 'CMD';
}
return 'SH';
}
lib/BATsh.pm view on Meta::CPAN
+ - * / % ** (** right-assoc; / % truncate toward zero)
== != < <= > >= && || ! (results 0/1)
& ^ | ~ << >> (bitwise; ~ is signed)
= += -= *= /= %= <<= >>= &= ^= |= (write back to the variable)
++ -- (prefix and postfix), ?: (ternary), comma
0xNN hex and 0NN octal literals, $1..$9 inside
$( command ) and `command` (command substitution, nested)
cmd1 | cmd2 [| cmd3 ...] (pipeline via temporary file)
cmd1 && cmd2, cmd1 || cmd2, cmd1 ; cmd2 (compound commands)
> >> < 2> 2>> 2>&1 1>&2 (I/O redirection)
name() { ... }, function name { ... } (function definitions)
$VAR, ${VAR}, $1..$9, $@, $*, $#, $?, $$, $0
${VAR:-default}, ${VAR:=default}, ${VAR:+alt}
${VAR%pat}, ${VAR%%pat} -- shortest/longest suffix removal
${VAR#pat}, ${VAR##pat} -- shortest/longest prefix removal
${VAR/pat/rep}, ${VAR//pat/rep} -- first/all substitution
${VAR^^}, ${VAR^}, ${VAR,,}, ${VAR,} -- case conversion
${VAR:N:L}, ${VAR:N} -- substring
${#VAR} -- string length
arr=(a b c), arr+=(d e), arr[i]=v, arr[i]+=v -- indexed arrays
declare -a arr, declare -A map, typeset ... -- array declaration
map=([k]=v ...), map[k]=v -- associative arrays
${arr[i]}, ${map[key]}, $arr (== ${arr[0]}) -- element access
${arr[@]}, ${arr[*]}, ${#arr[@]}, ${#arr[i]}, ${!arr[@]}
unset arr, unset arr[i]
source / . file
{a,b,c}, {1..5}, {a..e}[..step] -- brace expansion
shopt -s/-u extglob; ?(),*(),+(),@(),!() -- extended pattern
matching in case patterns and ${VAR%pat}-family patterns
cmd <<< word -- here-string
<(cmd), >(cmd) -- process substitution via temp file
select VAR in list; do ... done -- menu loop
alias name=value, alias, unalias
exec cmd, exec > file ...
( cmd1; cmd2 ) -- subshell command group, isolated scope
=head1 ENCODING (CP932 / Shift_JIS SUPPORT)
Scripts written in CP932 -- the ANSI encoding of Japanese Windows --
run correctly as of version 0.07, including the notorious "dame-moji"
whose second byte collides with an ASCII shell metacharacter:
SO (0x83 0x5C) trail byte = backslash
HYOU (0x95 0x5C) trail byte = backslash
PO (0x83 0x7C) trail byte = pipe
CHI (0x83 0x60) trail byte = backtick
DA (0x83 0x5E) trail byte = caret (the cmd.exe escape)
The encoding is B<auto-detected> by default: a non-UTF-8 source
containing bytes above 0x7F is treated as CP932. Pure-ASCII and
UTF-8 scripts are unaffected. Explicit selection:
BATsh->run($file, encoding => 'cp932'); # per run
BATsh->set_encoding('cp932'); # for the process
set BATSH_ENCODING=cp932 # environment variable
perl lib/BATsh.pm --encoding=cp932 script.batsh
Supported names: cp932 (sjis), gbk (cp936), uhc (cp949), big5
(cp950), utf8, none, auto. Under an active DBCS encoding the
substring and length operators C<${#VAR}>, C<${VAR:N:L}> and
C<%VAR:~n,m%> count characters rather than bytes. A UTF-8 BOM on
the first line is stripped. See L<BATsh::MB> for the mechanism.
=head1 EXIT STATUS
C<run>, C<run_string> and C<run_lines> return the script's B<final exit
status> as an integer: the argument of SH C<exit N> or CMD C<EXIT [/B] N>
if one was executed, otherwise the status of the last command. C<EXIT>
with no code keeps the current C<ERRORLEVEL> (so C<false> then C<EXIT /B>
returns 1). The same value is available afterwards as
C<BATsh-E<gt>last_status>.
At every CMD/SH section boundary the status is mirrored in both
directions, so an SH failure is immediately visible as C<%ERRORLEVEL%>
(and C<IF ERRORLEVEL n>) in the following CMD section, and a CMD failure
is visible as C<$?> in the following SH section.
C<BATsh-E<gt>main(@ARGV)> implements the command-line interface used by
the modulino (C<perl lib/BATsh.pm ...>) and by F<bin/batsh.pl> (installed
as C<batsh.pl>; on Windows MakeMaker's F<pl2bat> also provides
C<batsh>): C<--help>, C<--version>, C<-e 'source'>, a script filename,
or C<-> to read the script from STDIN. With a script filename or with
C<->, the remaining arguments become C<%1>..C<%9> / C<$1>..C<$9>. With
C<-e> they do B<not>: every remaining argument is joined with newlines
onto the inline source, so C<-e 'echo one' 'echo two'> runs a two-line
script. The modulino calls
C<exit(BATsh-E<gt>main(@ARGV))>, so the OS-level exit code of the process
is the script's own status. In the REPL, C<exit N> / C<EXIT N> ends the
session.
=head1 REQUIREMENTS
Perl 5.005_03 or later. Core modules only. No external shell required.
=head1 BUGS AND LIMITATIONS
Commands that are not built in -- C<FINDSTR>, C<SORT>, C<MORE>, C<CHOICE>,
C<TIMEOUT>, C<XCOPY>, C<ROBOCOPY> and the like in CMD mode, and any
non-builtin program in SH mode -- are B<not> reimplemented in Perl. They
are invoked as external programs (via Perl's C<system>), so they work only
where the host operating system provides the corresponding executable
(e.g. F<FINDSTR.EXE> on Windows). This is by design: only the built-in
command set is guaranteed to run identically on every platform.
The built-in CMD interpreter does not implement:
=over
=item * C<FOR /F> with C<usebackq> backtick-quoted commands on Windows
(the C<cmd /c> subprocess path is untested on Windows).
=back
Variable substring C<%VAR:~n,m%> / C<%VAR:~n%> / C<%VAR:~-n%> / C<%VAR:~n,-m%>
and in-place substitution C<%VAR:str1=str2%> / C<%VAR:*str1=str2%> are B<now
supported> as of version 0.05 (see L<BATsh::Env>).
Dynamic pseudo-variables C<%DATE%> (YYYY-MM-DD), C<%TIME%> (HH:MM:SS.cc),
C<%CD%> (current directory), C<%RANDOM%> (0-32767), C<%ERRORLEVEL%>, and
C<%CMDCMDLINE%> are B<now supported> as of version 0.05.
( run in 1.051 second using v1.01-cache-2.11-cpan-364913b4093 )