Bill Tutorial

Author: Adrian Perez <aperez@igalia.com>
License:GPL3
Copyright: 2008-2009 Igalia S.L.

Abstract

This step-by-step tutorial is a quick introduction to Bill, and covers using the usage of the interactive interpreter, usage of already available modules, and creating new ones.

Contents

Some literature first...

If you do code shell scripts it is almost sure that you have implemented the same functionality in more than one script, or maybe you found yourself copying and pasting snippets from others. Traditional shell programming is tedious and error prone: enter the world of Bill!

Bill makes shell programming a refreshing breeze, thanks to two of its main features:

Maybe the “reusable” and “modular” buzzwords have already picked up your attention, but just in case you are not convinced to continue reading the tutorial, Bill could look more attractive to you knowing that you do not need it at all once your code reaches production-grade quality: standalone versions of scripts can be automagically created with the included toolchain.

Using the interpreter

The Bill interpreter is usually installed at /usr/local/bin/bill or /usr/bin/bill, which are likely to be in the shell search path for programs. Starting the interpreter is only a matter of typing:

bill

Typing the end-of-file character (Ctrl-D) at the primary prompt will exit the interpreter. This can be accomplished using the exit command as well.

The interpreter will have the same capabilities as the installed Bash version, for example you will have line-editing functions if Bash was compiled with support for the GNU readline library. Remember that Bill builds upon Bash!

Commands will be executed interactively in a read-eval-print loop (REPL) when invoked without arguments. When passing a file name as argument, commands will be read from it instead and executed in sequence.

Interactive mode

Using Bill interactively allows for running code in the same environment as scripts do, and although you could use it as your day-to-day shell it is intended to be used for testing. Let us fire up the interpreter by entering bill at the command line:

aperez ~ $ bill

The primary prompt will greet you:

(bill)

When entering multi-line commands, the secondary prompt will be shown until all input lines have been read:

(bill) the_world_is_flat=true
(bill) if $the_world_is_flat ; then
  ...)     echo 'Be careful not to fall off!'
  ...) fi
Be careful not to fall off!

Executable scripts

On Unix systems, Bill scripts can be made directly executable, like regular shell scripts, by adding the line:

#! /usr/bin/env bill

at the beginning of the script (assuming that the interpreter is on the user's $PATH) and giving the file executable mode. The #! must be exactly the two first characters of the file. Note that the hash, or pound, is used to start comments in Bill, like in regular shell scripts.

The script can be given executable mode by using the chmod command:

$ chmod +x myscript

Defining functions

We can create a function that concatenates all of its arguments using a particular separator:

(bill) concatenate () {
  ...)     local separator=$1 result=$2 ; shift 2
  ...)     for word in "$@" ; do
  ...)         result+="$separator$word"
  ...)     done
  ...)     echo "$result"
  ...) }

Then we can use the function just defined:

(bill) concatenate - tic tac toe
tic-tac-toe

A name followed by parentheses () introduces a function definition. The contents of the body are enclosed within the brackets { and }. Defining a function makes its name available as a usable command. Some remarks regarding functions in shell code:

If you need to reuse the output of a function you can use $(...) to capture its output, as it it was a regular program:

(bill) output=$(concatename - tic tac toe)
(bill) echo "Output was: ${output}"
Output was: tic-tac-toe

Using modules

Importing existing modules

Existing modules can be readily used by bringing them into the execution environment. Thanks to use, one of Bill's builtins, this is an easy task:

(bill) use text/string
(bill) string_length hello
5

Module names are composed of a category name, a slash (/) and the module name. Categories have no real meaning and are only used to group related modules. Functions defined by the module are prefixed with the name of the module and an underscore (_). In the previous example the string module from the text category was imported. It defines several functions, including the used string_length function.

Regarding the the use command, you should know that:

  • Modules are searched in the directories specified in the $BILLPATH environment variable. By default it contains the path of the standard library and the directory where the interactive interpreter was started. If the interpreter is not interactive, the path where the script passed as argument resides in the search path instead.
  • It takes care of only importing modules once. Issuing use foo/bar twice will only import the module the first time.

Crafting your own

Modules are regular text files with .bsh suffix. Categories are no more than directories in the file system. If you wanted to create a module named bar inside category foo just create a foo/bar.bsh text file:

mkdir foo
echo 'echo "Hello module..."' > foo/bar.bsh

Now you can start the interpreter and import the module:

aperez ~ $ bill
(bill) use foo/bar
Hello module...

Using categories is a convention used in the Bill standard library, you do not need to use them in your programs. If you decide not to use them, you will not need to use directories and remove the category name from use invocations.

You can add any code you want to your modules, but it is recommended to only define functions and variables. Also, names should be prefixed with the name of the module plus an underscore, unless you have a very good reason to do so.

A full example

Note

You can find this example inside the examples/ subdirectory of the Bill source code distribution.

First, let us create a hello.bsh file for a fictitious hello module which includes a function capable of greeting people (or the entire world):

#! /usr/bin/env bill
#++
#   ==============
#   Example module
#   ==============
#   :Author: Adrián Pérez <aperez@igalia.com>
#   :Copyright: Igalia S.L, 2008
#   :Abstract: Provides an example ``hello`` function.
#       This module is used by the ``hello`` test script.
#--

#++ hello [ name ]
#
#   The (in)famous “Hello, world!” example, as a Bill module.
#   Pass ``name`` to greet someone, otherwise the full world will be greeted
#   instead.
#
#--
hello () {
    echo "Hello ${1:-world}!"
}

Comment blocks starting with #++ and ending with #-- are documentation strings. There is a set of tools included with Bill which can be used to generate documentation, but we will not cover that topic in the tutorial.

In the same directory, write down a hello file which uses the module above:

#! /usr/bin/env bill

use hello

hello "Linus"
hello "Richard"
hello

Then, you can add the execution permission and test it:

aperez ~ $ chmod +x hello
aperez ~ $ ./hello
Hello Linus!
Hello Richard!
Hello world!
aperez ~ $