BUSH 0.8 - the PegaSoft Business Shell


Copyright © 2001 PegaSoft Canada and Free Software Foundation
This software is released under the GNU Public License. See COPYING file.
AdaScript language created by Ken O. Burtch and is a trademark of PegaSoft Canada.

Overview


BUSH (Business Shell) is a powerful Linux/UNIX shell for designing secure, reliable shell scripts that can be later compiled as a fast executable programs. It can also be used an an interactive login shell or to generate Java Virtual Machine applications. BUSH is a robust and readable alternative to BASH, CSH, and (to a certain extent) Python and PERL.

BUSH is available for downloads from http://www.vaxxine.com/pegasoft/bush-down.html. Discuss BUSH at PegaSoft's Linux Cafe. Send bug reports to ken@nospam.tiamet.vaxxine.com (remove the "nospam").

[elisa.bush script screenshot]

Contents

Introduction and Advocacy

New To This Version
What's Not Complete
BUSH vs. BASH
BUSH vs. Python
BUSH vs. PERL
BUSH vs. Ada 95
BUSH vs. Java
Does Bush...

Interactive BUSH

A Very Basic Tutorial
Command Line Interaction
Progressive Development Model
Compiling An Executable
Interactive Statements
Profile Scripts
Script Structure
Restricted Shells

Language Reference

AdaScript Language Reference
Fundamental Types
User-defined Types
Basic Assignment and Expressions
The @ and % Operands
Built-in Constants/Variables
Basic Input/Output
Text File I/O
TCP/IP Sockets
Numeric Formatting with Put
Flow of Control
Other Statements/Subprograms
External Commands
Interpreter Directives
Command Line Options
Command Reference
System Package
Numerics Package
Strings Package
ASCII Character Set


NEW TO THIS VERSION

What's Not Complete

BUSH vs. BASH

BASH (the "Bourne Again SHell") is an extended version of the traditional UNIX Bourne shell (sh).   BASH supports most of the Bourne shell's original syntax, but also has features of the  C andKorn shells such as integer and array variables.  The Bourne shell syntax is hard to debug and is not compatible with other computer languages.  BASH can act as an interactive shell or can run scripts.

BUSH is a shell that  has the fundamental syntax of the Bourne shell.  However, BUSH uses a different language for advanced features making BUSH easier to read and debug.  BUSH scripts can be compiled into Java byte code or into an executable program.  BUSH's reliability, scalability and speed make it ideally suited for professional scripting projects.  BUSH can act as an interactive shell and can run scripts.

BUSH vs. Python

Python (named after "Monty Python") is an interpreted language designed for small projects needing rapid development. It is not a shell or a scripting language. It contains many features, like lamda functions, complex numbers and objects, not suitable for shell scripts. The shortcuts provided by Python cause reliability problems in large, complex projects. Because of its unique design, projects developed in Python cannot easily be upgraded to a compiled language.

BUSH is a shell and language designed for high reliability business applications. BUSH scripts are slower to develop than Python programs but are easier to maintain over the lifetime of a project. The shortcuts BUSH provides are carefully chosen so as not to undercut reliability. BUSH features do not fall victim to the "feature bloat" syndrome that weakens the reliability of a language. BUSH scripts can be easily upgraded to a compiled language.

BUSH vs. PERL

PERL ("Practical Extraction and Report Language") is, as its name suggests, a scripting language for designing reports. It combines the features of a shell language, the sed command, and the awk command. It is not a shell. It contains many features beyond report generation, including interprocess communication and database access. Because of its emphasis on pattern matching, typos are easily made and accepted without errors, making PERL scripts difficult to debug and maintain.

BUSH is a Linux shell and shell language designed for high reliability business applications. It's not a report generation language although it can generate reports using formatted output and pattern matching like PERL does. BUSH scripts are slower to develop than PERL scripts but are easier to maintain over the lifetime of a project. Because of its emphasis on a typo-resistent syntax, BUSH is easier to maintain, especially when used large projects.

BUSH vs. Ada 95

