Bush Guide: Part 2 - Tutorials

<--Part 1  Part 3 -->
This part of the Bush Guide introduces the basic features of the BUSH shell.  There is a tutorial for interactive sessions, a tutorial for script writing as well as descriptions of restricted shells, profile scripts are other general features.  A complete list of features is contained in Part 3, and details of the many BUSH built-in packages are contained in Part 4.

Interactive BUSH: A Very Basic Tutorial

Thanks for taking the time to look at the Business Shell, or BUSH. This is a "quick start" tutorial on using BUSH as a shell. The intended audience is professional programmers.

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 are many other shells, 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 "return" (or "logout" if you are in your login shell). 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]
Screenshot of an interactive BUSH session

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 line 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 most shells, BUSH numbers are not limited to integers.

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

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. On UNIX/Linux systems, 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
=> echo (2*3)

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

Traditional Bourne shell $ expansions do not work: use AdaScript arguments instead.

=> echo $HOME
=> echo (HOME)

External commands will run in the background when a trailing ampersand (&) is used.

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

The bracketed number returned is the operating system 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 )

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

=> ? "Hello world!"
Hello world!
=> ? '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" )

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

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

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

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

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
=> ? (5>6) or (2<3)
=> ? false

BUSH has several built-in packages.  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" )
=> ? strings.glob( "*cherry*", "apple pear peach" )
=> ? strings.length( "apple" )

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

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

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
=> 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

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

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

[BUSH help command]
Screenshot of the example eliza.bush script

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.

Interactive BUSH Part 2

Like most shells, BUSH supports input/output redirection and command pipelines.

=> ls > list.txt;         -- save the results of the ls command to a file called list.txt
=> grep bush < list.txt;  -- search list.txt for files containing "bush"

The > operator will write the results of a command to a file.  If the file exists, it will be overwritten.  If the file doesn't exist, it will be created.  In this case, the results of the ls command are written to a file named "list.txt" in the current directory.  The >> operator will append the results to the end of an existing file instead of overwriting it.

Likewise the < operator will read a file and use it as input to another command just as if someone had typed the file by hand.  In this case, grep will search list.txt for the word "bush".

Errors from a command are redirected using the 2> or 2>> operators.  The 2> creates a new file or overwrites an old file and 2>> will append the errors to an existing file.

A pipeline is a combination of two or more commands connected by a pipe symbol (|).  The results of the first command become the input to the second command.  Using a pipeline, you don't need to use an intermediate file to store the results.

=> ls | grep bush; -- same as example above but using pipes

The >, >>, <, 2>, 2>> and | features make it easy to work with programs at the command prompt.  BUSH provides more powerful redirection features for scripting in the Text_IO package described in part 4.

In most shells, variables are only string types.  In UNIX/Linux operating systems, variables are shared between shells and other programs through collections of variables called an environment.  The environment can only hold string variables.  To get around this problem, BUSH variables are stored independently of the operating system environment.  Environment variables must be "imported" into BUSH or "exported" so that the programs you run can see them.

On startup, BUSH automatically imports all the environment variables it can find.  You can see that they are imported using the env command.

=> env LOGNAME
LOGNAME = "ken" ( imported identifier of type string )

You can assign a new value to LOGNAME but this changes the variable for the BUSH session.  It doesn't change the LOGNAME variable from the point of view of the program that started BUSH.

=> LOGNAME := "monkey"
=> env LOGNAME
LOGNAME = "monkey" ( imported identifier of type string)

To restore the original value, LOGNAME must be re-imported from the environment variables using pragma import.

=> pragma import( shell, LOGNAME )
=> env LOGNAME
LOGNAME = "ken" ( imported identifier of type string )

LOGNAME is restored to its original value.

To provide a variable to a program you run, it must be exported out of BUSH using pragma export.  BUSH will take the variable and convert it to a form that can be placed the environment of the new program.  CVS, for example, requires a variable called CVSROOT.

=> CVSROOT : string := "/home/ken/cvsroot"; -- location of CVS root directory
=> pragma export( shell, CVSROOT )
=> env CVSROOT
CVSROOT = "/home/ken/cvsroot" ( exported identifier of type string )

If you don't export CVSROOT, the CVS command will not be able to see it.  It will be "local" to the BUSH session.

Since environments can only hold string variables, you cannot export variables other than string variables.

=> sum : integer
=> pragma export( shell, sum )
pragma export( shell, sum );
                           ^ type interger (an universal_numeric) is inherently different from an universal_string

BUSH has three kinds of "universal" types.  A universal_string can hold any kind of string or character.  A universal_numeric can hold any kind of number.  Finally, a universal_typeless variable can hold any value.  The universal types are provides as a command line conveninece for quick calculations.

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

