NAME

BATsh::SH - Pure Perl bash/sh interpreter for BATsh

SYNOPSIS

# Used internally by BATsh; not normally called directly.
# BATsh::SH implements the SH-mode interpreter invoked when BATsh
# detects a bash/sh section in a .batsh script.

# Executed via BATsh:
use BATsh;
BATsh->run_string(<<'END');
x=hello
greet() {
    echo "Hello, $1 -- ${#1} chars"
}
greet world
echo ${x^^}
for i in 1 2 3; do
    echo item $i
done
ls /tmp | perl -ne "print"
echo out > /tmp/out.txt
END

DESCRIPTION

Executive Summary

BATsh::SH is the sh/bash interpreter component of BATsh. It handles any script section whose first token contains a lowercase letter, executing it entirely in Pure Perl -- no external shell required. It supports pipelines (|), I/O redirection (> >> 2>&1), functions, compound commands (&&/||/;), and rich parameter expansion: ${var%pat}, ${var^^}, ${var:N:L}, ${#var}.

Mixed-Mode Sample (via BATsh)

use BATsh;
BATsh->run_string(<<'SCRIPT');
:: CMD section: uppercase first token
SET CITY=Tokyo

# SH section: lowercase first token
greet() { echo "Hello from $1!"; }
greet $CITY
echo "lower: ${CITY,,}"
echo $CITY | perl -ne "print uc"
SCRIPT

FULL DESCRIPTION

BATsh::SH implements the POSIX sh / bash command set entirely in Perl. No external sh or bash is required.

Supported Features

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
(for/while/until accept the loop body either on following lines or fully
 inline on one line, e.g. "for i in 1 2 3; do echo $i; done")
case $var in pat) ... ;; pat1|pat2) ... ;; *) ... ;; esac
(case: |-separated patterns, * ? [abc] [a-z] [!abc] globs, quoted/literal
 patterns, and the bash ;& / ;;& fall-through terminators)
test / [ ... ]  (file tests, string, integer comparisons)
cd, pwd, exit, true, false, :, read, shift, local, set, eval
shift [N]  -- shift positional parameters left by N (default 1)
getopts optstring name [arg ...]  -- POSIX option parser (v0.07)
set -e / -u / -x, +e/+u/+x, set -o errexit|nounset|xtrace
trap 'cmd' SIG... / trap - SIG / trap '' SIG / trap [-p]
$(( arithmetic )) -- full C-style operator set (see Arithmetic
 Expansion below); supports $1..$9 positional params
