Why Sponsor Oils? | source | all docs for version 0.19.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 by the name of a shell builtin, an Oil proc / shell "function", an external command, or an alias:
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
Oil:
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.
Oil 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 Oil, 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; }
Note the trailing ;
-- which isn't necessary in Oil.
( echo one; echo two )
Use forkwait in Oil 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!
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.
Command or expression:
if (x > 0) {
echo 'positive'
}
case $x {
# balanced parens around patterns
(*.py) echo 'Python' ;;
('README') echo 'README' ;; # consatnt words must be quoted
(*) echo 'Other' ;;
}
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"
}
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'])
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.