NAME

BATsh - Bilingual Shell for cmd.exe and bash in one script (self-contained)

VERSION

Version 0.03

SYNOPSIS

use BATsh;

# Run a bilingual .batsh script
BATsh->run('myscript.batsh');
BATsh->run('myscript.batsh', args => ['arg1', 'arg2']);

# Run source inline
BATsh->run_string('echo hello from sh');
BATsh->run_string("SET MSG=hello\nECHO %MSG%");

# Interactive REPL
BATsh->repl();

# CMD features: pipe, tilde modifiers, SET /P
BATsh->run_string('ECHO hello | perl -e "while(<STDIN>){chomp;print uc(\$_).chr(10)}"');
BATsh->run_string("SET /P NAME=Enter name: ");

# SH features: functions, expansions, pipelines, redirection
BATsh->run_string(<<'BATSH');
greet() {
    echo "Hello, \$1"
}
greet world
x=\$(echo hello | perl -e 'while(<STDIN>){chomp;print uc}')
echo \$x
echo out > /tmp/out.txt
BATSH

# Perl 5.005_03 and later; pure-Perl, no external shell required.

DESCRIPTION

Executive Summary

BATsh is a self-contained bilingual shell interpreter written in pure Perl. It runs cmd.exe batch syntax and bash/sh syntax in the same script file, switching automatically between CMD mode and SH mode on a line-by-line basis. No external cmd.exe, bash, or sh is required -- everything runs inside Perl.

Mixed-Mode Sample

The following script demonstrates cmd.exe and bash sections coexisting and sharing variables through the common BATsh::Env variable store.

:: -- CMD section: sets a variable and calls a SH function via bridge --
@ECHO OFF
SET LANG=BATsh
SET COUNT=3

# -- SH section: reads CMD variables, uses functions and pipeline --
greet() {
    echo "Hello from $1 (bash/sh mode)"
}
greet $LANG
for i in 1 2 3; do echo "  item $i of $COUNT"; done
result=$(echo "$LANG" | perl -e 'while(<STDIN>){chomp;print uc}')
echo "Uppercase: $result"
echo "log line" >> /tmp/batsh_demo.txt

:: -- CMD section again: reads variable set by SH side --
ECHO Back in CMD mode
ECHO Uppercase result: %result%