$( command substitution ), `backtick substitution`
$VAR, ${VAR}, $1..$9, $@, $*, $#, $?, $$, $0, $!
${VAR:-default}, ${VAR:=default}, ${VAR:+alt}
${VAR%pat}, ${VAR%%pat}  -- suffix removal (shortest/longest)
${VAR#pat}, ${VAR##pat}  -- prefix removal (shortest/longest)
${VAR/pat/rep}, ${VAR//pat/rep}  -- substitution (first/all)
${VAR^^}, ${VAR^}, ${VAR,,}, ${VAR,}  -- case conversion
${VAR:offset:length}, ${VAR:offset}  -- substring
${#VAR}  -- string length
arr=(a b c), arr+=(d e)  -- indexed array assignment / append
arr[i]=v, arr[i]+=v      -- indexed element assignment / append
declare -a arr, declare -A map, typeset ...  -- array declaration
map=([k1]=v1 [k2]=v2), map[k]=v  -- associative array assignment
${arr[i]}, ${map[key]}, $arr (== ${arr[0]})  -- element access
${arr[@]}, ${arr[*]}     -- all elements
${#arr[@]}, ${#map[@]}   -- element count
${#arr[i]}               -- length of one element
${!arr[@]}, ${!map[@]}   -- indices / keys
unset arr, unset arr[i]  -- whole array / single element
source / . file
name() { ... }, function name { ... }  -- function definition
cmd1 | cmd2 [| cmd3 ...]  (pipeline via temporary file)
cmd1 && cmd2  (run cmd2 only if cmd1 succeeds)
cmd1 || cmd2  (run cmd2 only if cmd1 fails)
cmd1 ; cmd2   (sequential execution)
> file, >> file, < file  (I/O redirection)
2> file, 2>> file        (stderr redirect)
2>&1                     (merge stderr into stdout)
cmd << DELIM ... DELIM   (here-document on STDIN)
cmd <<-DELIM             (here-document, strip leading tabs)
cmd <<'DELIM'            (here-document, no expansion)
cmd &                    (background execution; external commands)
echo *.txt               (filename glob expansion: *, ?, [abc])
for f in *.pl; do ...    (glob expansion in for-loop word list)
{a,b,c}, {1..5}, {a..e}[..step]  -- brace expansion (v0.07)
shopt -s/-u extglob; ?(),*(),+(),@(),!()  -- extended pattern matching
 in case patterns and ${VAR%pat}-family patterns (v0.07)
cmd <<< word              -- here-string (v0.07)
<(cmd), >(cmd)            -- process substitution via temp file (v0.07)
select VAR in list; do ... done  -- menu loop (v0.07)
alias name=value, alias, unalias  (v0.07)
exec cmd, exec > file ...  (v0.07)
( cmd1; cmd2 )            -- subshell command group, isolated scope (v0.07)

Variable Expansion

$VAR, ${VAR}, and positional parameters $1..$9 are expanded before each line executes. $@ and $* expand to all positional parameters space-joined; $# gives their count. The special parameters $? (last exit status), $$ (process id), $0 (script name) and $! (process id of the most recent background job, empty before any) are also expanded.

The following parameter expansion forms are supported:

${VAR:-default}   value if set, else default
${VAR:=default}   set and use default if unset
${VAR:+alt}       alt if set, else empty
${VAR%pat}        remove shortest suffix matching pat
${VAR%%pat}       remove longest suffix matching pat
${VAR#pat}        remove shortest prefix matching pat
${VAR##pat}       remove longest prefix matching pat
${VAR/pat/rep}    replace first match of pat with rep
${VAR//pat/rep}   replace all matches of pat with rep
${VAR^^}          convert to uppercase
${VAR^}           uppercase first character
${VAR,,}          convert to lowercase
${VAR,}           lowercase first character
${VAR:N:L}        substring from offset N, length L
${VAR:N}          substring from offset N to end
${#VAR}           length of value

Patterns use shell glob syntax: * matches any string, ? matches any single character, [abc] matches a character class.

Arithmetic Expansion

$(( expression )) is evaluated by a recursive-descent parser with the full C-style operator set, in decreasing precedence:

( )  grouping
var++ var--        postfix increment / decrement (write back)
++var --var + - ! ~  prefix (~ is signed bitwise NOT: -v-1)
**                 exponentiation (right-associative)
* / %              / and % truncate toward zero (-7/2=-3, -7%2=-1)
+ -
<< >>              bit shifts
< <= > >=          comparisons (result 0 or 1)
== !=
&                  bitwise AND
^                  bitwise XOR
|                  bitwise OR
&&                 logical AND (result 0 or 1)
||                 logical OR  (result 0 or 1)
?:                 ternary conditional
= += -= *= /= %= <<= >>= &= ^= |=   assignment (write back)
,                  comma (evaluate both, yield the right)

Operands are decimal, hexadecimal 0xNN, or octal 0NN literals, variable names (an unset variable reads as 0), and $1..$9. Assignment and ++/-- write the result back to the variable store. A syntax error emits a warning and the expression yields 0.

Shell Options (set -e / -u / -x)

set with option letters controls execution modes; letters combine (set -eux), + turns an option off, and the long forms set -o errexit, set -o nounset, set -o xtrace are accepted.

set -e (errexit): a simple command that exits with a non-zero status terminates the script with that status. Exempt, as in POSIX shells: the condition of if/while/until, and every member of a && / || list except the last (so false && echo x does not stop the script, while true && false does).

set -u (nounset): expanding an unset variable ($VAR or ${VAR}) prints sh: VAR: unbound variable on STDERR and stops the script with status 1. Forms with a fallback such as ${VAR:-default} are exempt. Limitation: the command containing the expansion first completes with the empty string before the script stops.

set -x (xtrace): each simple command is traced to STDERR with a + prefix before execution. Limitation: the raw pre-expansion line is traced (tracing an expanded copy would execute $(...) command substitutions twice).

All three options are reset at the start of each top-level BATsh->run / run_string / run_lines, so set -e in one script does not leak into a later run in the same process.

eval

eval [args...] removes one level of quoting from its (already expanded) arguments, concatenates them, and executes the result as a new command line -- which is parsed and expanded again, giving the double expansion eval exists for:

a=b
b=deep
eval echo \$$a     # prints "deep"

eval with no arguments does nothing and sets status 0.

getopts

getopts optstring name [arg ...] is the POSIX option parser. Called repeatedly (usually as the condition of a while loop), it walks the argument list one option at a time, setting the variable named by name to the option letter found and, for an option that takes an argument, setting OPTARG to that argument. OPTIND holds the index (1-based) of the next argument to be processed; it starts at 1 and must be reset to 1 by hand before a second, independent parse (for example at the top of a function that parses its own arguments). getopts returns 0 while an option was found and non-zero when the options are exhausted, so the loop ends naturally.

optstring lists the recognised option letters; a letter followed by : takes an argument. When no [arg ...] operands are given, the positional parameters are parsed.

while getopts "ab:c" opt; do
    case $opt in
        a) all=1 ;;
        b) file=$OPTARG ;;
        c) count=1 ;;
        \?) echo "usage: ..." >&2; exit 2 ;;
    esac
done
shift $((OPTIND - 1))     # drop the parsed options; "$@" is the operands

Both option-argument forms are accepted: -b file (separate word) and -bfile (attached). Flags may be clustered: -ac is the same as -a -c. A -- argument ends option processing (and is consumed); the first non-option word ends it too (and is left in place).

Error reporting has two modes. By default getopts prints a diagnostic to STDERR and sets name to ? for an unknown option or a missing required argument. If optstring begins with a colon (:ab:c), "silent" mode is selected instead: no message is printed, name is set to ? (unknown option) or : (missing argument), and OPTARG receives the offending option letter, so the script can report the error itself.

Arrays and Associative Arrays

Indexed arrays are created by a parenthesised list, by an explicit element assignment, or by declare -a:

arr=(alpha beta gamma)    # arr[0]=alpha arr[1]=beta arr[2]=gamma
arr[3]=delta              # element assignment
arr+=(epsilon)            # append at the next index
arr[0]+=X                 # append to one element's string value
declare -a empty          # declare an empty indexed array

Associative arrays must be declared with declare -A (or typeset -A) before use, then keyed by arbitrary strings:

declare -A color
color[red]=FF0000
color=([green]=00FF00 [blue]=0000FF)   # whole-array (re)assignment

Element and bulk access mirror bash:

${arr[2]}        one element (indexed subscript is evaluated arithmetically)
${color[red]}    one element (associative subscript is a literal string)
$arr             shorthand for ${arr[0]}
${arr[-1]}       negative index counts back from the last set element
${arr[@]}        all element values
${arr[*]}        all element values (same as [@] here)
${#arr[@]}       number of set elements
${#arr[2]}       length of one element's value
${!arr[@]}       list of indices (indexed) or keys (associative)

unset arr removes the whole array; unset arr[i] removes one element.

Element values that contain spaces survive a quoted whole-array reference: in a for list "${arr[@]}" and "${!arr[@]}" expand to one item per element or key. Elsewhere ${arr[@]} joins elements with a single space, consistent with the word-splitting model used throughout BATsh::SH. Array names are case-insensitive (like scalar variables); a name is either a scalar or an array, never both. Element order for ${arr[@]} is ascending numeric index for indexed arrays and sorted key order for associative arrays (bash leaves associative order unspecified, so a deterministic order is used for portable output).

Case Statements

case WORD in ... esac selects a clause by matching WORD against shell glob patterns:

case $fruit in
    apple)         echo "an apple" ;;
    pear|quince)   echo "pome fruit" ;;     # | separates alternatives
    a*)            echo "starts with a" ;;  # * ? globbing
    [0-9])         echo "a digit" ;;        # character classes
    [!aeiou]*)     echo "not a vowel" ;;    # [!...] negated class
    *)             echo "something else" ;; # default catch-all
esac

Each clause is pattern) commands TERMINATOR. Patterns are separated by |; a clause matches if any of its patterns matches the word. Pattern syntax is shell glob: * (any string), ? (any character), [abc], ranges [a-z], and negation [!abc] or [^abc]. Quoted or backslash-escaped metacharacters match literally (e.g. "*") matches a literal asterisk). *) is the conventional default clause.

