source | all docs for version 0.8.7 | all versions | oilshell.org
This is an informal, lightly-organized list of recommended idioms for the Oil language. Each section has snippets labeled No and Yes.
No:
local x='my song.mp3'
ls "$x" # quotes required to avoid mangling
Yes:
var x = 'my song.mp3'
ls $x # no quotes needed
No:
local myflags=( --all --long )
ls "${myflags[@]}" "$@"
Yes:
var myflags = %( --all --long )
ls @myflags @ARGV
Oil doesn't split arguments after variable expansion.
No:
local packages='python-dev gawk'
apt install $packages
Yes:
var packages = 'python-dev gawk'
apt install @split(packages)
Even better:
var packages = %(python-dev gawk) # array literal
apt install @packages # splice array
Oil doesn't glob after variable expansion.
No:
local pat='*.py'
echo $pat
Yes:
var pat = '*.py'
echo @glob(pat) # explicit call
Oil doesn't omit unquoted words that evaluate to the empty string.
No:
local e=''
cp $e other $dest # cp gets 2 args, not 3, in sh
Yes:
var e = ''
cp @maybe(e) other $dest # explicit call
No:
local n=3
for x in $(seq $n); do # No implicit splitting of unquoted words in Oil
echo $x
done
Yes:
var n = 3
for x in @(seq $n) { # Explicit splitting
echo $x
}
Note that {1..3}
works in bash and Oil, but the numbers must be constant.
In other words, avoid groveling through backslashes and spaces in shell.
Instead, emit and consume the QSN and QTSV interchange formats.
Custom parsing and serializing should be limited to "the edges" of your Oil programs.
These are discussed in the next two sections, but here's a summary.
write --qsn # also -q
read --qsn :mystr # also -q
read --line --qsn :myline # read a single line
That is, take advantage of the the invariants that the IO builtins respect. (doc in progress)
proc
construct and flagspec
!--qsn
flag.ls
in Python with
little effort.write
Builtin Is Simpler Than printf
and echo
No:
printf '%s\n' "$mystr"
Yes:
write -- $mystr
The write
builtin accepts --
so it doesn't confuse flags and args.
No:
echo -n "$mystr" # breaks if mystr is -e
Yes:
write --end '' -- $mystr
write -n -- $mystr # -n is an alias for --end ''
var myarray = %(one two three)
write -- @myarray
read
builtinNo:
read line # Bad because it mangles your backslashes!
read -r line # Better, but easy to forget
Yes:
read --line # also faster because it's a buffered read
No:
read -d '' # harder to read, easy to forget -r
Yes:
read --all :mystr
\0
(consume find -print0
)No:
# Obscure syntax that bash accepts, but not other shells
read -r -d '' myvar
Yes:
read -0 :myvar
shopt
Instead of set
bin/oil
uses the set
keyword for assignments, although OSH code will often
use setvar
. Using shopt
"clears the way" for this upgrade.
Discouraged:
set -o errexit
Idiomatic:
shopt --set errexit
(As always, using set
is useful if your script needs to run under another
shell.)
:
When Mentioning Variable NamesOil accepts this optional "pseudo-sigil" to make code more explicit.
No:
read -0 record < file.bin
echo $record
Yes:
read -0 :record < file.bin
echo $record
--long-flags
Easier to write:
test -d /tmp
test -d / && test -f /vmlinuz
shopt -u extglob
Easier to read:
test --dir /tmp
test --dir / && test --file /vmlinuz
shopt --unset extglob
No:
( cd /tmp; echo $PWD ) # subshell is unnecessary (and limited)
No:
pushd /tmp
echo $PWD
popd
Yes:
cd /tmp {
echo $PWD
}
No:
set +o errexit
myfunc # without error checking
set -o errexit
Yes:
shopt --unset errexit {
myfunc
}
forkwait
builtin for Subshells, not ()
No:
( not_mutated=foo )
echo $not_mutated
Yes:
forkwait {
setvar not_mutated = 'foo'
}
echo $not_mutated
fork
builtin for async, not &
No:
myproc &
{ sleep 1; echo one; sleep 2; } &
Yes:
fork { myproc }
fork { sleep 1; echo one; sleep 2 }
$1
, $2
, ...No:
f() {
local src=$1
local dest=${2:-/tmp}
cp "$src" "$dest"
}
Yes:
proc f(src, dest='/tmp') { # Python-like default values
cp $src $dest
}
"$@"
No:
f() {
local first=$1
shift
echo $first
echo "$@"
}
Yes:
proc f(first, @rest) { # @ means "the rest of the arguments"
write -- $first
write -- @rest # @ means "splice this array"
}
You can also use the implicit ARGV
variable:
proc p {
cp -- @ARGV /tmp
}
declare -n
Out params are one way to "return" values from a proc
.
No:
f() {
local in=$1
local -n out=$2
out=PREFIX-$in
}
myvar='init'
f zzz myvar # assigns myvar to 'PREFIX-zzz'
Yes:
proc f(in, :out) { # : is an out param, i.e. a string "reference"
setref out = "PREFIX-$in"
}
var myvar = 'init'
f zzz :myvar # assigns myvar to 'PREFIX-zzz'.
# colon is required
That is, dynamic scope is turned off when procs are invoked.
Here's an example of shell functions reading variables in their caller:
bar() {
echo $foo_var # looks up the stack
}
foo() {
foo_var=x
bar
}
foo
In Oil, you have to pass params explicitly:
proc bar {
echo $foo_var # error, not defined
}
Shell functions can also mutate variables in their caller! But procs can't do this, which makes code easier to reason about.
errexit
Bug in POSIX shell, which Oil's strict_errexit
warns you of:
if myfunc; then # oops, errors not checked in myfunc
echo 'success'
fi
Suggested workaround:
if $0 myfunc; then # invoke a new shell
echo 'success'
fi
"$@"
Oil has a run
builtin, which re-enables errexit without the extra process:
if run myfunc; then
echo 'success'
fi
(TODO: decide on this) Or you can also use curly braces for an implicit run
:
if myfunc {
echo 'success'
}
This explicit syntax avoids breaking POSIX shell. You have to opt in to the better behavior.
run
Builtin With !
, ||
, and &&
These constructs require an explicit run
:
No:
! myfunc
myfunc || fail
myfunc && echo 'success'
Yes:
! run myfunc
run myfunc || fail
run myfunc && echo 'success'
Although ||
and &&
are rare in idiomatic Oil code.
&&
Outside of if
/ while
It's implicit because errexit
is on in Oil.
No:
mkdir /tmp/dest && cp foo /tmp/dest
Yes:
mkdir /tmp/dest
cp foo /tmp/dest
No:
local mystr=foo
mystr='new value'
local myint=42 # still a string in shell
Yes:
var mystr = 'foo'
setvar mystr = 'new value'
var myint = 42 # a real integer
No:
x=$(( 1 + 2*3 ))
(( x = 1 + 2*3 ))
Yes:
setvar x = 1 + 2*3
No:
(( i++ )) # interacts poorly with errexit
i=$(( i+1 ))
Yes:
setvar i += 1 # like Python, with a keyword
Container literals in Oil look like %(one two)
and %{key: 'value'}
.
No:
local -a myarray=(one two three)
myarray[3]='THREE'
Yes:
var myarray = %(one two three)
setvar myarray[3] = 'THREE'
No:
local -A myassoc=(['key']=value ['k2']=v2)
myassoc['key']=V
Yes:
# keys don't need to be quoted
var myassoc = %{key: 'value', k2: 'v2'}
setvar myassoc['key'] = 'V'
No:
local x=${a[i-1]}
x=${a[i]}
local y=${A['key']}
Yes:
var x = a[i-1]
setvar x = a[i]
var y = A['key']
No:
if (( x > 0 )); then
echo positive
fi
Yes:
if (x > 0) {
echo 'positive'
}
No:
echo flag=$((1 + a[i] * 3)) # C-like arithmetic
Yes:
echo flag=$[1 + a[i] * 3] # Arbitrary Oil expressions
# Possible, but a local var might be more readable
echo flag=$['1' if x else '0']
No:
local pat='[[:digit:]]+'
if [[ $x =~ $pat ]]; then
echo 'number'
fi
Yes:
if (x ~ /digit+/) {
echo 'number'
}
Or extract the pattern:
var pat = / digit+ /
if (x ~ pat) {
echo 'number'
}
No:
if [[ $x =~ ([[:digit:]]+) ]] {
echo "${BASH_REMATCH[@]}"
}
Yes:
if (x ~ / <d+> /) { # <> is capture
argv.py @M # special M variable
}
No:
if [[ $x == *.py ]]; then
echo Python
fi
TODO: Implement the ~~
operator.
Yes:
if (x ~~ '*.py') {
echo 'Python'
}
No:
case $x in
*.py)
echo Python
;;
*.sh)
echo Shell
;;
esac
Yes (purely a style preference):
case $x { # curly braces
(*.py) # balanced parens
echo 'Python'
;;
(*.sh)
echo 'Shell'
;;
}
TODO
$RANDOM
vs. random()
LANG=C
vs. shopt --setattr LANG=C
set -e
/ errexit
. Oil fixes the
flaky error handling in POSIX shell and bash.