Changes for version 0.09 - 2026-07-27
- FIX (SH, Windows): the result of an expansion is literal data, not shell source. A line is expanded first and has its quotes removed afterwards, and the quote-removal stage could not tell text written in the script from text an expansion had just produced: every backslash coming out of a variable, a command substitution or a tilde expansion was re-read as a shell escape and deleted. With HOME set to C:\home\flower,
- cd ~ tried to enter C:homeflower d="C:\Users\x" ; cd $d tried to enter C:Usersx
- so practically every Windows pathname that travelled through a variable was destroyed. This is what made t/0020-tilde-expansion.t (TE01 TE02 TE04 TE07) fail in the CPAN Testers report for BATsh-0.08 on Windows with perl 5.8.9.
- POSIX quote removal applies to the source word only, never to the result of an expansion, and BATsh now follows that rule. A backslash produced by an expansion is held behind a NUL-delimited sentinel -- a NUL can never occur in shell source -- until quote removal has finished, while a backslash written in the script keeps its escaping meaning. Protected are all of $VAR, ${VAR} and the whole ${VAR...} family, array elements and ${arr[@]}, $1..$9, $@, $*, $0, $( ... ), `...` and tilde expansion; the literal backslash is restored again for the consumers that never dequote (external command lines, here-document bodies, the variable store).
- A tilde expansion is additionally protected against field splitting, so a home directory whose name contains a space stays a single word, as POSIX requires.
- FIX (SH): "test" / "[" now removes quotes from its operands the same way command words do, instead of stripping one leading and one trailing double quote. A half-stripped operand such as "C:\dir"/sub could never name an existing file.
- FIX (SH): filename globbing keeps only names that exist. When nothing matches, glob() hands the pattern back with its escape backslashes already removed, which silently turned an unmatched C:\dir\*.txt into C:dir*.txt. POSIX leaves an unmatched pattern untouched, and so does BATsh now.
- FIX (SH): "echo -e" interprets \n, \t, \r and \\ arriving from an expansion ("v='a\tb' ; echo -e $v") as well as from the source.
- FIX (SH): "export VAR=value" stores the value rather than the quote characters around it. "export V='C:\x'" used to export the quotes as part of the value, and "export V='a b'" was truncated at the space; the operands are now split on unquoted whitespace and dequoted exactly like an ordinary assignment.
- FIX (SH and CMD, Windows): pathname expansion no longer goes through Perl's glob(). glob() reads a BACKSLASH AS AN ESCAPE character, so an ordinary Windows pattern
- d="C:\dir" ; for f in $d/*.txt ; do ... done
- was searched for as C:dir/*.txt, matched nothing, and came back with its backslashes deleted. File::Glob's GLOB_NOESCAPE would have avoided that, but File::Glob needs Perl 5.6 and BATsh supports 5.005_03, so the matcher (BATsh::SH::_glob_paths) is now part of BATsh and walks a pattern one path segment at a time, reusing the module's own glob-to-regex translator:
- the separator is "/" everywhere and "\" as well on Windows, where that is simply how a path is written; on Unix a backslash remains an ordinary character in a filename, so a directory really named "a\b" keeps working;
- a leading "." is matched only by a pattern that starts with ".", and every segment but the last has to name a directory;
- the separators written in the pattern are the separators returned, matches are sorted, case is ignored on Windows, and only names that exist come back -- a pattern matching nothing is left exactly as written, as POSIX requires;
- an unusable pattern (an empty or reversed character class) is taken literally instead of leaking a Perl regex warning.
- CMD-mode wildcards (FOR %f IN (...), DEL) use the same matcher.
- FIX (SH): a backslash written inside quotes survives a line that also contains a filename pattern. Such a line is split into words (which removes the quotes) and the rebuilt line is dequoted again, and that second pass read the now unquoted backslash as an escape: echo "C:\dir"/*.txt printed C:dir/*.txt. The quoted text is marked as data when the words are rebuilt, so only a backslash written OUTSIDE quotes is still an escape, exactly as in bash.
- Which is worth spelling out, because the two modes differ: a CMD-mode line has no escape character and takes C:\dir\*.txt as written, while an SH-mode line follows bash, where a bare echo C:\dir\*.txt means C:dir*.txt. In SH mode a Windows pattern belongs in quotes or, as in any real script, in a variable, where its backslashes are data and stay separators. See "Pathname Expansion" in BATsh::SH.
- FIX (SH): "[!abc]" in a filename pattern and in a ${VAR%pat}-family pattern now negates the character class. Perl reads "[!abc]" as the class of "!", "a", "b" and "c", so the negation was inverted. (Case patterns already handled it.)
- NEW (SH): "set" implements its POSIX operands. "set -- ARG ..." replaces the positional parameters, "set --" clears them, and a first operand that is not an option does the same, as in "set a b c". They are stored the way a function call and "shift" store them, so $1..$9, $@, $*, $#, shift and getopts all see them, and the standard
- set -- -f value extra while getopts f: opt ; do ... done shift $((OPTIND - 1))
- idiom now works: until 0.09 every operand of "set" was silently ignored, so getopts fell back to an argument list that was never filled in. OPTIND is left alone, as in bash.
- FIX (CMD): DEL and DIR no longer mistake a forward-slash pathname for their own switches. The old code removed every "/word" run from the argument, so "DEL /tmp/log/*.tmp" lost "/tmp" and "/log". Only a real switch letter (/P /F /S /Q /A[:attrs] for DEL, and the DIR set), standing as a word of its own, is removed now.
- POD: every module now carries a =head1 VERSION section. BATsh::CMD, BATsh::Env and BATsh::SH had none, which the shared ina@CPAN distribution checks (G2) require of every lib/*.pm. Note for future releases: a version bump now touches five POD VERSION lines, not two.
- Perl 5.005_03: the three "my (undef, ...)" declarations in BATsh.pm and BATsh::SH are written as a list plus an index instead. Skipping an element that way inside "my" is a Perl 5.10 spelling, and the distribution supports 5.005_03.
- eg/06_sh_comprehensive.batsh: trailing whitespace removed from three lines (no change to what the example prints).
- QA: the shared ina@CPAN maintenance files are updated to the current common versions -- t/lib/INA_CPAN_Check.pm 0.37 -> 0.41 and pmake.bat 0.40 -> 0.42. The newer checker adds per-module POD and version checks and a Changes-format group, and t/9030-distribution.t now calls that group (L1-L3) as well.
- pmake.bat 0.42 restores the generated Makefile.PL's EXE_FILES clause, which 0.41 had dropped: without it "make install" does not install bin/batsh.pl as a command (with its .bat wrapper on Win32). The clause is guarded by -d 'bin' and is inert for a distribution that ships no bin/ directory.
- New regression tests: t/0030-expansion-literals.t (16 checks, eight of them fail on 0.08), t/0031-set-positional.t (11 checks) and t/0032-glob-paths.t (16 checks).
- QA: t/9040-style.t now calls the checker's style group K over lib/*.pm -- K1 (a comma must be followed by whitespace or by a closing bracket), K2 (use [ @array ] rather than \@array) and K3 (use { %hash } rather than \%hash). The group has shipped in t/lib/INA_CPAN_Check.pm for several releases but no test file called it, so it had never run against BATsh. With group K wired in, the suite runs 1888 tests.
- K1 needed thirteen lines respaced. None of them changes what the code does:
- lib/BATsh.pm 482-486 the @frame_keys list, rewrapped lib/BATsh/CMD.pm 411 421 428 434 435 open(STDIN,'<&...') and open(STDOUT,'>&...') gain the space lib/BATsh/Env.pm 56-59 68 the one-line accessors, my ($c,$n)=@_ -> my ($c, $n) = @_
- K2 and K3 already passed on every module.
- K1 needed thirteen lines respaced. None of them changes what the code does:
- created by INABA Hitoshi
Modules
Bilingual Shell for cmd.exe and bash in one script
Pure Perl cmd.exe interpreter for BATsh
Shared variable store for BATsh
Multibyte (CP932/DBCS) script guard for BATsh
Pure Perl bash/sh interpreter for BATsh
Examples
- eg/00_hello.pl
- eg/01_hello.batsh
- eg/02_env_bridge.batsh
- eg/03_cmd_features.batsh
- eg/04_sh_features.batsh
- eg/05_cmd_comprehensive.batsh
- eg/06_sh_comprehensive.batsh
- eg/07_mixed_comprehensive.batsh
- eg/08_sh_arrays.batsh
- eg/09_cmd_subroutines.batsh
- eg/10_sh_case.batsh
- eg/11_sh_trap.batsh
- eg/12_cmd_vs_sh.batsh
- eg/13_cp932_demo.pl
- eg/14_sh_getopts.batsh