Three clause terminators are supported, matching bash:

;;    stop after this clause (the normal case)
;&    fall through: run the NEXT clause's body unconditionally
;;&   continue: keep testing the remaining patterns against the word

The construct may be written across lines or fully inline on one line (case $x in a) echo a ;; *) echo b ;; esac). A leading ( before the pattern list ((pattern)) is accepted.

Traps and Signals

trap registers a handler to run on a signal or on the EXIT pseudo-signal:

trap 'COMMANDS' SIGSPEC...   run COMMANDS when each SIGSPEC fires
trap - SIGSPEC...            reset to the default action
trap '' SIGSPEC...           ignore the signal
trap            (or trap -p) list the current traps

A SIGSPEC is a signal name with or without a leading SIG (INT, SIGINT), a signal number (2), or the EXIT pseudo-signal (also spelled 0). Real signals are bridged to Perl's %SIG: trap 'cmd' INT installs a %SIG{INT} handler that runs cmd, trap '' INT sets it to IGNORE, and trap - INT restores DEFAULT. The EXIT trap is run internally when the script finishes or when exit is called.

The handler command is stored unexpanded and expanded when it fires, so

tmp=$(mktemp); trap 'rm -f $tmp' EXIT

removes the file named by $tmp as it stood at exit. Handlers run at the next safe point after a signal is delivered. EXIT / ERR / DEBUG / RETURN are treated as pseudo-signals and never touch %SIG; of these, only EXIT currently runs a handler. Signal names unsupported by the host (common on Windows) are accepted but degrade quietly.

Function Definitions

Shell functions are defined with name() { ... } or function name { ... }. Inline single-line bodies are also supported: name() { cmd; }. Functions receive arguments as $1..$9 and $@. The caller's positional parameters are saved before the call and restored on return.

Pipeline

The | operator is supported in SH mode. The left side's standard output is written to a temporary file (File::Spec->tmpdir()), which is then fed as standard input to the right side. Multiple pipes (cmd1 | cmd2 | cmd3) are handled by chaining temporary files. All temporary files are removed after use. This implementation is Pure Perl and Perl 5.005_03 compatible.

I/O Redirection

cmd > file      stdout overwrite (create or truncate)
cmd >> file     stdout append
cmd < file      stdin from file
cmd 2> file     stderr overwrite
cmd 2>> file    stderr append
cmd 2>&1        merge stderr into stdout (current stdout target)
cmd 1>&2        merge stdout into stderr

Redirections are parsed after variable expansion, so filenames may contain variables (e.g. echo text > $outfile). All file handles use bareword globs for Perl 5.005_03 compatibility.

Here-Documents

A here-document attaches the lines following the command, up to a line equal to a delimiter word, to the command's standard input:

cat <<EOF
line one
line two
EOF

Three forms are recognised:

cmd <<DELIM      body is variable-expanded
cmd <<'DELIM'    body is literal (no expansion); "DELIM" also works
cmd <<-DELIM     leading tab characters are stripped from body and
                 from the line carrying the closing delimiter

When the delimiter is unquoted, each body line is expanded exactly like an ordinary SH line ($VAR, ${...}, $(...)). When the delimiter is quoted, the body is passed through verbatim.

The body is written to a uniquely named temporary file created with sysopen(...,O_CREAT|O_EXCL,...) to avoid symlink races, and that file is supplied as standard input through the same redirection path used by < file. Both built-ins (e.g. read) and external commands run via system() therefore see the body on STDIN. The temporary file is removed immediately after the command finishes, with an END block as a failsafe. This implementation is Pure Perl and Perl 5.005_03 compatible.

The closing delimiter must appear on a line by itself and match exactly (after tab stripping for <-<<->); trailing whitespace is not ignored. If no matching delimiter is found before end of input, a warning is issued and $? is set to a non-zero value.

Here-Document Limitations

The following are not supported in this release and are documented as known limitations:

  • Here-documents are recognised only in SH mode. The << sequence has no special meaning in CMD mode (BATsh::CMD) and is left untouched there.

  • Only a single here-document per command line is handled. Multiple here-documents on one line (cmd <<A <<B) are not supported.

  • <<< is deliberately not treated as a here-document opener (it is a here-string -- see "Here-Strings" below -- which is handled separately, after expansion, once the ordinary << scan here has stepped past it).

  • Combining a here-document with a pipeline or compound operator on the same line (e.g. cmd <<EOF | other) is best-effort only and not guaranteed; use a separate command for portable behaviour.

  • The delimiter word is matched literally; the <<"a b" form with an embedded space in the delimiter is not supported.

  • A here-document body line that looks like a BATsh subroutine marker (a line of the form :LABEL later followed by RET/RETURN) may be consumed by subroutine extraction, which runs before mode dispatch. Avoid such lines inside here-document bodies.

Background Execution

An unquoted & at the very end of an SH command line starts the command asynchronously and returns control immediately, in the style of POSIX shells:

longjob &
echo "next prompt"

Only the single & at the end of the line is consumed. An & that is part of &&, of an fd-duplication such as 2>&1 or 1>&2, inside single or double quotes, or backslash-escaped (\&) is not treated as a background operator and is left in place.

The launch is Pure Perl and Perl 5.005_03 compatible, with a portable split by platform:

  • On Win32, the command is spawned through the command shell with system(1, ...) (P_NOWAIT), which returns the process id directly.

  • On Unix-like systems the command is started by delegating to /bin/sh (no Perl fork is used), and the background job's process id is captured through the shell's own $! into a uniquely named temporary file created with sysopen(...,O_CREAT|O_EXCL,...). The temporary file is removed immediately, with an END block as a failsafe.

On a successful launch $? is set to 0; the exit status of the background job itself is not awaited. The process id of the most recently started background job is available through $!, which expands to the empty string before any background job has been started.

Background Execution Limitations

The following are not supported in this release and are documented as known limitations:

  • Background execution applies only to external commands in SH mode. A trailing & on a built-in, a defined function, a variable assignment, or a control keyword is ignored and the command runs in the foreground. In CMD mode (BATsh::CMD) & keeps its cmd.exe meaning as a sequential command separator and is unchanged.

  • Only a trailing & is recognised. A mid-line & that backgrounds part of a line (e.g. a & b) is not supported; write a on its own line with a trailing & instead.

  • There is no job control: jobs, wait, wait %n, fg, bg and job-specification (%n) syntax are not implemented. Signals are not delivered to background jobs by BATsh.

  • A backgrounded pipeline or compound list is delegated as a unit to the underlying OS shell; BATsh expands variables and command substitutions first, then hands the resulting line to that shell, so redirections and operators inside a backgrounded line follow OS-shell rules rather than BATsh's own redirection engine.

  • When the command word is supplied by a variable (e.g. $CMD &), the foreground/background decision is made on the literal first token before expansion; such lines are treated as external and backgrounded.

Compound Commands

cmd1 && cmd2    run cmd2 only if cmd1 exits with status 0
cmd1 || cmd2    run cmd2 only if cmd1 exits with non-zero status
cmd1 ; cmd2     run cmd2 unconditionally after cmd1

These are detected before variable expansion to ensure short-circuit logic works correctly. Quoting (', ") and $(...) nesting are respected when splitting.

Function Definitions

name() { body }
function name { body }
name() { cmd1; cmd2; }   # inline single-line body

Functions are registered in a package-level hash %_SH_FUNCTIONS. The caller's positional parameters ($1..$9, $*) are saved before the call and restored on return. local VAR=value saves the existing value of VAR in the function's stack frame and restores it on return.

Brace Expansion

echo a{b,c,d}e        # abe ace ade
echo {1..5}            # 1 2 3 4 5
echo {5..1}             # 5 4 3 2 1
echo {01..03}            # 01 02 03  (zero-padded from the wider operand)
echo {a..e}               # a b c d e
echo {1..10..2}            # 1 3 5 7 9  (numeric step)
echo {a..e..2}               # a c e     (alpha step)
echo pre{a,b}mid{c,d}post      # preamidcpost preamidcpost ...

Brace expansion (v0.07) runs lexically on the raw source line, before any other expansion, exactly like tilde expansion. A brace group is only expanded when it contains a top-level comma or a valid .. range; otherwise it -- and any earlier literal braces on the same word -- is left untouched (echo x{foo}y prints x{foo}y). Quoted text and ${...}, $(...), $((...)), `...`, <(...), >(...) regions are protected and copied through unexpanded, matching the fact that these are not brace-expansion syntax even though some of them also use { } or ( ). Nested and nested nested groups are supported (each alternative is itself recursively brace-expanded).

Extended Pattern Matching (extglob)

shopt -s extglob        # enable; "shopt -u extglob" disables (the default)
shopt extglob            # query; "shopt" alone lists all known options
shopt -p extglob          # print in "shopt -s/-u extglob" form

case $f in
  @(*.tar.gz|*.tgz)) echo archive ;;
  !(*.jpg|*.png))     echo not-an-image ;;