Ada 95 (named after the world's first programmer) is a multithreaded, exception handling, object-oriented programming language used for large, high-integrity projects. It is known for abundant features, high performance, strong checking and readability.

The BUSH shell is not an Ada interpreter. BUSH uses AdaScript, a subset of the Ada 95 language with additional features specifically for interactive shell sessions. Because of its Ada 95 background, BUSH scripts are easy to create, maintain, debug and can be compiled into fast, executable programs using an Ada 95 compiler.

If you are looking for full Ada compilers or interpreters, they are available on the Internet.
 

BUSH vs. Java

Java is an interpreted programming language with threads, exception handling and object-oriented features. It can be compiled into "byte code" and applets can be created for web pages.  Java is known for it's awkward syntax, poor performance and debugging problems because of its C heritage.  Java is not a shell.

BUSH scripts can also be compiled into Java byte code (using the JGNAT compiler) and run the same way as Java applets/applications, including web page applets.  BUSH scripts are easy to create, maintain, debug and can be compiled into fast, executable programs using an Ada 95 compiler.

Does BUSH...?

 
Have a GPL licence? YES
Have source code available? YES
Can it be compiled? YES
Can it create Java byte code? YES
Run as an interactive shell? YES
Run as a login shell? YES
Run scripts? YES
Offer easy scalability to a more powerful language? YES
Have test suite included? YES
Run on Linux or UNIX? YES
Use its own interal byte code? YES
Have scoped variables and blocks? YES
Check for undeclared variables? YES
Have strong type checking? YES
Have powerful pattern matching like PERL? YES
Have command line shortcuts like Python? YES
Have built-in string and numeric functions? YES
Support Bourne shell syntax? Often
Have arrays? When a script is upgraded to Ada 95
Have enumerated types? A boolean type? YES
Have floating-point numbers? YES
Have unlimited length strings? YES
Have modules/units/packages? When a script is upgraded to Ada 95
Have objects? When a script is upgraded to Ada 95
Have multithreading? When a script is upgraded to Ada 95
Have TCP/IP sockets? YES
Do formatted output for writing reports? YES
Have user-defined types? YES
Support typeless variables? YES
Job control?  Signal handling? Partially, incomplete.
Have I/O redirection? YES
Have secure command aliases? YES
Automatically declare variables at the command prompt? YES, with a warning.
Have process substitution? No

Interactive BUSH: A Very Basic Tutorial

When BUSH is started without an argument, it presents you with a welcome message and a command prompt, a "=>". From this prompt, you can declare variables, execute AdaScript statements, or run external commands. BUSH is case-sensitive, as is the BASH shell, because UNIX/Linux environment variables are also case-sensitive.

In most shells, you can quit your interactive session using control-d, the end of file character. BUSH will only terminate your session by the command "logout". This prevents an accidental logout caused by the end of file character. (In a shell script, BUSH will quit when the script file is reached or at a return statement.)

The help command presents a short summary of recognized BUSH commands.

[BUSH help command]

For information on a particular topic, use "help" and the topic name.

=> help delay
delay - wait (sleep) for a specific time
delay secs

BUSH comments are started with a double minus sign. Any text following the comment sign to the end of the document are treated as explanatory text. For example,

=> -- BUSH is great

BUSH can be used as a simple calculator. The "?" command will print the result of an arithmetic expression. The basic arithmetic operators are the same as they are in most languages: + (add), - (subtract), * (multiply), and / (divide). Also available are ** (exponentiation), rem (remainder), mod (modulus), and (bitwise and), or (bitwise or), xor (bitwise exclusive or). Parentheses can be used to group subexpressions. Unlike BASH, BUSH numbers are not limited to integers.

=> ?5.5+2.5
8
=> ? (25-4)*6
126
=> ? 12/4
3

Because BUSH is a shell, the "?" is required. If it is missing, BUSH assumes that the expression is the name of a Linux command and will report an error. (This differs from Python because Python is not a shell.)

=> 12/4
12/4: No such file or directory

External operating system commands can be executed by typing them at the prompt. The "ls" command lists files in the current directory.

=> ls
letter2mom.txt bushscript.bush

Operating system commands can be called in two different formats. If the command is followed by a left parenthesis, the command is executed using AdaScript arguments. If there is no parenthesis, it's executed with traditional Bourne shell arguments.

=> echo 2*3
2*3
=> echo (2*3)
6

This behavior is only applicable to external commands. BUSH commands almost always use the AdaScript syntax.

External commands will run in the background when a trailing ampersand (&) is used. The ampersand must be separated from the rest of the command by a space.

=> sleep( 5 ) &
sleep ( 11400) has started

The bracketed number returned is the UNIX/Linux process id.

The "?" command is a short-form for the put_line command. The shortform is provided as a convenience at the command prompt.

=> put_line( 12/4 )
3

Strings are surrounded by double quotes, characters by single quotes.

=> ? "Hello world!"
Hello world!
=> ? 'c'
c

The "put" command is similar to put_line except that it doesn't start a new line after printing and it can display formatted numeric values. The formatting is described by a picture string.

=> put( 123.45, "####9.99" )
 $123.45

Strings are joined together with the concatenation operator, "&".

=> ? "Hello " & "world!"
Hello world!

The last outputted value is represented by "%". Like "?", this is a convenience operand to save typing at the command prompt.

=> ?% & " It's good to be alive!"
Hello world! It's good to be alive!"

Other string functions are available in the strings package. Package functions are accessed by prefixing the function with the package name.

=> ? strings.glob( "*pear*", "apple pear peach" )
true
=> ? strings.glob( "*cherry*", "apple pear peach" )
false
=> ? strings.length( "apple" )
5

ASCII character codes can be expressed with "ASCII", a period, and the code for the character.

=> ? "The tilde is " & "'" & ASCII.Tilde & "'."
The tilda is '~'.

Boolean and relation expressions can be computed using "=", "/=", ">", ">=", "<", "<=", and, or and xor. The built-in constants "true" and "false" represent true and false respectively.

=> ? 5=5
true
=> ? (5>6) or (2<3)
true
=> ? false
false

All variables in BUSH are typed. When a variable has a type, only values of the same type can be assigned to it.  This prevents variables containing fundamentally different values from being combined.

The assignment operator is ":=".

=> x : integer
=> x := 5
=> ? x
5
=> x := "hello"
x := "hello";
            ^ type integer (a universal_numeric) is inherently different from a universal_string

When using BUSH interactively, undeclared variables being assigned values will be declared for you with a warning. The type of variable is guessed at by the value being assigned. (This behaviour can be suppressed with an appropriate pragma.)

=> y := 15
=> (Assuming y is a new universal_numeric variable)
=> a : integer := 5
=> b := a
=> (Assuming b is a new integer variable)

If BUSH's guess is not specific enough, you can explicitly set the type of the variable with the typeset command.

=> typeset y is float

A typeless variable adapts its type to the context of an expression. If there is doubt, the type is assumed to be a string.  This behaviour is very similar to the way traditional Bourne shell variables work.

=> t : universal_typeless
=> t := "hello"
=> t := 5.0
=> ? t + 1
6
=> ? t & " is a string"
5.0 is a string

Constants are created with the word "constant" before the type.

=> company : constant string := "Omnicorp Software"
=> ? company
Omnicorp Software
=> company := "Evil Rivals Ltd."
company := "Evil Rivals Ltd.";
        ^ "company" is a constant, not a variable

Commands can be ended with a semi-colon. Using semi-colons, more than one command can be placed on a line.

=> x := 3
=> (Assuming x is a new universal_numeric variable)
=> y := 4
=> h := numerics.sqrt( x*x + y*y )
=> (Assuming h is a new universal_numeric variable)
=> ? "Hypoteneuse is" ; ? h
Hypoteneuse is
5

This concludes this short tutorial of BUSH's interactive session features. Read on to learn about BUSH built-in packages, scripting and complete coverage of BUSH's interactive features. To see a sample script, try running "eliza.bush", the BUSH version of the famous artificial intelligence program. This script is included in the examples directory and can be run with "bush eliza.bush".

Command Line Interaction

BUSH recognizes the Linux console arrow keys and common key sequences for the "emacs" and "vi" text editors. The sequences are called key bindings and represent cursor movement and editing functions.

The "emacs mode" key bindings are:

BUSH remembers up to 40 command lines for the current session, and you can cycle through them using control-p and control-n.

If you prefer to use vi cursor movement instead, press the escape key to enter vi mode. Press escape again to return to emacs mode. In vi mode, you can use lower-case 'h', 'j', 'k', 'l', '^' and '$'.

Progressive Development Model


BUSH recognizes a fundament problem in business programming. Programmers with limited time and resources are often required to write up quick scripts to solve pressing business problems. Once the script is written, they have no time to return to the script to write a "proper" solution. Scripts, designed for rapid and simple solutions, continue to grow and mutate, becoming rapidly unreadable and easily broken, leaving a complete rewrite in a different language as the only to upgrade the software.

To combat this problem, BUSH scripts can be developed using a progressive development model.

In its native mode, BUSH provides a quick and easy environment to write short Linux programs. Like BASH, variables can be declared anywhere and can be typeless. Although its structure is relaxed compared to a compiled programming language, AdaScript's syntax is both easier to read than a BASH script and provides much more security against typing errors. Common errors in BASH such as missing a quotation or spelling mistakes on variable names are quickly caught.

As an BUSH script grows over time, becoming longer and carries more responsibility, the structure of the script can be improved by using "pragma ada_95". This BUSH directive disables many of the "lazy" AdaScript features such as typeless variables and requires closer conformation to the Ada language. A programmer can "tighten" his code as part of his regular duties, add type checking, and ensure that the script's structure is sound and the source is readable without resorting to a complete rewrite.

Some important scripting features, like running commands in the background with "&", are still allowed.

Finally, if the BUSH script continues to grow and is no longer suitable as a script, the script can be compiled with minimum changes as an Ada program. With other shells and scripting languages, a developer would have no choice but to rewrite the script "from scratch".

Results of an HP-UX benchmark with an early version of BUSH 0.8 shows that BUSH (when compiled) easily outperforms than the fastest shell tested. It ran about 16 times faster than BASH. Except for white space, the output was identical to the original shell script. It did not require a rewrite into another programming language.

Compiling An Executable

To compile a script as an executable program, you'll need to download the GNAT GNU Ada 95 compiler.  (This compiler is in the process of being folded into the GCC 3.x source tree.)  The compiler is available from Ada Core Technologies (www.gnat.com). A Linux-specific version in RPM format is available from the Ada Linux Team (www.gnuada.org/alt.html).

AdaScript is not completely compatible with Ada 95 because Ada 95 was never designed as a language for writing scripts.  Changes must be made to your script, but the changes should be minimal and easily made.

First, compile your script with the pragma ada_95 directive.  This will report most non-Ada 95 features as an error.  pragma ada_95 disallows the following in scripts:

Second, make sure your script is contained in a procedure block (see "script structure" below).

Third, external commands will have to be rewritten as Ada procedures or calls to GNAT.OS_Lib.Spawn or the Linux/UNIX system() call.  You will have to import system() to use it.

Finally, compile the script with GNAT compiler.  To generate Java byte code to run on a Java Virtual Machine, use the JGNAT compiler instead of GNAT.
 

Interactive Statements

Interactive sessions refer to a user typing commands at the BUSH command prompt.  AdaScript has a number of built-in statements to be used in interactive sessions: Interactive sessions behave slightly differently than scripts.  First, in an interactive session, the ending semi-colon is optional.  If it is missing, BUSH will append a semi-colon before executing the command.  If an error occurs, the entire line will be shown, including the extra semi-colon.

Second, compound statements (statements that enclose other statements) can be used provided that the complete statement appears on a single line.  Semi-colons will have to be placed after every command except the final command.

=> for i in 1..5 loop put_line( i ) ; end loop
1
2
3
4
5

A semi-colon isn't required after the first "loop" because the "for" statement is a compound statement ending with "end loop"--the semi-colon is only required after the final keyword in a compound statement.  In this case, the final keyword is also the last keyword on the command line and the semi-colon is optional.

During an assignment, if the variable being assigned isn't declared, it's automatically declared unless pragma ada_95 or pragma restriction( no_auto_declarations ) is used. The type of the variable is the type of the expession being assigned.

=> y := numerics.sqrt( 9 )
=> (Assuming y is a new universal_numeric variable)

External commands can be grouped into pipelines using the vertical bar character (|).  Pipelines are a command line convenience feature and are not intended for use in scripts (this is true in other Linux shells as well).

Interactive sessions can only be terminated with the logout command.
 

Profile Scripts

When BUSH is started when a user first logs in, BUSH attempts to find and run "profile" scripts--a list of commands to set up a user's environment. By creating a profile script, a user can setup variables or run programs every time they log in.

First, BUSH looks for a script called "/etc/bush_profile". This script should contain commands to be run by every user logging in.

Second, BUSH looks for a script called ".bush_profile" in the user's home directory. This script should contain commands specific to a particular user.

The following is a sample ".bush_profile" script:

-- Sample profile script

-- disable automatic declaractions because I don't like them
pragma restriction( no_auto_declaractions );

-- declare company name
company : constant string := "Compu-Global-Mega-Corp Inc.";

-- show the date and time
date;

After the profile scripts are executed, the user sees the "=>" prompt and can being entering commands.
 

Script Structure

One crude way to get BUSH to execute commands is to redirect the commands through standard input. For example, from BASH you could type

$ echo "? \"Hello World\" ; logout" | bush

BUSH treats this as if it was an interactive session with a human user. Command prompts are written to the screen and the keyword logout is necessary to terminate the session. (In an interactive session, the end of file without a logout will cause BUSH to go into an infinite loop as it waits for more commands to be typed by the user.)

A better way to execute a set of commands is to write a script. A script is a text file containing AdaScript commands.  A script has these advantages:

Commands typed into a script are treated slightly differently than those in an interactive session.: The first two differences are necessary because in a script there's no user to interact with the commands. BUSH cannot not make assumptions or take any automatic actions.

In simple scripts, variables, statements, and external commands can be freely mixed. Scripts usually start with "#!/bin/bush" (or whatever BUSH's pathname is), UNIX/Linux's way of determining the program to execute when a script is run.

#!/bin/bush
sales_force : universal_numeric := 55;
yearly_sales : universal_numeric := 2000000;
put( "Average sales is " );
put( yearly_sales / sales_force );
new_line;

BUSH will execute these statements as if they were typed in sequentially at the command prompt.  However, to make your script easier to read, you can organize your script into separate declaration and executable sections using a procedure statement.

#!/bin/bush
procedure demo_script is
  sales_force : universal_numeric := 55;
  yearly_sales : universal_numeric := 2000000;
  subtotal : universal_numeric := yearly_sales / sales_force;
begin
  put( "Average sales is " );
  subtotal : universal_numeric := yearly_sales / sales_force;
  put( subtotal );
  new_line;
end demo_script;

For compliancy with Ada 95 compilers, your program must be enclosed in a procedure statement if your program uses pragma ada_95.

A program can be separated in declarations and an executable part with the declare statement.

#!/bin/bush
declare
  sales_force : universal_numeric := 55;
  yearly_sales : universal_numeric := 2000000;
  subtotal : universal_numeric := yearly_sales / sales_force;
begin
  put( "Average sales is " );
  put( yearly_sales / sales_force );
  new_line;
end;

Declare blocks can be nested inside of one another, usually to break up a very long script into logical parts.

#!/bin/bush
declare
  sales_force : universal_numeric := 55;
  yearly_sales : universal_numeric := 2000000;
begin
  put( "Average sales is " );
  declare
    subtotal : universal_numeric := yearly_sales / sales_force;
  begin
    put( subtotal );
  end;
  new_line;
end;

When blocks are nested, you can declare new variables which only have meaning in that particular block, reducing the chance of using the wrong variable name in a large script.

A script is run by using the script name as an argument to BUSH. BUSH normally performs a syntax check before running the script.

$ bush demo.script

There are several command line options which can change how the script is run.  For example, the BUSH syntax check will sometimes report errors for a legitimate script--for example, by declaring variables inside of an  "if" statement.  Normally, this is a sign of a poorly organized script, but the syntax check can be disabled .   To run the script without checking it, use the --no-check command line option.

The execution of a script can be traced with the BUSH trace command.  Suppose you want to trace the execution of the following script:

#!/bin/bush

trace true;

procedure main is
  type paint is ( black, white, blue );
begin
  for p in black..blue loop
      put_line( p );
  end loop;
end main;

Once tracing is turned on by trace true, each line will be shown before it's executed, with the line number in square brackets to the right of the line.  Some statements will also show additional information.  For example, an assignment statement will show the value being assigned.  The trace information is written to standard error, so the output from the command may appear in the wrong order because some versions of UNIX buffer their output.

$ bush trace_demo.bush
Trace is on
=> "" [ 4]
=> "procedure main is" [ 5]
=> "  type paint is ( black, white, blue );" [ 6]
=> "begin" [ 7]
=> "  for p in black..blue loop" [ 8]
=> "      put_line( p );" [ 9]
=> (p := ' 0')
black
=> "  end loop;" [ 10]
=> "  end loop;"
=> "      put_line( p );" [ 9]
=> (p := ' 1')
white
=> "  end loop;" [ 10]
=> "  end loop;"
=> "      put_line( p );" [ 9]
=> (p := ' 2')
blue
=> "  end loop;" [ 10]
=> "  end loop;"
=> "      put_line( p );" [ 9]
=> "  end loop;" [ 10]
=> "end main;" [ 11]
=> "" [ 12]
=> "[End of File]" [ 13]
=> (Script exit status is 0)
 

Restricted Shells

A restricted shell is a BASH session started with the --restricted (or -r) command line option.  Restricted shells limit the variables, directories and commands available to a script, providing a secure environment to run potentially problematic scripts.

BUSH restricted shells don't allow:

A BUSH restricted shell will allow: Allowing data to be appended differs from other restricted shells.

=> cd ..
cd ..;
   ^^ cd not allowed in a restricted shell

BUSH's restricted shell mode differs from Bourne restricted shells in that it's not intended to isolate users but scripts.  User sessions, however, can be started in restricted mode, but the restrictions do not apply to the profile files, allowing the administrator to setup the shell environment before the restrictions are enforced.
 

AdaScript Language Reference

The computer language that the BUSH shell understands is called AdaScript. Unlike JavaScript, which has no relation to Java, AdaScript is a small subset of the Ada programming language, with additional features related to shell commands.

AdaScript is intended to be "upward compatible" with Ada 95.  AdaScript scripts should run with little difficulty under Ada, but Ada programs may require large changes run under BUSH.  For example,AdaScript is a case-sensitive language (because shell variables and Linux commands are case-sensitive), but Ada is case-insensitive and programs may need to have the capitalization of keywords changed to fit the case expected by AdaScript.

AdaScript has a number of reserved words. These are words that have a special significance to the language and they cannot be used as variable names. All Ada 95 keywords are also reserved, even those that are not used by AdaScript, to make it easier to compile scripts with an Ada compiler. The reserved words must always be in lower case.
abort mod
abs new
abstract not
accept null
access of
aliased or
all others
and out
array package
at pragma
begin private
body procedure
case protected
clear raise
constant range
declare record
delay rem
delta renames
digits requeue
do return
else reverse
elsif select
end separate
entry subtype
env tagged
exception task
exit terminate
for then
function trace
generic type
goto typeset
help unset
if until
in use
is when
jobs while
limited with
logout xor
loop

Comments are denoted by either a "#" or a "--" and all text after these symbols is ignored until the end of line.

Fundamental Types

All AdaScript variables must be declared because they have a specific type (even universal typeless variables, athough that may seem counter-intuitive).  When variables are automatically declared at the command prompt, BUSH will attempt to choose an appropriate type.  In a script, the variables must be declared to indicate the type of data they contain.

Universal Types

There are three "universal types":

* universal_string
* universal_numeric
* universal_typeless

These universal types can be used to disable AdaScript's strong typing in parameters and expressions. Universal types automatically match any type derived from the same universal type. For example, a string literal like "hello" is a universal_string and can be used with any string type. And the numeric literal 45.5 is a universal_numeric can be used with float, long_float, or any other numeric type.

Variables can also be declared universal_typeless, which means they can be used with both string variables and numeric variables, automatically converting into the proper type when required. (Two typeless variables in the same subexpression are treated as two strings.)

Using these three built-in types will give you a minimum amount of type checking, suitable for short scripts or quick command line calculations.  Universal types should not be used in large scripts because they make debugging difficult.

Pre-defined, Non-Universal Types

For more extensive scripts, AdaScript extends the universal string and numeric types into all the predefined Ada language types, plus some AdaScript-specific types:


By default, all numeric variables are initialized without any value. Any attempt to use uninitialized numeric variables in an expression will cause an error or exception.

=> i : integer -- no value specified
=> i := i + 1
i := i + 1;
          ^ exception raised

All types are logical types: that is, all numeric types are stored in the same format.  A natural, integer or float are all stored as a universal numeric value.  There are only minimal checks to make sure that variables of these types conform to their descriptions. However, types provide an indication to the reader of how a variable is intended to be used, and AdaScript will not allow these types to be combined without an appropriate type cast.

=> i : integer := 5
=> f : float := 0
=> f := i
f := i;
      ^ type 'float' is not compatible with type 'integer'
=> f := float(i) -- typecast
=> ? f
5

You cannot typecast a numeric type into a string type directly.  There are functions in the numerics and strings packages to do these conversions.

Since all values are stored as a universal type, this can cause some unusual side-effects. A character variable can contain more than one character if you really want it to by assigning a string literal. Characters are stored as a universal_string and a string literal is a universal_string type.  AdaScript will allow the assignment.  However, the type checking will prevent a character variable from being assigned to a string variable.

c : character;
c := "hello"; -- confusing, perhaps stupid, but legal
s : string := c; -- ILLEGAL

AdaScript strings are an unbounded string type--that is, as an Ada.Strings.Unbounded.Unbounded_String variable.  They have an unlimited length and are not implemented as any sort of array. Unlike Ada "fixed" strings, AdaScript strings can have any length and are not subject to array restrictions.

Constants

Constants can be declared with the word "constant" for any type. The use of "constant" doesn't not affect the type of the variable--it simply prevents new values from being assigned by making the variable "read-only".

program_name : constant string := "Nightly FTP Transfer";

Limited Types

file_type and socket_type variables are known as limited type variables. Limited types cannot be assigned a new value with an assignment statement

=> f : file_type
=> g : file_type
=> f := g
f := g;
      ^ limited variables cannot be assigned a value

BUSH manages the contents of these variables and scripts are not allowed to change the contents.

Command Types

External operating system commands can be declared using command variables. When a command is declared, BUSH will ensure that the command exists and is runnable.

Command types are similar to limited types and have several restrictions.


=> l : constant command := "/bin/ls"
=> m : constant command := "/bin/lt"
m : constant command := "/bin/lt";
                                 ^ "/bin/lt" is not an executable command
=> n : constant command := l;
n : constant command := l;
                         ^ type universal_string is inherently different from a command
=> ? l & " is the path"
? l & " is the path";
                    ^ type command is inherently different from a universal_string
=> ? l
/bin/ls

User-defined Types

You can extend the fundamental types to create your own types.

The subtype statement will create a type that is compatible with the original, as if it was a renaming of the original type.

=> subtype int is integer;
=> i1 : integer := 1
=> i2 : int := 2
=> ? i1 + i2
3

In this case, "int" is equivalent to "integer" and variables of both types can be mixed freely without type casting.  Unlike Ada 95, there is no way to place restrictions on a subtype--they are simple renamings in AdaScript.

To make incompatible types, you need to create a new type with the type statement.

=> type employee_number is new integer;
=> type customer_number is new integer;
=> en : employee_number
=> cn : customer_number
=> en := cn
en := cn;
        ^ type 'employee_number' is not compatible with type 'customer_number'

In this case, "employee_number" variables cannot be mixed with "customer_number" (or other) variables without a typecast. Use new types to make sure variables that are logically different don't accidently mix.

AdaScript also has an enumerated type. Enumerated types are naturally incompatible with one another, and the items in the enumerated list cannot be overloaded with other enumerated types.

=> type fruit is (apple, blueberry, cherry);
=> f : fruit
=> f := apple
=> f := 5
f := 5;
      ^ type fruit (an enumerated type) is inherently different from a universal_numeric
 

There are two built-in enumerated types.  The boolean type is a predefined enumerated type with values "false" and "true".  The file_mode type has the values "in_file", "out_file" and "append_file".

AdaScript has no aggreagate types. There is no object, array or record/structure type. If you need these, your project is probably too complicated for a simple script and should be upgraded to Ada 95.

Basic Assignment and Expressions

AdaScript expressions can contain the following operators: The bitwise operators are identical to the boolean operators: BUSH choses which to use on the context of the expression.

Assignment (:=) is a statement, not an operator, and cannot be used in expressions.

x := 5 * ( 7 + 2 );
s := "hello " & "there!";

The @ and % Operands

AdaScript provides a self-referential operand. "@", pronounced "itself", returns the value of the variable on the left side of an assignment statement. Use @ to save yourself unnecessary typing.

total := @ + 1; -- total := total + 1;

AdaScript provides a last output operand. "%", pronounced "last output", returns the last put_line value.

=> put_line( 5*2 );
10
=> put_line( %+5 );
15

The type of the last output is remembered.

=> put_line( 5 )
5
=> put_line( % & " is a good number" )
put_line( % & " is a good number" );
                                  ^ type universal_numeric is inherently different from a universal_string
 

Built-in Constants/Variables


AdaScript contains many built-in constants, variables and functions. Some of these are grouped into packages.

These packages are referenced by name and are described in detail later in this guide.

To use a function or constant inside a package, prefix the function with the name of the package.  For example, to use the "length" function in the "strings" package, refer to it as "strings.length".

=> ? strings.length( "PegaSoft" )
8

The built-in Text_IO package, which contains put_line and get_line, is treated as an inherent part of the language and doesn't use a prefx of "Text_IO".

Parameters to the script are available using the built-in command_line package. However, for  compatibility, the Bourne shell syntax can also be used:

If there are more than 9 arguments, they can be accessed with the command_line package.

The Bourne shell form are intended as command line shortcuts and the command_line package should normally be used in a well-structured script.

rm ("/tmp/temp." & $$)
if $? /= 0 then
   put_line( standard_error, "unable to delete temp file" );
end if;

The status code of the last command executed is returned from a script when it finishes executing.  A specific status code can be returned using the command_line package.
 

Basic Input/Output


Directory Walking

There are two built-in BASH-style commands for managing your current directory:

The cd command ignores the value of a CDPATH variable.

Console I/O


The question mark command "?" is a short-form for put_line to standard output.

get is provided for compatibility for Ada 95. A more useful function for reading a keypress is

The file_type constants standard_input, standard_output and standard_error can be used to redirect I/O to a particular file.

put and put_line can take any type of parameter. Also, they can also explicitly direct their output to standard output or standard error by using standard_output or standard_error as the first parameter.

=> put_line( standard_error, "an error occurred!" );

get_line reads a line of text from the keyboard until the return key is pressed.

Ada's get_immediate is not implemented.
 

Text File I/O


BASH opens files on the basis of file numbers or "file descriptors". In AdaScript, files are declared using a file_type variable.

AdaScript's I/O system implements a subsection of the Ada Text_IO library.

=> f : file_type
=> create( f, out_file, "data.out" )
=> put_line( f, "Added to file" )
=> ? name( f )
data.out
=> ? mode( f )
out_file
=> ? line( f )
1

The file is closed with close.

=> close( f )

standard_input, standard_output and standard_error can be redirected with the appropriate command to an open file.

=> set_output( f )

From now on, all output going to standard output is written to the file_type variable f. The current_output function refers to the file being written to (in this case, f). Output redirected to f is not counted by the line function.
 

TCP/IP Sockets

Network sockets can be declared using socket_type variables. Most of the file I/O functions will work with sockets as well as files.

The main difference between a socket and a file is that sockets are always "in" and "out". When opening a socket, do not specify a mode.

The name of the socket is a string containing the name of the server computer, followed by a colon and a port number. The port number is 80 (a web server) if no port is specified. Certain ports, such as those above 32767, are prohibited as a security precaution.

#!/bin/bush
-- download a web page

f : socket_type;
c : character;

open( f, "www.somewebserver.com" );

put( f, "GET /index.html HTTP/1.0" & ASCII.CR & ASCII.LF );
put( f, "User-Agent: BUSHTest/1.0 (BUSHTest)" & ASCII.CR & ASCII.LF );
put( f, ASCII.CR & ASCII.LF );

loop
  get( f, c );
  put( c );
  exit when end_of_file( f );
end loop;

put_line( "End of web page" );
close( f );
 

Numeric Formatting with Put

BUSH offers numeric formatting for numbers up to 18 digits and 4 decimal places. Formatted numbers are written with the put command, not put_line. Following the number, provide a formatting picture string describing how the number should be formatted.

The picture string may include:

Here are a few examples.

=> put( 12.34, "***9.99" ) ; new_line
**12.34
=> put( 12.34, "###9.99" ) ; new_line
 $12.34
=> put( 12.34, "+###9.99" ) ; new_line
+ $12.34
=> put( 12.34, "-###9.99" ) ; new_line
  $12.34
=> put( 12.34, "<###9.99>" ) ; new_line
  $12.34
=> put( -12.34, "<###9.99>" ) ; new_line
( $12.34)
 

Flow of Control


if statements are used for conditional branching.

if x > y then
   put_line( "x is greater than y" );
elsif x = y then
   put_line( x is equal to y" );
else
   put_line( x is less than y" );
end if;

The case statement can test a variable for several different values.

type country is ( australia, u_k, brazil );
...
case c is
when austraila =>
   put_line( "Australia" );
when u_k =>
   put_line( "United Kingdom" );
when brazil =>
   put_line( "Brazil" );
when others =>
   put_line( "Unexpected country" );
end case;

The "when" cases must not be variables. The "when others" case is always required.

The while loop is a pre-test loop.  The commands in the loop are repeat while the condition is true and the condition is tested each time the first line of the loop is executed.

while x > y loop
  x := @ + 1;
end loop;

The for loop increments its index variable by 1 until it iterates through the specified range. The index variable is automatically declared for you and only exists for the scope of the the loop.  The range can either be numeric or enumerated.

for i in 1..10 loop
  put( "i is " );
  put_line( i );
end loop;

To loop in reverse in a for loop, use "in reverse" instead of "in".

Any loop can be exited by using either an exit statement or an "exit when" shorthand.

if x > 100 then
  exit;
end if;

exit when x > 100;

A "loop" loop is a general purpose loop. It can only be exited with "exit".

loop
  reply := get_line;
  exit when reply = "exit";
end loop;
 

Other Statements/Subprograms

The null statement, "null", doesn't do anything.
The delay statement, "delay", will delay for a specific number of seconds.
The return statement will unconditionally terminate the script and return an optional exit status.
The system subprogram will give a string to your default shell to execute as a shell command.

null; -- do nothing
delay 2.5; -- wait 2 1/2 seconds
system( "date" ); -- execute the Linux date command
return; -- quit the program
 

External Commands

The purpose of an AdaScript script is, ultimately, to execute shell commands. Any command not understood by AdaScript as a command is assumed to be a shell command. For example,

=> echo

will run the Linux echo command and print a blank line to the screen, the same as new_line.

Commands are accepted in one of two different formats. If the command is followed by a "(", the parameters are expected to be in AdaScript format with each parameter separated by a comma (","). These parameters may be any legitimate AdaScript expression and no shell quote removal or file globbing is performed.

=> ls( "bush.adb" )
bush.adb

If the command is not followed by a "(", the parameters are expected to be in Bourne shell format. This is provided for convenience in interactive sessions.  The parameters are shell "words" separated by spaces. Each word has file globbing performed. The words may be enclosed in double quotes to allow embedded spaces, or single quotes to inhibit file globbing. Special characters may also be escaped with backslashes.

=> ls b*.adb
builtins.adb bush.adb

Bush will not perform any BASH-style "$" substitutions.

Command names containing space characters can be quoted with double quotes.

=> "inventory report"

When pragma ada_95 is used, shell commands must only use the AdaScript parameter format, to make conversion to Ada 95 easier.

External commands can be run in the background using an ampersand ("&") at the end of the command. With AdaScript parameters, place the ampersand after the closing parenthesis. The jobs command displays the status of any outstanding background commands.

The built-in shell commands are listed in a section above. when a Linux command conflicts with a built-in command, the Linux command can be executed using the command command.

Interpreter Directives

Pragmas, or interpreter directives, provide additional instructions to AdaScript.

Command Line Options

There are four command options for BUSH.

Command Reference

Here is a list of the AdaScript built-in commands and statements.

begin

see declare

case

Syntax: case var is when constant|literal => ... when others => ... end case
Description: Test a variable for multiple values. "when others" case is required.

cd

Syntax: cd - | dirname
Description: change directory. "-" is the previous directory.  A leading '~' is your home directory.

clear

Syntax: clear
Description: reset tty device and clear the screen

close

Syntax: close( file )
Description: close an open file

command

Syntax: command cmd
Description: run a Linux command (instead of a built-in command).

create

Syntax: create( file [, out_file | append_file] [, path ] )
Description: create - create a new file or overwrite an existing file. The default type is out_file. The default path a temporary file name.

declare

Syntax: [declare declarations] begin ... end
Description: begin a new block

delay

Syntax: delay secs
Description: wait (sleep) for a specific time

delete

Syntax: delete( file )
Description: close and delete a file

end_of_file

Syntax: end_of_file( file )
Description: true if an in_file file has no more data

end_of_line

Syntax: end_of_line( file )
Description: true if an in_file file has reached the end of a line with get

else

see if

elsif

see if

env [ident or keyword]

Syntax: env
Description: show all identifiers, or identify an identifier or keyword.

exit

Syntax: exit | exit when condition
Description: break out of a loop

for

Syntax: for var in [reverse] first..last loop ... end loop
Description: for loop - loop through between first and last assigning value to var. The for starts a new block and the for variable is automatically declared based on the type of first.

get

Syntax: get( [file,] var )
Description: read a character from a line of text.

get_line

Syntax: var := get_line [ (file) ]
Description: read a line of text

if

Syntax: if expression then ... [elsif expr then ...] [ else ...]
Description: conditional execution";

inkey

Syntax: c := inkey
Description: read a character from standard input without echoing

is_open

Syntax: is_open( file )
Description: true if file is open

jobs

Syntax: jobs
Description: list status of current background jobs

line

Syntax: line( file )
Description: the number of read/written lines

logout

Syntax: logout
Description: terminate an interactive session

loop

syntax: loop ... end loop
Description: General loop. exit with an exit statement.

mode

Syntax: mode( file )
Description: the file mode (in_file, out_file, append_file)

name

Syntax: name( file )
Description: name of an open file

new_line

Syntax: new_line [(file)]
Description: start a new line

null

Syntax: null
Description: do nothing

open

Syntax: open( file, in_file | out_file | append_file, path )
Description: open an existing file or open a socket

pragma

interpreter directive

put

Syntax: put ( [file], expression [, picture] )
Description: write to output, no new line. If picture is included, format the number according to the picture.

put_line

Syntax: put_line ( [file], expression )
Display: write to output and start new line

pwd

Syntax: pwd
Description: present working directory

reset

Syntax: reset( file [,mode]
Description: reopen a file

return

Syntax: return [status code]
Description: exit script and return status code

set_input

Syntax: set_input( file ), set_output( file ), set_error( file )
Description: input/output redirection

skip_line

Syntax: skip_line [(file)]
Description: discard the next line of input

subtype

Syntax: subtype newtype is oldtype
Description: create an alias for a type

system

Syntax: system( commandstring )
Description: run a BASH shell command

trace

Syntax: trace [true | false]
Description: show verbose debugging information

typeset

Syntax: typeset var [is type]
Description: change the type of a variable, declaring it if necessary. Cannot be used in scripts or with pragma ada_95.

unset

Syntax: unset ident
Description: delete an identifier. Cannot be used in scripts or with pragma ada_95.

wait

Syntax: wait
Description: wait for all background commands to finish.

while

Syntax: while condition loop ... end loop
Description: while loop - repeat the loop until the condition is false

?

Syntax: ? expression
Description: put_line to standard output. Cannot be used with pragma ada_95.

System Package

The BUSH System package provides information about the computer a script is running on. BUSH is case-sensitve.  "system.system_name" is not the same as "System.System_Name".

=> ? System.Memory_Size
 4294967296
 

Numerics Package

The BUSH built-in package numerics provides mathematical functions for floating point numbers. Most of the functions accept any kind of number. The bit shifting operations which require a natural for the number of bits to shift, and leading_part requires an integer as the second parameter.

Predefined numeric constants:


Absolute value (abs) is not technically a part of the numerics package but is documented here.

Strings Package

The BUSH strings package provides string operations. Character positions are numbered from 1. Note: There's a known bug in Gnat 3.12 and 3.13 which causes strings.match to fail.

Match characters include:

Glob characters include:

command_line Package

The command_line package contains the following functions. There is also one procedure:

ASCII Character Set

The ASCII character set is represented by the ASCII enumerated type.

ASCII.NUL - control-@
ASCII.SOH - control-A
ASCII.STX - control-B
ASCII.ETX - control-C
ASCII.EOT - control-D
ASCII.ENQ - control-E
ASCII.ACK - control-F
ASCII.BEL - control-G
ASCII.BS - control-H
ASCII.HT - control-I
ASCII.LF - control-J
ASCII.VT - control-K
ASCII.FF - control-L
ASCII.CR - control-M
ASCII.SO - control-N
ASCII.SI - control-O
ASCII.DLE - control-P
ASCII.DC1 - control-Q
ASCII.DC2 - control-R
ASCII.DC3 - control-S
ASCII.DC4 - control-T
ASCII.NAK - control-U
ASCII.SYN - control-V
ASCII.ETB - control-W
ASCII.CAN - control-X
ASCII.EM - control-Y
ASCII.SUB - control-Z
ASCII.ESC - Escape key
ASCII.FS
ASCII.GS
ASCII.RS
ASCII.US
ASCII.DEL - ASCII 127, delete key
ASCII.Exclam - "!"
ASCII.Quotation - """"
ASCII.Sharp - "#"
ASCII.Dollar - "$"
ASCII.Percent - "%"
ASCII.Ampersand - "&"
ASCII.Colon - ":"
ASCII.Semicolon - ";"
ASCII.Query - "?"
ASCII.At_Sign - "@"
ASCII.L_Bracket - "["
ASCII.Back_Slash - "\"
ASCII.R_Bracket - "]"
ASCII.Circumflex - "^"
ASCII.Underline - "_"
ASCII.Grave - "`"
ASCII.L_Brace - "{"
ASCII.Bar - "|"
ASCII.R_Brace - "}"
ASCII.Tilde - "~"
 

End of Document