A bash script runs in FreSH unchanged. .sh and .frsh go through the same
parser; the extension only decides which icon Explorer shows and whether the
file is found on PATH without typing it.
This page is the honest inventory. Everything under Works has a test in
tests/, so it keeps working.
It is not only checked against what the author believed bash does. Every script in tests/diff is run through real bash on Linux and through FreSH on Windows on every push, and any difference in output or exit status fails the build. The differences listed at the bottom of this page are the ones that survive that check on purpose.
${v:-default} ${v:=default} ${v:+alternate} ${v:?message}
${#v} ${v:offset} ${v:offset:len} ${v: -3}
${v#pat} ${v##pat} ${v%pat} ${v%%pat}
${v/a/b} ${v//a/b} ${v/#a/b} ${v/%a/b}
${v^^} ${v,,} ${v@U} ${v@L} ${v@Q}
${!name} ${!prefix*} $'ansi\tquoting'
$(command) `command` $(<file) $((1 + 2)) $((2#1010))arr=(one two three) arr+=(four) arr[7]=eight
${arr[0]} ${arr[-1]} ${arr[@]} ${arr[*]} ${#arr[@]} ${#arr[1]}
${!arr[@]} ${arr[@]:1:2} ${arr[@]/a/b} ${arr[@]^^}
declare -a list declare -A table declare -i counter declare -p name$0 $1 $9 $# $@ $* $? $$ $_ ${@:2} ${@:2:2} ${#@}
$RANDOM $SECONDS $LINENO $FUNCNAME $PIPESTATUS $OLDPWD $BASH_REMATCHif/elif/else/fi, while, until, for x in, for ((i=0;i<n;i++)),
case with ;; and ;& fallthrough, select, functions in both spellings,
local, return, break n, continue n, &&, ||, !, { }, ( ).
> >> < 2> 2>&1 &>, here documents << and <<-, quoted
delimiters, here strings <<<, pipes, |&, process substitution <(...)
and >(...), and /dev/null, /dev/stdout, /dev/stderr.
: . [ alias bg break builtin cd command compgen complete compopt
continue declare dirs disown echo eval exec exit export false fg
getopts help history jobs kill let local mapfile popd printf pushd
pwd read readarray readonly return set shift shopt source test trap
true type typeset unalias unset wait
Their options, exit statuses and output follow bash 5, down to the format of
declare -p, export -p, set -o, shopt and trap -p, and the bash
differential checks each of them. set -e -u -x -f -o pipefail --, shopt -s globstar nullglob dotglob extglob nocasematch expand_aliases, trap for EXIT
HUP INT QUIT TERM ERR DEBUG RETURN.
complete -F, -W, -C, -X, -P, -S, -G, -o and the actions,
compgen with the same options, and compopt. A completion function gets
COMP_WORDS, COMP_CWORD, COMP_LINE and COMP_POINT and answers in
COMPREPLY, so the bash completion scripts tools ship, git's included, run
unchanged. The details are in Tab completion.
* ? [a-z] [!a-z], extended groups ?(a) *(a) +(a) @(a|b)
!(a), and ** for a recursive walk when globstar is on. [[ ... ]]
including =~ with BASH_REMATCH, -v, && and ||.
History expansion !!, !$, !42 and !prefix, which print what they
became before running.
exec 3> log exec 3>&- printf 'x\n' >&3
exec > file cmd 4< input cmd 2>&1 >fileDescriptors 3 to 9 work for the shell and its builtins, exec with no command
keeps its redirections, and N>&- closes one.
Splitting follows the bash rules exactly: IFS whitespace collapses, each
non whitespace IFS character delimits one field so a::b keeps the empty
one, a quoted "$*" and "${a[*]}" join with the first character of IFS,
and an unquoted ${a[@]} splits its elements again.
coproc.${!prefix@}in its array form, and${v@a}attribute listing.hash,times,ulimit,umaskandlogoutare not builtins.setaccepts every option bash has and reports it inset -oand$-, soset -Eeuo pipefailruns, but only-e,-u,-x,-fandpipefailchange anything.- An alias defined in a script is not expanded on that script's later lines, because FreSH reads a whole script before running it. A file sourced after the definition does see it.
- Duplicating an input descriptor,
cmd <&3and3<&-, and a here string on a descriptor other than 0,3<<< text.read -u 3and3>&-work. complete -D,-Eand-I, and the bash-completion library beyond_get_comp_words_by_refand_filedir.bindbinds keys in the FreSH way, described in shortcuts.jobs -n,jobs -x,suspendandset -b.- Signals other than the eight above, which Windows does not deliver.
- A descriptor above 2 is not passed to an external program.
printf x >&3works,some-program.exe >&3does not, because Windows only inherits the three standard handles.
- Paths use forward slashes, and
\stays an escape character. - Completion splits words on
COMP_WORDBREAKSwithout the:bash puts in it, soC:/Usersstays one word, and a completion that offers nothing hands Tab back to FreSH's own. Both are explained in Tab completion. printf,echo,find,sortandawkalways use the built in version, so a script behaves the same on every machine. Callgawkor a full path for a different one.${#v},${v:offset:length}andsubstrcount bytes, not characters, so a string with an umlaut in it measures longer than it looks. Everything else about non ASCII text, including paths, works.- Filename matching ignores case, because Windows filenames do. Matching in
caseand[[ ]]is case sensitive unlessnocasematchis set, as in bash. - Output to a console keeps Windows line endings; output to a file or a pipe is written as bytes, so a redirected FreSH produces the same file bash does.
- Line numbers in errors count the lines the parser saw, so a here document body shifts the ones after it.
- An error is louder than in bash in a few places, all listed in how it fails, because a wrong answer is worse than a stop.
- Job control is always on. In a script,
fg,bgandstopwork and a job stopped withstoporkill -STOPis listed as stopped, where bash withoutset -msaysno job controland never notices the stop. A job killed by a signal stays injobsuntil it is listed or waited for, where a bash script drops it after the next command. Windows has no SIGTSTP and no Ctrl+Z:stop,kill -STOPandkill -TSTPsuspend the job's threads, and any other signal ends the job outright, so a job there cannot trap it. A background pipeline is one job with one process id.