source | all docs for version 0.10.0 | all versions | oilshell.org
This document is for sophisticated shell users.
You're unlikely to encounter these incompatibilities in everyday shell usage. If you do, there's almost always a simple workaround, like adding a space or a backslash.
OSH is meant to run all POSIX shell programs, and most bash programs.
Roughly speaking, shells treat arithmetic like "macro processing", while OSH treats it more like part of a programming language.
Despite these differences, OSH is very compatible with existing shell scripts.
Note that you can opt into more errors with shopt -s strict_arith
.
Arithmetic is statically parsed, so expressions like $(( 1 $op 2 ))
fail with
a parse error. Use an explicit eval
for these rare use cases.
Related: A 30-year-old security problem / Simple Word Evaluation
shopt -s eval_unsafe_arith
If you have a variable like code='1+2'
, OSH doesn't accept
a[$code]=value # dynamic parsing and evaluation in bash, mksh, zsh
or
echo ${a[$code]} # ditto
by default. If you want this behavior, you can turn on shopt -s eval_unsafe_arith
.
In other shells, printf %d invalid_integer
prints 0
and a warning. OSH
gives you a runtime error.
This section describes differences related to static parsing. OSH avoids the dynamic parsing of most shells.
(Note: This section should encompass all the failures from the wild tests and spec tests.
Strings should be quoted inside array indices:
No:
"${SETUP_STATE[$err.cmd]}"
Yes:
"${SETUP_STATE["$err.cmd"]}"
When unquoted, the period causes an ambiguity with respect to regular arrays vs. associative arrays. See Parsing Bash is Undecidable.
You can have a subshell in a command sub, but it usually doesn't make sense.
In OSH you need a space after $(
. The characters $((
always start an
arith sub.
No:
$((cd / && ls))
Yes:
$( (cd / && ls) ) # Valid but usually doesn't make sense.
$({ cd / && ls; }) # Use {} for grouping, not (). Note trailing ;
$(cd / && ls) # Even better
The OSH parser distinguishes these two constructs with a space:
[[ !(a == a) ]]
is an extended glob.[[ ! (a == a) ]]
is the negation of an equality test.In bash, the parsing of such expressions depends on shopt -s extglob
. In
OSH, shopt -s extglob
is accepted, but doesn't affect parsing.
Lines like EOF]
or EOF)
don't end here docs. The delimiter must be on its
own line.
No:
a=$(cat <<EOF
abc
EOF)
a=$(cat <<EOF
abc
EOF # this is not a comment; it makes the EOF delimiter invalid
)
Yes:
a=$(cat <<EOF
abc
EOF
) # this is actually a comment
Bash allows:
a[1 + 2 * 3]=value
OSH only allows:
a[1+2*3]=value
because it parses with limited lookahead. The first line would result in the
execution of a command named a[1
.
This means that they aren't "dynamic":
b=break
while true; do
$b # doesn't break in OSH
done
Static control flow will allow static analysis of shell scripts.
(Test cases are in spec/loop).
For example, push
is a builtin in Oil, but not in bash
. Use env push
or
/path/to/push
if you want to run an external command.
(Note that a user-defined function push
take priority over the builtin
push
.
In contrast with builtins, keywords affect shell parsing.
For example, func
is a keyword in Oil, but not in bash
. To run a command
named func
, use command func arg1
.
Note that all shells have extensions that cause this issue. For example, [[
is a keyword in bash
but not in POSIX shell.
These differences occur in subsequent stages of parsing, or in runtime parsing.
No:
{a,b}{ # what does the second { mean?
{a,b}{1...3} # 3 dots instead of 2
Yes:
{a,b}\{
{a,b}\{1...3\}
bash will do a partial expansion in the former cases, giving you a{ b{
and a{1...3} b{1...3}
.
OSH considers them syntax errors and aborts all brace expansion, giving you
the same thing back: {a,b}{
and {a,b}{1...3}
.
In bash, {~bob,~jane}/src
will expand the home dirs of both people. OSH
doesn't do this because it separates parsing and evaluation. By the time tilde
expansion happens, we haven't evaluated the brace expansion. We've only
parsed it.
(mksh agrees with OSH, but zsh agrees with bash.)
Don't use ambiguous syntax for a character class consisting of a single bracket character.
No:
echo [[]
echo []]
Yes:
echo [\[]
echo [\]]
The ambiguous syntax is allowed when we pass globs through to libc
, but it's
good practice to be explicit.
In rare cases, OSH processes backslashes within backticks differently than other shells. However there are two workarounds that are compatible with every shell.
No:
`echo \"` # is this a literal quote, or does it start a string?
Yes:
$(echo \") # $() can always be used instead of ``.
# There's no downside to the more modern construct.
`echo \\"` # also valid, but $() is more readable
Notes:
Almost all "real" aliases should work in OSH. But these don't work:
alias left='{'
left echo hi; }
(cases #33-#34 in spec/alias)
or
alias a=
a (( var = 0 ))
Details on the OSH parsing model:
SimpleCommand
are the only ones that are further alias-expanded.For example, these result in SimpleCommand
nodes:
ls -l
read -n 1
(normally a builtin)myfunc foo
These don't:
x=42
declare -r x=42
break
, continue
, return
, exit
— as explained above, these are
keywords and not builtins.{ echo one; echo two; }
for
, while
, case
, functions, etc.In bash, $array
is equivalent to ${array[0]}
, which is very confusing
(especially when combined with set -o nounset
).
No:
array=(1 2 3)
echo $array # Runtime error in OSH
Yes:
echo ${array[0]} # explicitly choose the first element
echo "${array[@]}" # explicitly choose the whole array
NOTE: Setting shopt -s strict_array
further reduces the confusion between
strings and arrays. See the options doc for details.
Most shells split the entries of arrays like "$@"
and "${a[@]}"
here:
echo ${undef:-"$@"}
In OSH, omit the quotes if you want splitting:
echo ${undef:-$@}
I think OSH is more consistent, but it disagrees with other shells.
In bash, cells (locations for values) are tagged with types. For example, these two statements are different:
declare -A assoc # unset cell that will LATER be an assoc array
declare -A assoc=() # empty associative array
set -u # now we can tell the difference
OSH behaves more like Python or JavaScript: values are tagged with types
like Str
and AssocArray
.
This is a consequence of the previous point.
OSH has bash-compatible arrays, which are created like this:
local indexed=(foo bar)
local -a indexed=(foo bar) # -a is redundant
echo ${indexed[1]} # bar
local assoc=(['one']=1 ['two']=2)
local -A assoc=(['one']=1 ['two']=2) # -A is redundant
echo ${assoc['one']} # 1
In bash, the distinction between the two is blurry, e.g. in cases like this:
local -A x=(foo bar) # -A disagrees with literal
local -a y=(['one']=1 ['two']=2) # -a disagrees with literal
The assignment builtins are export
, readonly
, local
, and
declare
/typeset
.
In bash, you can do unusual things with them:
vars='a=b x=y'
touch foo=bar.py spam=eggs.py
declare $vars *.py # assigns at least 4 variables
echo $a # b
echo $x # y
echo $foo # bar.py
echo $spam # eggs.py
In contrast, OSH disables splitting and globbing within assignment builtins. This is more like the behavior of zsh.
On a related note, assignment builtins are both statically and dynamically parsed:
declare x=$y
when $y
contains spaces.declare $1
where $1
is a=b
mksh
, and have other differencesThat is, in OSH and mksh, something like echo *.@(cc|h)
is an extended glob.
But echo $x
, where $x
contains the pattern, is not.
For more details and differences, see the Extended Glob section of the Word Language doc.
The OSH completion API is mostly compatible with the bash completion API,
except that it moves the responsibility for quoting out of plugins and onto
the shell itself. Plugins should return candidates as argv
entries, not
shell words.
See the completion doc for details.
The rules for history substitution like !echo
are simpler. There are no
special cases to avoid clashes with ${!indirect}
and so forth.
TODO: Link to the history lexer.
External:
set -o posix
does in bash.