esac

echo ${name%%+([0-9])}   # strip a trailing run of digits

While shopt -s extglob is active, ?(list), *(list), +(list), @(list), and !(list) pattern-list operators (|-separated alternatives, each itself an ordinary glob or a nested extglob group) are recognised in case patterns and in the ${VAR%pat} / ${VAR%%pat} / ${VAR#pat} / ${VAR##pat} / ${VAR/pat/rep} / ${VAR//pat/rep} pattern operand. extglob is off by default, matching bash, and is reset to off by reset_sh_options() between top-level runs, alongside set -e / -u / -x.

Extended Pattern Matching Limitations

  • Extglob operators are recognised in case patterns and in the ${VAR#pat}-family parameter-expansion patterns only; pathname (filename) globbing (echo *.@(jpg|png)) does not expand them, since filename globbing is delegated to Perl's built-in glob(), which has no extglob support of its own.

  • !(list) is approximated with a repeated negative-lookahead regex fragment ("any run of characters that never forms a complete match of one of the alternatives"). This matches the common "exclude these whole patterns" usage exactly, but is not a byte-for-byte reimplementation of bash's extglob matcher when !(...) is combined with further pattern text after it in the same glob.

Here-Strings

cat <<< "$greeting"
read LINE <<< hello

A here-string (<<< word, v0.07) supplies word -- after tilde, parameter, command, and arithmetic expansion, and quote removal, exactly like any other word -- as the command's standard input, with a trailing newline appended. Unlike a here-document body, word is not further word-split. Implementation-wise this reuses the here-document temporary-file machinery: the expanded content is written to a uniquely named sysopen(...,O_CREAT|O_EXCL,...) temp file and supplied through the same redirection path as < file, removed immediately after the command finishes. As with here-documents, only one here-string (or here-document) per command line is handled.

Process Substitution

diff <(sort a.txt) <(sort b.txt)
generate | tee >(gzip > out.gz)

This interpreter never forks (see "Background Execution" above), so neither form of process substitution (v0.07) uses a real named pipe:

<(cmd)

cmd is run immediately, its standard output captured into a fresh temporary file (exactly like $(cmd), but the file is kept rather than read back into a scalar), and <(cmd) is replaced by that file's path -- suitable for anything that wants a filename to read from.

>(cmd)

An empty temporary file is created immediately and >(cmd) is replaced by its path; cmd itself is deferred and run with that file as its standard input only after the current simple command has finished. Because cmd runs after, rather than concurrently with, the writer, this is a best-effort approximation of real streaming >(...) and does not suit a writer that expects the reader to keep up in real time.

Both temporary files are removed once the current simple command (and, for >(cmd), its deferred job) has finished; a process substitution used inside a nested command substitution or loop condition is cleaned up at that inner level, not held open for the rest of the script.

select

select CHOICE in one two three
do
    echo "you picked: $CHOICE"
    break
done

select (v0.07) prints a numbered menu of list -- one item per line, to STDERR -- prompts with $PS3 (default "#? "), and reads one line from STDIN into REPLY: a number in range sets VAR to the corresponding item and runs the body; anything else (including a blank line) sets VAR to the empty string and still runs the body, after which the menu is shown again. End of input on STDIN ends the loop, as does break. Unlike bash, the menu is always one item per line; there is no terminal-width-based multi-column layout.

alias / unalias

alias ll='ls -la'
alias grep='grep --color=auto'
alias
unalias ll
unalias -a

alias (v0.07) with no arguments lists all aliases; alias NAME prints one; alias NAME=VALUE ... defines one or more (quote-aware, like a normal command line). unalias NAME ... removes the named aliases; unalias -a removes all of them. Only the first word of a simple command is checked against the alias table, with chained aliases (an alias whose value's first word is itself an alias) resolved up to a bounded number of times; a name is only ever expanded once per line, so a self-referential alias (alias ls=ls) cannot loop forever. Unlike bash, a trailing space in the alias value does not make the following word alias-eligible as well.

exec

exec > logfile 2>&1     # (stderr-merge form not yet honoured; see below)
exec > logfile
exec cmd arg1 arg2

exec (v0.07) with only redirections (no command word) applies them permanently to the current shell -- future output goes to the new target for the rest of the script, with no save/restore. exec cmd runs cmd (with its own redirections, if any) and then terminates the whole script with cmd's exit status, approximating "exec replaces the shell" without a real fork/exec. Only >, >>, and < trailers on exec are honoured; 2>, 2>&1, and 1>&2 forms are not yet supported on exec itself (they work normally on an ordinary command).

Subshell Command Groups

( cd /tmp; VAR=1; echo "in subshell: $VAR" )
echo "back out here: $VAR"    # unaffected by the subshell's VAR=1 and cd

(
    f() { echo "defined only inside"; }
    f
)
f    # "f: command not found" -- the function did not leak out

A parenthesised command group (v0.07) runs its body with an isolated scope: variable assignments, array changes, function and alias definitions, and cd made inside ( ... ) do not affect the calling shell. Because this interpreter never forks, isolation is approximated by snapshotting BATsh::Env, the array tables, %_SH_FUNCTIONS, %_SH_ALIAS, and the working directory before running the body, and restoring all of it afterward regardless of how the body finished. An exit inside the group ends only the group (its status becomes the group's exit status, and the script continues after it); break / continue / return still propagate outward to an enclosing loop or function, since bash's ( ... ) does not stop them either. Both the single-line form (( cmd1; cmd2 )) and the multi-line form (a bare ( ending one line, a matching bare ) ending a later one) are recognised; a trailing >, >>, or < redirection on the closing ) line is honoured, but a trailing & and 2>-style redirections on that line are not (documented limitations, mirroring the ones noted for exec above).

AUTHOR

INABA Hitoshi <ina.cpan@gmail.com>

LICENSE

Same as Perl itself.