NAME

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

VERSION

Version 0.02

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

The built-in CMD interpreter does not support:

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

  • CHOICE, TIMEOUT, XCOPY, ROBOCOPY, FINDSTR, SORT, MORE and other external utilities (delegated to the OS)

The built-in SH interpreter does not support:

  • Background execution (& at end of command)

  • Here-documents (<<)

  • Process substitution (<(cmd))

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

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.org>

LICENSE

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