Why Sponsor Oils? | source | all docs for version 0.20.0 | all versions | oilshell.org
This doc addresses these questions:
YSH is a graceful upgrade to shell, and the behavior of variables follows from that philosophy.
YSH has 5 keywords affect shell variables. Unlike shell builtins, they're statically-parsed, and take dynamically-typed expressions on the right.
var
and const
It looks like JavaScript:
var name = 'Bob'
const age = (20 + 1) * 2
echo "$name is $age years old" # Bob is 42 years old
Note that const
is enforced by a dynamic check. It's meant to be used at the
top level only, not within proc
or func
.
const age = 'other' # Will fail because `readonly` bit is set
setvar
and setglobal
proc p {
var name = 'Bob' # declare
setvar name = 'Alice' # mutate
setglobal g = 42 # create or mutate a global variable
}
Place
(advanced)A Place
is a more principled mechanism that "replaces" shell's dynamic scope.
To use it:
&
prefix operatorsetValue(x)
method.Example:
proc p (s; out) { # place is a typed param
# mutate the place
call out->setValue("prefix-$s")
}
var x
p ('foo', &x) # pass a place
echo x=$x # => x=prefix-foo
$(myproc)
to retrieve it.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 YSH code.
The "top-level" of the interpreter is used in two situations:
Experienced YSH users may notice that var
and setvar
behave differently in
the top-level scope vs. proc
scope. This is caused by the tension between
the interactive shell and the strictness of YSH.
In particular, the source
builtin is dynamic, so YSH can't know all the names
defined at the top level.
For reference, JavaScript's modern let
keyword has similar behavior.
Before going into detail on keyword behavior, here are some practical guidelines:
x=y
, or YSH setvar
. You can think
of setvar
like Python's assignment operator: it creates or mutates a
variable.
proc
.
proc main { }
.const
declarations. (You can use var
,
but it has special rules, explained below.)proc
and func
should have variables declared with var
.setvar
to mutate local variables, and
setglobal
to mutate globals.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:
ysh$ setvar x = 42 # create variable 'x'
ysh$ 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
and func
Scope Have Static ChecksThese YSH code units have additional static checks (parse errors):
var
. A duplicate
declaration 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
YSH 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 YSH.
In shell, these language constructs assign to variables using dynamic scope. In YSH, 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:
YSH 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
}
This includes first-class YSH blocks:
proc p {
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
Hay allows const
declarations without the keyword:
hay define Package
Package cpython {
version = '3.12' # like const version = ...
}
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 YSH, 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 YSH.
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 (YSH doesn't have this)
}
YSH:
var g = 'G' # global variable (discouraged)
const c = 'C' # global constant
proc myproc {
var x = 'L' # local variable
setvar x = 'mutated' # mutate local
setglobal g = 'mutated' # mutate global
setvar newglobal = 'G' # create new global
}
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
.