BATsh features (both modes): pipelines (|), I/O redirection (> >> < 2>&1), variable expansion (${var%pat} ${var^^} ${#var}), functions, shift, local.

FULL DESCRIPTION

BATsh is a self-contained bilingual shell interpreter written in pure Perl. It implements both the cmd.exe command set and the sh/bash command set entirely in Perl -- no external cmd.exe, bash, or sh is required.

Scripts are divided into CMD sections (uppercase first token) and SH sections (lowercase first token). Both sections share a common variable store via BATsh::Env, so variables set in a CMD section are immediately visible in the next SH section and vice versa.

CMD MODE

Any line whose first token is all uppercase (A-Z, 0-9, path chars) is a CMD line. CMD sections are executed by BATsh::CMD, which implements:

ECHO, @ECHO OFF/ON
SET VAR=value, SET /A expr (arithmetic)
SET /P VAR=Prompt  (interactive prompt input from STDIN)
IF "A"=="B" ... ELSE ..., IF /I (case-insensitive), IF NOT
IF EXIST "path with spaces", IF DEFINED var, IF ERRORLEVEL n
FOR %%V IN (list) DO ..., FOR /L %%V IN (s,step,e) DO ...
FOR /F "tokens= delims= skip= eol= usebackq" %%V IN (src) DO ...
GOTO :label, :label, GOTO :EOF
CALL :label [args], CALL file.batsh
SHIFT, SHIFT /N
SETLOCAL [ENABLEDELAYEDEXPANSION|DISABLEDELAYEDEXPANSION], ENDLOCAL
CD, DIR, COPY, DEL, MOVE, MKDIR, RMDIR, REN, TYPE
PAUSE, EXIT [/B] [code], CLS, TITLE, VER, PUSHD, POPD
cmd1 | cmd2  (pipeline via temporary file)
&, &&, ||  (sequential, conditional-and, conditional-or)

Variable Expansion

%VAR% references are expanded before each line is dispatched. Variable names are case-insensitive (SET foo=x is visible as %FOO%).

Inside parenthesised IF and FOR blocks, %VAR% is expanded at parse time (before any commands in the block run), matching cmd.exe behaviour. To see a value updated inside a block, use delayed expansion:

SETLOCAL ENABLEDELAYEDEXPANSION
SET X=old
IF 1==1 (
    SET X=new
    ECHO !X!       &:: prints "new" (delayed)
    ECHO %X%       &:: prints "old" (parse-time)
)
ENDLOCAL

Batch Parameters

%0 is the script path (absolute); %1..%9 are positional arguments; %* is all arguments joined by space. SHIFT / SHIFT /N shifts the positional parameters. CALL :label saves and restores caller's arguments.

Batch-parameter tilde modifiers expand %0..%9 components:

%~0    dequote (strip surrounding "...")
%~f1   full absolute path of %1
%~d1   drive letter only   (e.g. C:)
%~p1   directory path only (with trailing /)
%~n1   filename without extension
%~x1   extension only       (e.g. .bat)
%~dp0  drive + directory    (most common usage)
%~nx1  filename + extension

Redirection and Compound Commands

ECHO text > file      stdout overwrite
ECHO text >> file     stdout append
prog 2> err.txt       stderr redirect
& cmd                 sequential execution
cmd1 && cmd2          run cmd2 only if cmd1 succeeded (ERRORLEVEL 0)
cmd1 || cmd2          run cmd2 only if cmd1 failed   (ERRORLEVEL != 0)

The ^ character escapes the next character:

ECHO a^&b    prints  a&b   (& not treated as compound separator)
ECHO a^^b    prints  a^b
ECHO text^   next line is joined (line continuation)

SH MODE

Any line whose first token contains a lowercase letter is a SH line. SH sections are executed by BATsh::SH, which implements:

VAR=value, export VAR=value, unset VAR
echo, printf
if/then/elif/else/fi
for VAR in list; do ... done
while condition; do ... done
until condition; do ... done
case $var in pattern) ... ;; esac
test / [ ... ]  (file, string, and integer comparisons)
cd, pwd, exit, true, false, :, read, shift [N], local VAR=value
$(( arithmetic )) -- +, -, *, /, %, and $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
source / . file

REQUIREMENTS

Perl 5.005_03 or later. Core modules only. No external shell required.

BUGS AND LIMITATIONS

Commands that are not built in -- FINDSTR, SORT, MORE, CHOICE, TIMEOUT, XCOPY, ROBOCOPY and the like in CMD mode, and any non-builtin program in SH mode -- are not reimplemented in Perl. They are invoked as external programs (via Perl's system), so they work only where the host operating system provides the corresponding executable (e.g. 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:

  • Variable substring %VAR:~n,m% and substitution %VAR:str1=str2% expansion.

  • Dynamic pseudo-variables %RANDOM%, %DATE%, %TIME%, %CD%, %CMDCMDLINE%, and %ERRORLEVEL% used as a variable reference.

  • FOR /F with usebackq backtick-quoted commands on Windows (the cmd /c subprocess path is untested on Windows).

The built-in SH interpreter does not implement:

  • Arrays, indexed and associative: arr[i], ${arr[@]}, declare -A.

  • Filename (pathname) globbing such as echo *.txt or for f in *.pl. (The *, ? and [abc] metacharacters are honoured in case patterns and in ${VAR%pat} / ${VAR#pat} expansion only, not for expanding filenames on the command line.)

  • Tilde expansion ~/path and ~user (only cd with no argument uses $HOME).

  • Brace expansion {a,b} and {1..5}.

  • Here-strings (<<< word) and process substitution (<(cmd), >(cmd)).

  • The shell options set -e, set -u, set -x, and the builtins trap, getopts, select, alias, declare/typeset, eval and exec.

Common to both modes: a parenthesised group ( ... ) does not run in a separate sub-shell; it shares the one variable store, so there is no variable-scope isolation.

Pipeline (|), I/O redirection (> >> < 2> 2>> 2>&1), compound commands (&& || ;), and function definitions are supported in both modes.

Here-documents (<<EOF, <<'EOF', <<-EOF) are supported in SH mode, with the limitations described in BATsh::SH: one here-document per command line, no here-strings, and best-effort behaviour when combined with a pipeline or compound operator on the same line.

Background execution (a trailing &) is supported in SH mode for external commands only, with the limitations described in BATsh::SH: only a trailing & is recognised, built-ins/functions/ assignments/control words ignore it, there is no job control (jobs, wait, fg, bg, %n), and no signals are delivered to background jobs. In CMD mode & keeps its cmd.exe meaning as a sequential separator.

Section boundary detection is token-based (uppercase vs. lowercase first token). Mixed-case first tokens are treated as SH.

Please report bugs via the issue tracker: https://github.com/ina-cpan/BATsh/issues

SEE ALSO

BATsh::CMD, BATsh::SH, BATsh::Env

AUTHOR

INABA Hitoshi <ina.cpan@gmail.com>

LICENSE

This software is free software; you can redistribute it and/or modify it under the same terms as Perl itself.