If an expression using several univeral_typeless variables and BUSH is unable to determine whether the variables are strings or numbers, BUSH will assume that the variables contain strings.

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

The results of a command can be assigned to a variable using backquotes, the same as many other shells.  The results are considered a string.  Between a pair of backquotes (`), you can type any BUSH commands.  BUSH treats these commands like a script: always end the commands with a semi-colon.

=> date : string := `date;`
=> ? "The current time is " & date
The current time is Mon Aug 12 13:54:10 EDT 2002

To assign a value to a numeric value, you will need to convert the string using numerics.value.

=> -- get number of files, strip out spaces
=> s := `ls -1 | wc -l | sed 's/ //g';`
=> ? s
=> -- numerics.value requires one leading space
=> num_files : natural := numerics.value( ' ' & s );
=> ? num_files

Many people like to customize their shell prompt.  To change your shell prompt, use pragma prompt_script and include a backquote script to create your prompt.  Include an empty backqoute script to restore the original "=>" prompt.

=> pragma prompt_script( `pwd; put( HOSTNAME & ':' &  LOGNAME ); put( " => " );` )
linuxbox.dhs.org:ken =>

Command line aliases can be created using command type variables.  When you create a command variable, BUSH will verify the the command exists and can be executed by you.

=> list_files : constant command := "/bin/ls"
=> list_files
letter2mom.txt bushscript.bush
=> imon : constant command := "/sbin/imon"; -- I can't run this
                                          ^ "/sbin/imon" is not an executable command

Command aliases cannot have option switches.

This ends the second part of the command line tutorial.

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.

Typing a control-c (sending a SIGINT signal) at the command prompt acts the same as a control-x.  Pressing control-c while a script or command is running will cause it to stop.

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 '$'. Filename completion is accomplished with two escape key presses in a row.

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

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.

IDEs That Support BUSH

If you prefer to work with an integrated development environment, PegaSoft's Tiny IDE for Anything/Ada (TIA) supports BUSH.

Screenshot of the TIA IDE

The File/Check command will run BUSH with the --check (syntax check) option.

Tia is designed for building large projects. In the project parameters window, chose "Make" and create a Makefile so that TIA can build and execute your Bush projects.  As long as your scripts contain a "#!" line and have executable permissions, they should be runnable from TIA after a project is built.

Instructions for using TIA are contained in the Big Online Book of Linux Ada Programming.

A First Script

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

By convention, BUSH shell scripts have file names ending with ".bush". The following is a script called "hello.bush", a very short script to write a message on the screen.

-- hello.bush
-- this is my first BUSH script
put_line( "Hello! BUSH is wonderful." );
command_line.set_exit_status( 0 );

Lines begining with a double minus (--) are comments. They are notes to the reader and do not affect the execution of a script. Everything between the double minus and the end of the line is ignored by BUSH.

put_line and set_exit_status are built-in commands. put_line, part of the Text_IO package, displays a message on the screen. The set_exit_status command( 0 ) command informs the program which ran your shell script that the script ran successfully.

You can run your shell script

=> bush hello.bush

If there are no mistakes, BUSH will respond with the message.

Hello! BUSH is wonderful.

Creating a Organized Script

Suppose you want to write a script that does something more useful, such as email someone with a "bob" login when there are files waiting in a certain directory. In BUSH, the following commands can do the job:

num_files : integer := numerics.value( `ls -1 incomingdir;` );
if num_files > 0 then
   put_line( "There are files waiting in the incoming directory" ) | mail -s "waiting files" bob;
end if;

But consider the following questions:

BUSH scripts can be more than a list of commands. BUSH has a number of features that allow scripts to be "well-structured". Well-structured scripts are easier to read and debug. Here's an example of a well-structured BUSH script:

-- checkfiles.bush - check the files in the incoming directory and email someone when the files arrive.
-- Ken O. Burtch
-- CVS: $Header$

procedure checkfiles is

-- declarations

  num_files : integer;
  ls : constant command := "/bin/ls"; -- the ls command - man 1 ls
  mail : constant command := "/usr/bin/mail"; -- the mail command - man 1 ls

-- commands begin here

  num_files : integer := numerics.value( `ls -1 incomingdir` );
  if num_files > 0 then
    put_line( "There are files waiting in the incoming directory" ) | mail -s "waiting files" bob;
  end if;

-- cleanup

  command_line.set_exit_status( 0 );
end checkfiles;

The first part of this script is called the header. The header defines what kind of script this is, who wrote it, what version it is, and what restrictions or BUSH pragmas will apply to this script.

The very first line of a script is the header line. This line begins with a "#!" at the top of the script, flush with the left margin. THis character combination identifies the kind of script. Linux and UNIX users this information to start the right program to run the script. For BUSH scripts, this line contains the absolute pathname to where the BUSH shell resides. On many systems, this will be /usr/local/bin/bush.

If you don't know the location of the BUSH shell, use the "whereis" command to find it:

=> whereis bush
bush: /usr/local/bin/bush

The header line is followed by comments describing the purpose of the script and who wrote it. This is important in case your script needs to be debugged in an emergency situation. The "CVS" line is used by the cvs program, if it is installed and you intend to use it.

The main script is wrapped in a procedure statement. The procedure statement divides the script into two sections: declaring variables and commands to run. Putting all the declarations for a script in one place makes it easy for someone to refer to them while reading the script.

The commands are declared as command variables. A command variable is a special BUSH variable type: when a command variable is declared, BUSH verifies that the command exists and that it can be run. If the command cannot be run, BUSH stops the script before any command are executed.

Without command variables, BUSH searches several directories for the command you want to run. If it can't find it, the script will stop with an error after executing part of the script. In this circumstance, it is diffult to determine where the script left off and what needs to be done to continue. Instead, command variables and other "sanity checks" should be put at the top of the script, or in the variable declarations, to ensure that when the script fails because of these kind of errors that the system will be left in a consistent state.

After the main portion of the script runs (the part that does the actual work), the script should clean up after itself. Any open files should be closed or deleted and the script should return a status code to the person or program running the script. In this case, there are no files to clean up. All that is necessary is the set_exit_status command( 0 ) which indicates that the script ran successfully.

Stopping a Script Early

The logout command, which ends an interactive login session, cannot be used to stop a script. (After all, a script is not a login session.) Instead, use the return command to unconditionally stop a script and return control to the calling program or person. Set the exit status to zero to indicate there is no error.

Scripts automatically stop when it reaches its end as if there was an implict "return" typed there.

Scripts can be paused using the bult-in delay command. delay will suspend the script for a specific number of seconds after which it will wake up and resume the next statement after the delay command. The number of seconds can include a fractional part.

delay 5.5; -- wait for 5 1/2 seconds

delay is useful for putting pauses in a script, allowing the user to read what's been displayed. delay isn't suitable for synchronizing events, however, because how long a particular program runs on the computer often depends on the system load, the number of users, hardware upgrades and other factors outside of the script's control.

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

Bush vs. .Net and J2EE

[Ada-Bush Enterprise Model]

Figure: Ada-Bush Multitier Enterprise Application Model

Using an Ada-Bush business strategy creates a multitiered application model comparable to .NET and J2EE. Reusable components, client interaction using HTML/XML/HTTP, scalability and high availability over a network are all possible with an Ada-Bush strategy.

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.

Declare Blocks

A procedure statement can separate the declarations from the executable statements in a script.  Declaration can be made anywhere with with the declare block.

  sales_force : universal_numeric := 55;
  yearly_sales : universal_numeric := 2000000;
  subtotal : universal_numeric := yearly_sales / sales_force;
  put( "Average sales is " );
  put( yearly_sales / sales_force );

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

  sales_force : universal_numeric := 55;
  yearly_sales : universal_numeric := 2000000;
  put( "Average sales is " );
    subtotal : universal_numeric := yearly_sales / sales_force;
    put( subtotal );

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.

Script Debugging

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

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:


trace true;

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

Once tracing is turned on by trace true (or with --trace), 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')
=> "  end loop;" [ 10]
=> "  end loop;"
=> "      put_line( p );" [ 9]
=> (p := ' 1')
=> "  end loop;" [ 10]
=> "  end loop;"
=> "      put_line( p );" [ 9]
=> (p := ' 2')
=> "  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)

If a script is started with the --break (or -b) option, a SIGINT (e.g. a control-c) will pause the script and give the user a command prompt. This breakout prompt is identical to a normal interactive session except for the command to quit. "return" will retry the last statement. "logout" will terminate the script.

Without a --break, a SIGINT will terminate the script, just as if a return command was executed.

$ cat breakdemo.bush
for i in 1..10000 loop
  put_line( i );
  delay 1;
end loop;
$ bush breakdemo.bush

$ bush --break breakdemo.bush
breakdemo.bush: 5: 1: While in for loop
  put_line( i );
^ Break: return to continue, logout to quit
=> i := 9999
=> return
=> (returning)
breakdemo.bush: 5: 1: While in for loop
  put_line( i );
^ resuming here

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 create 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 Red Hat Profile script
-- This is executed by the Business Shell during startup.

-- Define Aliases

-- ls, by default, has no color.  ls_color is a script that turns
-- on the ls --color flag before running ls.

ls : constant command := "/home/ken/bush/ls_color";

-- Define various environment variables needed by other software

PATH := "/usr/gnat/bin:/home/ken/bin:/usr/java/bin:" & PATH;
pragma export( shell, PATH );

ASIS_DIR : string := "/usr/lib/asis";
pragma export( shell, ASIS_DIR );

CLASSPATH : string := ".:/usr/lib/jgnat.jar";
pragma export( shell, CLASSPATH );

JGNAT_LIB : string := "/usr/lib/jgnat";
pragma export( shell, JGNAT_LIB );

KDEDIR : string := "/usr";
pragma export( shell, KDEDIR );

LANG : constant string := "en_US";
pragma export( shell, LANG );

LESSOPEN : constant string := "|/usr/bin/lesspipe.sh %s";
pragma export( shell, LESSOPEN );

LS_COLORS : string := "no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:
pragma export( shell, LS_COLORS );

QTDIR : string :="/usr/lib/qt-2.3.1";
pragma export( shell, QTDIR );

SSH_ASKPASS : constant string := "/usr/libexec/openssh/gnome-ssh-askpass";
pragma export( shell, SSH_ASKPASS );

-- export DISPLAY only if it exists
DISPLAY : string := "undefined";
pragma unchecked_import( shell, DISPLAY );
if DISPLAY /= "undefined" then
   pragma export( shell, DISPLAY );
end if;

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

-- declare company name, but don't export beyond shell session
company : constant string := "Compu-Global-Mega-Corp Inc.";

-- show the date and time with UNIX/Linux date command

-- change my BUSH prompt
pragma prompt_script( `pwd;echo "=> ";` );

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

Application Scripting

Certain kinds of programs, such as games or the Gimp, allow users to write their own scripts   For example, there are game scripts that change the behaviour of enemies, or Gimp scripts that apply imaging effects to a photograph.  BUSH can be used as a scripting language for these kind of applications.

There is a special pragma, pragma restriction( no_external_commands ), that will disable all operating system commands.   If you are using BUSH strictly as a scripting language, this pragma will guarantee that your BUSH scripts will be portable to other operating systems.  In addition, if you use pragma ada_95, your scripts will have less errors and commonly used scripts can compiled with GCC Ada (with only minor changes) for extra speed.

Scripts that interact with other programs must be able to share data with the BUSH scripts.  In order to share variables with BUSH, you will have to export your variables as environment variables or add the appropriate declarations to the scripts before you run them with BUSH.

A simple C example called scripting.c is included in the examples directory.

/* -------------------------------------------- */
/* scripting.c                                  */
/*                                              */
/* An example of using BUSH as a scripting      */
/* language for a C program.                    */
/* -------------------------------------------- */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int main() {
  char i_string[255];
   int i;
   FILE *f;

/* assign a value to i */

  i = 5;

/* export i */

  sprintf( i_string, "i_string=%d", i ); // convert i to a string
  if ( putenv( i_string ) != 0 )         // add i to the environment
    printf( "putenv i_string failed: %s\n", strerror( errno ) );

/* Create the script to run */

  f = fopen( "scripting_example.bush", "w" );
  fprintf( f, "%s\n", "pragma restriction( no_external_commands );" );
  fprintf( f, "%s\n", "pragma ada_95;" );
  fprintf( f, "%s\n", "procedure scripting_example is" );
  fprintf( f, "%s\n", "i : integer := numerics.value( i_string );" );
  fprintf( f, "%s\n", "begin" );
  fprintf( f, "%s\n", "put_line( i * 2 );" );
  fprintf( f, "%s\n", "end scripting_example;" );
  fclose( f );

/* Run the script. If successful, delete script */

  if ( system( "../bush scripting_example.bush" ) != 0 ) {
    printf( "Oh, no. There was an error in the script\n" );
  } else {
    unlink( "scripting_example.bush" );
  return 0;


When the script is run, the value of i is multipled by 2 and the result is printed.

=> gcc -Wall -o scripting script.c
=> ./scripting

In this example, since the script is being written "on the fly", i could have been defined by hard-coding it into the script:

fprintf( f, "%s\n", "i : integer := %d;", i );

For added security, application scripts can be run in a restricted shell (read the next section).

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.  Restricted shells run untrustworthy or important scripts "in a bottle".

BUSH restricted shells don't allow:

Unlike some other restricted shells, a BUSH restricted shell will allow:

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

Restricted shells are also useful for scripts that run as the superuser.  Such scripts can potentially create a lot of damage because of script mistakes since the ordinary security measures of the computer system do not apply.

End of Document