Why Sponsor Oils? | blog | oilshell.org

Translating Shell to Oil, Part Two

2017-02-06 (Last updated 2019-10-05)

Update 10/2019: The Oil language has changed. This post is accurate in spirit but not in detail.

In yesterday's post, we looked at the translation of make-hdb.sh and the oil language features it uses. Today we continue with three more translations.

Make sure to open the source files in a new window, and widen it so that the two code panes appear side-by-side.

Example 1: more/repo.sh from Aboriginal Linux

I explained the [ to test conversion yesterday, so the only new thing here is subshell syntax. Subshells look like this:

shell {
  cd packages/busybox
  echo "Working out of $PWD"
  git pull
}

Again, all blocks use the same curly brace syntax, rather than having a mix of { }, ( ), do done, and then fi.

Additionally, I plan to add a block argument to cd for this common idiom, in the style of Ruby:

cd packages/busybox {
  echo "Working out of $PWD"
  git pull
}  # working directory restored on block exit; no need to fork()

This is nicer than a pushd / popd pair in bash, especially in the presence of exceptions. A non-zero exit code under set -o errexit is essentially an uncatchable exception in shell. Oil will allow you to catch these errors.

Example 2: reboot from /etc/init.d

Notice the following:

(1) Mutable global variables require the global keyword. Shell has the anachronism that variables are global by default, unless declared with local keyword. In Oil, local variables are declared with var or const.

(2) Initialization uses the = operator, but mutation uses :=. Unlike shell and Python, variables aren't created automatically upon assignment. They're initialized once and then used multiple times.

The semantics are somewhat like ES6, but I'm not sure whether there will be block scope.

(3) . (period) is not an alias for source. Again, oil prefers readable names. Auto-completion makes it easier to type such names.

(4) case is spelled matchstr and uses curly braces. The match keyword is reserved for a construct that will work on arbitrary types like integers, strings, and structured data.

The code looks messy because the conversion intentionally preserves formatting. I prefer it written like this:

matchstr $1 {
  'start' {  # No-op
  }
  'restart' | 'reload' | 'force-reload' {
    echo "Error: argument '$1' not supported" > !2
    exit 3
  }
  'stop' {
    do_stop
  }
  * {
    echo "Usage: $0 start|stop" > !2
    exit 3
  }
}

(5) File descriptors are prefixed with !. Shell has a bad syntax for redirects:

echo 'to stderr' 1>&2   # Valid
echo 'to stderr' 1>& 2  # Valid space after >&
echo 'to stderr' 1 >&2  # INVALID space before >&

That is, > and >& are actually unary prefix operators, but there's lexical hack to accept an optional descriptor to their left. This makes them seem like binary operators with quirky rules about whitespace.

In oil, file descriptors have their own syntax to distinguish them from words:

echo 'to stderr' > !2             # write to stderr, like >&2
echo 'to stderr' !1 > !2          # same thing, like 1>&2
echo 'to stderr' !2 > stderr.txt
cat < stdin.txt                   # stdin from file
cat !0 < stdin.txt                # same thing, like 0< stdin.txt

The ! syntax also leaves room for named file descriptors.

Example 3: console-setup from /etc/init.d

This longer script makes use of many constructs we've seen: proc, if, matchstr, test, source, shell, and global.

Proponents of systemd argue that init shell scripts are poorly written, and there's evidence of that here.

In the run_by_init function, the subshell construct () is confused with the grouping construct { }. I've seen this many times, and it's in part due to the fact that () are proper operators and easier to use without spaces:

(echo hi)     # valid
( echo hi )   # valid
( echo hi; )  # valid

{echo hi}     # INVALID, tries to run the '{echo' command
{ echo hi }   # INVALID, tries to print 'hi }'
{ echo hi; }  # valid.  Space after { and terminating ; are required.

In Problems with $((, I mentioned that all five occurrences I found of a subshell () within a command substitution $() were improper usages! They should have used grouping { } within a command substitution $().

Conclusion

Syntax clearly matters, and I hope that oil's syntax will be easy to learn for

  1. experienced shell programmers,
  2. programmers who primarily use other languages, and
  3. first-time programmers, who will especially benefit from intelligent auto-completion.

Also recall what I mentioned yesterday: a new syntax will not only be easier to learn, but it will also enable new features. Shell needs new features. In a decade where it's been almost entirely static, languages like Python and JavaScript have evolved tremendously.