Skip to content
Nikita Sirgienko edited this page Feb 14, 2018 · 10 revisions

Contents

  1. Introduction
  2. Diving In
  3. Exploring Bish 3. Variables 3. Functions 3. String Interpolation 3. Shell Functions 3. External Functions 3. Conditionals 3. Loops 3. Pipelines 3. Importing Scripts

Introduction

(Note: under construction).

This document introduces bish, a new lightweight language designed to bring shell scripting into the 21st century. Bish aims to offer familiar and lightweight syntax to allow programmers to quickly get their shell scripts up and running. For portability, bish compiles to bash, which is available on most modern Linux based systems. Additional targets (such as sh, available on even more systems than bash) are planned for the future.

Many people who have written moderate to complex bash scripts will tell you how frustrating it can be to deal with the many idiosyncrasies of bash. As a concrete example, bash functions do not allow for named arguments, which can discourage programmers from encapsulating common pieces of code into separate functions. Simple tasks such as evaluating expressions in bash are made more complicated by necessitating remembering whether to use [[ ... ]] or (( ... )) or [ ... ] or -eq versus ==. The list goes on. Bish offers a familiar and comfortable syntax to express these operations, allowing programmers to rely on the bish compiler to deal with the specifics.

The rest of this document will introduce new users to the bish syntax and lay out a roadmap for future development of bish. The specifics of the language are detailed in the (forthcoming) language reference.

Diving In

First, we'll download and build the bish compiler:

$ git clone https://github.com/tdenniston/bish.git
$ cd bish && make

This will produce the compiler binary, which is named bish. There are no dependencies other than the C++ standard library, so bish should compile on a wide variety of systems. If you run into build problems, please let me know!

Let's begin with the traditional "hello world" example. Create the file hello.bish with the contents:

#!/path/to/bish -r
# hello.bish - Displays a greeting.
println("Hello from Bish!")

Then, simply run the bish script:

$ chmod +x hello.bish
$ ./hello.bish
Hello from Bish!

In addition to the usual "shebang" invocation, there are several other options to execute a bish program.

$ ./bish -r hello.bish

This is equivalent to the shebang usage. This will compile hello.bish into bash code, and pipe the result into the bash binary.

It's also possible to compile and run separately by omitting the -r flag:

$ ./bish hello.bish > hello.bash

This produces hello.bash which is the compiled version of the hello bish program. We can examine the output, as it's just a normal bash script:

#!/usr/bin/env bash
# Autogenerated script, compiled from the Bish language.
# Bish version 0.1
# Please see https://github.com/tdenniston/bish for more information about Bish.

function bish_main () {
    bish_println "Hello from Bish!";
}

function bish_println () {
    echo -e "$1";
}

bish_main;

Run the bash program as normal:

$ chmod +x hello.bash && ./hello.bash
Hello from Bish!

Using this ahead-of-time compile approach allows you to deploy and execute hello.bash as a normal bash script. This is the main advantage of using bish, as you gain the flexibility and comfort of programming in bish, but keep the portability of bash. Note that because bish is released under the MIT license, it is also perfectly fine to deploy a copy of bish along with your applications if you wish to ship the bish scripts as they are.

Exploring Bish

Let's examine some of the features of the bish language.

Variables

Variables do not require declarations or special syntax to define them. Simply define them directly:

x = 3
str = "This is a string."

You can redefine variables, but all definitions must be of the same type. For example:

x = 3
x = 5
x = x + 1
# This would generate a compile error:
# x = "This is a string."

Also note that in bish, statements do not need to end with a semicolon. However, you can use a semicolon to separate statements if you want several on the same line:

x = 3; y = 4
z = x+y

Functions

Defining and calling functions is unsurprising:

def square(x) {
    return x*x
}

print("square(4) is: ")
println(square(4))
# Output: square(4) is: 16

Bish also supports recursive function calls:

def factorial(n) {
    if (n < 1) {
        return 1
    } else {
        return n * factorial(n-1)
    }
}

print("factorial(5) is: ")
println(factorial(5))
# Output: factorial(5) is: 120

String Interpolation

The above example can be generalized slightly by using string interpolation to print and use the value of the argument to factorial. We could write the above program as:

value = 5
print("factorial($value) is: ")
println(factorial(value))
# Output: factorial(5) is: 120

To use a literal dollar sign, you must prefix it with a backslash (\):

println("Value is: $value, but this is not: \$value")
# Output: Value is: 5, but this is not: $value

Shell Functions

Bish provides a number of builtin functions for performing shell tasks, such as displaying files or changing directories:

 print("Files in current directory: ")
 println(ls())
 cd("/")
 print("Files in / directory: ")
 println(ls())

This will display the list of files in the current directory, change to the root directory, and display the list of files there.

The ls() and cd() functions are implemented as a part of the bish standard library, which can be viewed at src/StdLib.bish. The standard library is still quite small, but it will grow as time goes on.

External Functions

Sometimes, a shell function you need may not be implemented in the standard library. Or, you may wish to execute an external command as a part of your script. For this, bish offers the ability to execute arbitrary "external functions." External commands are inserted verbatim into the resulting compiled bash. The syntax is @(command arg1 arg2...).

For example, suppose you didn't know where StdLib.bish was located, and wished to invoke the find utility from a bish script. There is no find() function as a part of the standard library, but it can be called as an external command:

location = @(find . -name StdLib.bish)
println("Found it at: $location.")

You can use interpolation for external functions as well:

file = "StdLib.bish"
location = @(find . -name $file)
println("Found '$file' at: $location.")

You can also use this technique to access special bash variables, such as the HOME environment variable:

home = @(echo $(HOME))

Note the syntax $(varname) is used to distinguish from variables that should be interpolated into the string.

Conditionals

If statements are written like this:

if (condition) {
   ...
}

You can have multiple else if clauses and one else clause:

if (condition) {
   ...
} else if (condition2) {
   ...
} else {
   ...
}

Note that currently the braces surrounding the bodies are required. For example:

# This is illegal:
# if (condition) body
# This is how it should be written:
if (condition) { body }

Loops

Currently, bish only supports for loops. These can be used to iterate over a range of values.

Iterating over a range of integers:

for (i in 0 .. 10) {
    print("$i ")
}

You can also iterate over list structures. Currently, this relies on the IFS splitting behavior implemented by bash (e.g. splitting on whitespace or newlines). For example, to iterate over a list of files:

files = ls()
for (f in files) {
    println("Got file: $f")
}

Explicit array structures are not yet supported by bish, but are planned.

Pipelines

Importing Scripts