Oils Reference — Chapter Command Language

This chapter describes the command language for OSH, and some YSH extensions.

(in progress)

In This Chapter

Quick Sketch: What's a Command?

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

simple-command

Commands are composed of words. The first word may be the name of

  1. A builtin shell command
  2. A YSH proc or shell "function"
  3. A Hay node declared with hay define
  4. An external command
  5. An alias

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'

semicolon ;

Run two commands in sequence like this:

echo one; echo two

or this:

echo one
echo two

Conditional

case

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

For bash compatibility, the ;; terminator can be substituted with either:

if

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'
}

dbracket [[

Statically parsed boolean expressions, from bash and other shells:

x=42
if [[ $x -eq 42 ]]; then
  echo yes
fi  # => yes

Compare with the test builtin, which is dynamically parsed.

See bool-expr for the expression syntax.

true

Do nothing and return status 0.

if true; then
  echo hello
fi

false

Do nothing and return status 1.

if false; then
  echo 'not reached'
else
  echo hello
fi

colon :

Like true: do nothing and return status 0.

bang !

Invert an exit code:

if ! test -d /tmp; then   
  echo "No temp directory
fi

and &&

mkdir -p /tmp && cp foo /tmp

or ||

ls || die "failed"

Iteration

while

POSIX

until

POSIX

for

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.

for-expr-sh

A bash/ksh construct:

for (( i = 0; i < 5; ++i )); do
  echo $i
done

Control Flow

These are keywords in Oils, not builtins!

break

Break out of a loop. (Not used for case statements!)

continue

Continue to the next iteration of a loop.

return

Return from a function.

exit

Exit the shell process with the given status:

exit 2

Grouping

sh-func

POSIX:

f() {
  echo args "$@"
}
f 1 2 3

sh-block

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.

subshell

( echo one; echo two )

In YSH, use forkwait instead of parentheses.

Concurrency

pipe

Pipelines are a traditional POSIX shell construct:

ls /tmp | grep ssh | sort

Related:

ampersand &

Start a command as a background job. Don't wait for it to finish, and return control to the shell.

The PID of the job is recorded in the $! variable.

sleep 1 &
echo pid=$!
{ echo two; sleep 2 } &
wait
wait

In YSH, use the fork builtin.

Redirects

redir-file

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.)

redir-desc

Redirect to a file descriptor:

echo 'to stderr' >&2

here-doc

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'

Other Command

dparen ((

time

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!

YSH Simple

typed-arg

Internal commands (procs and builtins) accept typed arguments in parentheses:

json write (myobj)

Redirects can also appear after the typed args:

json write (myobj) >out.txt

lazy-expr-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.

Redirects can also appear after the lazy typed args:

assert [42 ===x] >out.txt

block-arg

Blocks can be passed to simple commands, either literally:

cd /tmp {
  echo $PWD  # prints /tmp
}
echo $PWD

Or as an expression:

var block = ^(echo $PWD)
cd /tmp (; ; block)

Note that cd has no typed or named arguments, so the two semicolons are preceded by nothing.

Compare with sh-block.

Redirects can appear after the block arg:

cd /tmp {
  echo $PWD  # prints /tmp
} >out.txt

YSH Cond

ysh-if

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'
}

ysh-case

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

YSH Iter

ysh-while

Command or expression:

var x = 5
while (x < 0) {
  setvar x -= 1
}

ysh-for

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"
}
Generated on Mon, 01 Jul 2024 03:11:05 +0000