Why Sponsor Oil? | source | all docs for version 0.15.0 | all versions | oilshell.org
This doc addresses these questions:
The Oil language is a graceful upgrade to shell, and the behavior of variables follows from that philosophy.
Oil has 5 keywords affect shell variables. Unlike shell builtins, they're statically-parsed, and take dynamically-typed expressions on the right.
var
and const
This is similar to JavaScript.
proc p {
var name = 'Bob'
const age = (20 + 1) * 2
echo "$name is $age years old" # Bob is 42 years old
}
setvar
and setglobal
proc p {
var name = 'Bob' # declare
setvar name = 'Alice' # mutate
setglobal g = 42 # create or mutate a global variable
}
setref
(advanced)"Out Params" are a more controlled version of shell's dynamic scope. They reuse the nameref mechanism.
proc p(s, :myout) { # declare out param with :
setref myout = "prefix-$s" # mutate it to "return" value to caller
}
$(myproc)
to retrieve it.__
prefix. This avoids a problem with nameref cycle detection.Shell and bash have grown many mechanisms for "declaring" and mutating variables:
x=foo
declare
, local
, and readonly
-n
"nameref" flagExamples:
readonly name=World # no spaces allowed around =
declare foo="Hello $name"
foo=$((42 + a[2]))
declare -n ref=foo # $foo can be written through $ref
These constructs are all discouraged in Oil code.
The "top-level" of the interpreter is used in two situations:
Experienced Oil users should know that keywords like var
behave differently
in the top-level scope vs. proc
scope. This is due to the tension between
shell's interactive nature and Oil's strictness (and to the dynamic nature of
the source
builtin).
For reference, JavaScript's modern let
keyword has similar behavior.
Before going into detail on keyword behavior, here are some practical guidelines:
setvar
only. This keyword is like Python's
assignment operator: it creates or mutates a variable.
proc
.
proc main(@argv)
.var
and const
.setvar
to mutate local variables, and setglobal
to mutate globals.const
declarations. (You can use var
,
but it has special rules, explained below.)That's all you need to remember. The following sections explain the rationale for these guidelines.
The lack of static checks affects the recommended usage for both interactive sessions and batch scripts.
setvar
onlyAs mentioned, you only need the setvar
keyword in an interactive shell:
oil$ setvar x = 42 # create variable 'x'
oil$ setvar x = 43 # mutate it
Details on top-level behavior:
var
behaves like setvar
: It creates or mutates a variable. In other
words, a var
definition can be redefined at the top-level.const
can also redefine a var
.var
can't redefine a const
because there's a dynamic check that
disallows mutation (like shell's readonly
).const
onlyIt's simpler to use only constants at the top level.
const USER = 'bob'
const HOST = 'example.com'
proc p {
ssh $USER@$HOST ls -l
}
This is so you don't have to worry about a var
being redefined by a statement
like source mylib.sh
. A const
can't be redefined because it can't be
mutated.
It may be useful to put mutable globals in a constant dictionary, as it will prevent them from being redefined:
const G = {
mystate = 0
}
proc p {
setglobal G->mystate = 1
}
proc
Scope Has Static ChecksProcs are Oil's stricter notion of "shell functions", and they have additional static checks (parse errors):
var
or const
. A
duplicate declaration is a parse error.const
is a parse error.setvar
of an undeclared variable is a parse error.Procs are designed to be encapsulated and composable like processes. But the dynamic scope rule that Bourne shell functions use breaks encapsulation.
Dynamic scope means that a function can read and mutate the locals of its caller, its caller's caller, and so forth. Example:
g() {
echo "f_var is $f_var" # g can see f's local variables
}
f() {
local f_var=42
g
}
f
Oil code should use proc
instead. Inside a proc call, the dynamic_scope
option is implicitly disabled (equivalent to shopt --unset dynamic_scope
).
This means that adding the proc
keyword to the definition of g
changes its
behavior:
proc g() {
echo "f_var is $f_var" # Undefined!
}
This affects all kinds of variable references:
proc p {
echo $foo # look up foo in command mode
var y = foo + 42 # look up foo in expression mode
}
As in Python and JavaScript, a local foo
can shadow a global foo
. Using
CAPS
for globals is a common style that avoids confusion. Remember that
globals should usually be constants in Oil.
In shell, these language constructs assign to variables using dynamic scope. In Oil, they only mutate the local scope:
x=val
x+=val
, a[i]=val
, a[i]+=val
export x=val
and readonly x=val
${x=default}
mycmd {x}>out
(stores a file descriptor in $x
)(( x = 42 + y ))
These builtins are also "isolated" inside procs, using local scope:
Oil Builtins:
--assign-status
All local variables in shell functions and procs live in the same scope. This
includes variables declared in conditional blocks (if
and case
) and loops
(for
and while
).
proc p {
for i in 1 2 3 {
echo $i
}
echo $i # i is still 3
}
It also includes Oil's first-class blocks:
var x = 42
cd /tmp {
var x = 0 # ERROR: x is already declared
}
The expression to the left of =
is called a place. These are basically
Python or JavaScript expressions, except that you add the setvar
or
setglobal
keyword.
setvar x[1] = 2 # array element
setvar d['key'] = 3 # dict element
setvar d->key = 3 # syntactic sugar for the above
setvar x, y = y, x # swap
shopt --set parse_equals
is currently off in both OSH and Oil. It allows
constants to be declared without the const
keyword:
const x = 'foo'
x = 'foo' # Similar. This is NOT a mutation as in C or Java.
However, it doesn't do a static check for 'const' already defined in proc. It will be used for future config-like use cases:
subdomain app.example.com { # subdomain proc takes a string and block
root = '/home/www/bin/' # no var or const keyword necessary
}
subdomain docs.example.com {
root = '/home/www/docs/' # not a redefinition
# blocks may or may not introduce a new scope
}
When it's enabled, x=foo
(no spaces) is disallowed to prevent confusion. Use
the env
command instead:
env PYTHONPATH=. ./foo.py # good
PYTHONPATH=. ./foo.py`. # disallowed because it would be confusing
Temp bindings precede a simple command:
PYTHONPATH=. mycmd
They create a new namespace on the stack where each cell has the export
flag
set (declare -x
).
In Oil, the lack of dynamic scope means that they can't be read inside a
proc
. So they're only useful for setting environment variables, and can be
replaced with:
env PYTHONPATH=. mycmd
env PYTHONPATH=. $0 myproc # using the ARGV dispatch pattern
This section may help experienced shell users understand Oil.
Shell:
g=G # global variable
readonly c=C # global constant
myfunc() {
local x=X # local variable
readonly y=Y # local constant
x=mutated # mutate local
g=mutated # mutate global
newglobal=G # create new global
caller_var=mutated # dynamic scope (Oil doesn't have this)
}
Oil:
var g = 'G' # global variable (discouraged)
const c = 'C' # global constant
proc myproc {
var x = 'L' # local variable
const y = 'Y' # local constant
setvar x = 'mutated' # mutate local
setglobal g = 'mutated' # mutate global
setvar newglobal = 'G' # create new global
# There's no dynamic scope, but you can use
# "out params" with setref.
}
setvar
in
interactive shells, and only const
in the global scope of programs.var
at the top level was partly inspired by this
paper. It's consistent with bash's declare
, and similar to JavaScript's
let
.