Why Sponsor Oils? | source | all docs for version 0.20.0 | all versions | oilshell.org
Warning: Work in progress! Leave feedback on Zulip or Github if you'd like this doc to be updated.
This chapter in the Oils Reference describes the command language for both OSH and YSH.
OSH:
print-files() {
for name in *.py; do
if test -x "$name"; then
echo "$name is executable"
fi
done
}
YSH:
proc print-files {
for name in *.py {
if test -x $name { # no quotes needed
echo "$name is executable"
}
}
}
Commands are composed of words. The first word may be the name of
proc
or shell "function"hay define
Examples:
echo hi # a shell builtin doesn't start a process
ls /usr/bin ~/src # starts a new process
myproc "hello $name"
myshellfunc "hello $name"
myalias -l
Redirects are also allowed in any part of the command:
echo 'to stderr' >&2
echo >&2 'to stderr'
echo 'to file' > out.txt
echo > out.txt 'to file'
Run two commands in sequence like this:
echo one; echo two
or this:
echo one
echo two
Match a string against a series of glob patterns. Execute code in the section below the matching pattern.
path='foo.py'
case "$path" in
*.py)
echo 'python'
;;
*.sh)
echo 'shell'
;;
esac
Test if a command exited with status zero (true). If so, execute the corresponding block of code.
Shell:
if test -d foo; then
echo 'foo is a directory'
elif test -f foo; then
echo 'foo is a file'
else
echo 'neither'
fi
YSH:
if test -d foo {
echo 'foo is a directory'
} elif test -f foo {
echo 'foo is a file'
} else {
echo 'neither'
}
Do nothing and return status 0.
if true; then
echo hello
fi
Do nothing and return status 1.
if false; then
echo 'not reached'
else
echo hello
fi
Like true
: do nothing and return status 0.
Invert an exit code:
if ! test -d /tmp; then
echo "No temp directory
fi
mkdir -p /tmp && cp foo /tmp
ls || die "failed"
POSIX
POSIX
For loops iterate over words.
YSH style:
var mystr = 'one'
var myarray = :| two three |
for i in $mystr @myarray *.py {
echo $i
}
Shell style:
local mystr='one'
local myarray=(two three)
for i in "mystr" "${myarray[@]}" *.py; do
echo $i
done
Both fragments output 3 lines and then Python files on remaining lines.
A bash/ksh construct:
for (( i = 0; i < 5; ++i )); do
echo $i
done
These are keywords in Oils, not builtins!
Break out of a loop. (Not used for case statements!)
Continue to the next iteration of a loop.
Return from a function.
Exit the shell process with the given status:
exit 2
POSIX:
f() {
echo args "$@"
}
f 1 2 3
POSIX:
{ echo one; echo two; }
The trailing ;
is necessary in OSH, but not YSH. In YSH, parse_brace
makes
}
is more of a special word.
( echo one; echo two )
Use forkwait in YSH instead.
CMD &
The &
language construct runs CMD in the background as a job, immediately
returning control to the shell.
The resulting PID is recorded in the $!
variable.
Examples of redirecting the stdout
of a command:
echo foo > out.txt # overwrite out.txt
date >> stamp.txt # append to stamp.txt
Redirect to the stdin
of a command:
cat < in.txt
Redirects are compatible with POSIX and bash, so they take descriptor numbers on the left:
make 2> stderr.txt # '2>' is valid, but '2 >' is not
Note that the word argument to file redirects is evaluated like bash, which is different than other arguments to other redirects:
tar -x -z < Python* # glob must expand to exactly 1 file
tar -x -z < $myvar # $myvar is split because it's unquoted
In other words, it's evaluated as a sequence of 1 word, which produces zero to N strings. But redirects are only valid when it produces exactly 1 string.
(Related: YSH uses shopt --set simple_word_eval
, which means that globs that
match nothing evaluate to zero strings, not themselves.)
Redirect to a file descriptor:
echo 'to stderr' >&2
TODO: unbalanced HTML if we use <<?
cat <<EOF
here doc with $double ${quoted} substitution
EOF
myfunc() {
cat <<-EOF
here doc with one tab leading tab stripped
EOF
}
cat <<< 'here string'
time [-p] pipeline
Measures the time taken by a command / pipeline. It uses the getrusage()
function from libc
.
Note that time is a KEYWORD, not a builtin!
Internal commands (procs and builtins) accept typed arguments.
json write (myobj)
Block literals have a special syntax:
cd /tmp {
echo $PWD
}
This is equivalent to:
var cmd = ^(echo $PWD) # unevaluated command
cd /tmp (cmd) # pass typed arg
Expressions in brackets like this:
assert [42 === x]
Are syntactic sugar for:
assert (^[42 === x])
That is, it's single arg of type value.Expr
.
Blocks can be passed to builtins (and procs eventually):
cd /tmp {
echo $PWD # prints /tmp
}
echo $PWD
Compare with sh-block.
Binds a name to a YSH expression on the right, with a dynamic check to prevent mutation.
const c = 'mystr' # equivalent to readonly c=mystr
const pat = / digit+ / # an eggex, with no shell equivalent
If you try to re-declare or mutate the name, the shell will fail with a runtime
error. const
uses the same mechanism as the readonly
builtin.
Consts should only appear at the top-level, and can't appear within proc
or
func
.
Initializes a name to a YSH expression.
var s = 'mystr' # equivalent to declare s=mystr
var pat = / digit+ / # an eggex, with no shell equivalent
It's either global or scoped to the current function.
You can bind multiple variables:
var flag, i = parseArgs(spec, ARGV)
var x, y = 42, 43
You can omit the right-hand side:
var x, y # implicitly initialized to null
At the top-level, setvar creates or mutates a variable.
setvar gFoo = 'mutable'
Inside a func or proc, it mutates a local variable declared with var.
proc p {
var x = 42
setvar x = 43
}
You can mutate a List location:
setvar a[42] = 'foo'
Or a Dict location:
setvar d['key'] = 43
setvar d.key = 43 # same thing
You can use any of these these augmented assignment operators
+= -= *= /= **= //= %=
&= |= ^= <<= >>=
Examples:
setvar x += 2 # increment by 2
setvar a[42] *= 2 # multiply by 2
setvar d.flags |= 0b0010_000 # set a flag
Creates or mutates a global variable. Has the same syntax as setvar
.
The =
keyword evaluates an expression and shows the result:
oil$ = 1 + 2*3
(Int) 7
It's meant to be used interactively. Think of it as an assignment with no variable on the left.
The call
keyword evaluates an expression and throws away the result:
var x = :| one two |
call x->append('three')
call x->append(['typed', 'data'])
Procs are shell-like functions, but with named parameters, and without dynamic scope (TODO):
proc copy(src, dest) {
cp --verbose --verbose $src $dest
}
Compare with sh-func.
TODO
To return an expression, wrap it in ()
as usual:
func inc(x) {
return (x + 1)
}
Like shell, you can use a command:
if test --file $x {
echo "$x is a file"
}
You can also use an expression:
if (x > 0) {
echo 'positive'
}
Like the shell case statement, the Ysh case statement has string/glob patterns.
var s = 'README.md'
case (s) {
*.py { echo 'Python' }
*.cc | *.h { echo 'C++' }
* { echo 'Other' }
}
# => Other
We also generated it to typed data within ()
:
var x = 43
case (x) {
(30 + 12) { echo 'the integer 42' }
(else) { echo 'neither' }
}
# => neither
The else
is a special keyword that matches any value.
case (s) {
/ dot* '.md' / { echo 'Markdown' }
(else) { echo 'neither' }
}
# => Markdown
Command or expression:
var x = 5
while (x < 0) {
setvar x -= 1
}
Two forms for shell-style loops:
for name in *.py {
echo "$name"
}
for i, name in *.py {
echo "$i $name"
}
Two forms for expressions that evaluate to a List
:
for item in (mylist) {
echo "$item"
}
for i, item in (mylist) {
echo "$i $item"
}
Three forms for expressions that evaluate to a Dict
:
for key in (mydict) {
echo "$key"
}
for key, value in (mydict) {
echo "$key $value"
}
for i, key, value in (mydict) {
echo "$i $key $value"
}