Shell scripts are a core component of ARES, implemented by the _exec shell (command-line processor.) They are used to install packages, load personas, and implement the Setup wizard.

Basics

A shell script consists mainly of a series of individual system commands listed one by one on separate lines in a text file (notecard.) These commands are unprefixed, so e.g. the @power off command (used by the unit to shut itself down) is written as power off in a shell script. For example, a simple script called "goodnight.as" might do the following:

say Goodnight. power off

Critically, the first command is finished before the second is started, ensuring that the unit actually says "Goodnight." before turning itself off.

You may also embed comments inside shell scripts by starting a line with a hash character (#). These lines will not be executed.

#say Good morning. #power off say Goodnight. #say Good afternoon. power off

Since lines starting with # will be ignored, this script is actually the same as the previous example.

Naming and placement

Like all user data, shell scripts should be placed in link 3 (user memory) of your ARES system. Since they are text files, they may also be stored on a remote source, and will be downloaded when required.

The preferred file extension for an ARES shell script is .as, which stands for ARES Script. Personas use the extension .p, and package scripts use the extensions .pkg or .spkg (for OS upgrades.) All of these types have full access to ARES. It is the responsibility of the unit not to download or run anything untrustworthy.

Where did my file go? Text files dropped in link 1 of your ARES system will automatically be moved into link 3. This is called "sideloading."

Running a shell script

exec interprets the do command as an exhortation to stop any currently-running shell script and begin a new one. To run a shell script named fudge.as, run do fudge.as (with appropriate prefix, e.g. @.) The do command does not care about file extensions, and will attempt to interpret any text file given to it as a shell script.

ARES also supports a basic file type association system, which assigns programs to run when a filename with an extension is presented directly on the command line. Thus, referring to our previous example and assuming no system settings have been changed from the default configuration, the unit may type @fudge.as to invoke what is actually exec do fudge.as. (This also works with .p files and any other associations defined in LSD:fs.open.)

Flow control

Often it is useful not to simply run commands in the order they are presented, but to be able to go back (or ahead) in the program, to execute commands only if certain conditions are met, or to repeat some instructions. exec shell scripts accomplish all these requirements with the use of labels and go to statements.

If you were raised on Pascal, bite the pillow.

exec uses the syntax jump <label> to go to a label. Labels must be on their own lines, and are written with a preceding @ and a trailing : with no surrounding whitespace (spaces, tabs, etc), which makes them easy for exec to find.

For example:

echo Statement 1 echo Statement 2 jump skip echo Statement 3 @skip: echo Statement 4

This program will print,

Statement 1 Statement 2 Statement 4

to the user, bypassing Statement 3. Labels may also be before the statement that jumps to them:

echo Statement 1 echo Statement 2 @skip: echo Statement 3 jump skip echo Statement 4

Note that we have swapped the lines @skip: and jump skip. This program will print,

Statement 1 Statement 2 Statement 3 Statement 3 Statement 3 Statement 3 Statement 3 Statement 3 Statement 3 ...

indefinitely! This is not a very useful program; if you ever make the mistake of writing something similar, you can stop it with the exit command.

Environment variables

Without the ability to manipulate or test variables, there is no real need for shell scripting; the unit may as well create SL gestures to run a batch of commands on its behalf. With variables, the shell programmer can store and substitute small pieces of text, including numbers, using the LSD:env database section.

Assigning variables

To assign a value to a variable, use the set statement:

set foo Hello, world!

The system command db env.foo will confirm that foo has indeed been set to Hello, world!. You can also use the default alias env to check the status of all your current shell script variables simultaneously.

The basic syntax of set is to specify the name of a variable (which is, as stated, actually, a database entry inside LSD:env) followed by the value you want to set it to. There are several different ways to use set depending on what you want it to do. Here are the basic uses.

To assign a variable to hold some text (called a "string" in programming):

set <variable> <string>

To assign a variable to hold a number (see "Expressions," below, for more details):

set <variable> = <expression>

The spaces around the equals sign are mandatory.

To delete a variable:

set <variable> %undefined

To assign a variable to a new, randomly-generated UUID:

set <variable> %key

Using variables

To retrieve the value of a variable and insert it into a program, put $ (a dollar sign) and then the variable's name. Using our first example,

set foo Hello, world! say $foo

will cause the unit to speak Hello, world!, as instructed. The dollar sign syntax will interpret letters, numbers, periods (full-stops), underscores, and hyphens as part of the variable name, so foo1234-a_b.c3 is an an acceptable variable name for use with substitution.

However, since dollar signs are often used to express monetary quantities (i.e., dollars), it will not accept variables that start with a number. say I need about $3.50. will cause the unit to speak I need about $3.50. without any alterations.

Escapes: In some situations it may be necessary to delay the evaluation of variable substitution. For example, the author of a persona may want to make a script that includes a pronoun, but not freeze that pronoun in place when the persona is loaded. (The unit's gender may be changed at any time, so this would be inconvenient.) In such situations you can use \$ to make exec emit a dollar sign. An example of this can be seen in in default.p, which includes the line:

persona set action.mind @say This unit cannot comply while \$pm.gen cortex is disabled.

To freeze the pronoun in place when the persona is activated, the code would simply be:

persona set action.mind This unit cannot comply while $pm.gen cortex is disabled.

Note the removal of @say. Persona messages are sent directly to the input processor for execution, so the @say (which passes control to exec with the @ and then back to input with the say) provides a second opportunity to perform variable substitution.

Arguments

When an exec script is started with exec do (or one of the shorthand syntaxes explained earlier), extra parameters can be taken in from the user by specifying them after the script's name on the command line. For example, package scripts are often invoked with the syntax @do pkgname-version.pkg install. These parameters are provided to the program as an array (numbered list) called arg, and their quantity is specified in a variable called args.

say This script was called with $args argument(s). Its filename is: $arg.0 say if $args > 0 then say The first argument passed was $arg.1
Programmers, take note—there are no square brackets here for array subscripts. The dot operator is used to access indices of both JSON objects and arrays.

Cleaning up

All variables used in exec scripts are automatically exported; that is, they are all persistent between scripts. Remember to delete your variables at the end of your program with set <name> %undefined, or your unit's environment will eventually run out of memory.

Environment constants

exec supports some pseudo-variables that are always recognized but cannot be modified. These are:

NameExamplePurpose
$userd45552db-1a7d-4a57-b97d-c435474bd39dThe UUID of the user (the avatar that triggered the script)
$selfd69ca06e-aa22-49e4-86e1-42677e26f3f5The unit's own UUID
$nameSerenaThe display name of the user (the avatar that triggered the script)
$meSXD rhet0ricaThe unit's callsign (copied from LSD:id.callsign)

Attempting to assign values to these names is discouraged; doing so will create entries in the LSD:env database section, but they will be masked (overridden) during access attempts.

Conditionals

To execute a statement only under certain circumstances, exec provides the if [not] ... then ... syntax. This has four major forms:

To test for equality between two strings of text, use is:

if <variable> is <value> then <statement>

To test for the presence of an element in a JSON object, use in (added in ARES 0.3.1):

if <key> in <object> then <statement>

To test whether a mathematical expression is non-zero, write the expression directly:

if <expression> then <statement>

To test whether a file exists in ring 3 inventory, use:

if exists <filename> then <statement>

Note that this will only tell you if a local file exists. To test for a remote file, see the examples under xset.

Mathematical expressions are explained in the next section.

All if statement forms can be negated by writing not after the if.

Be careful what variables you put in an if statement. At present, all variable substitution is done prior to evaluation, meaning that a variable with any of the words "in", "is", or "not" inside can cause all sorts of chaos.

Here are some more concrete examples of if statements:

if $arg.1 is Bob then say You guessed my name! if not $fruit in {"apple":1.00,"banana":2.00} then say I've never heard of $fruit if 1 + 1 == 2 then echo Math test passed!

Mathematical Expressions

exec supports the operators:

OperatorMeaning
+Add two numbers
-Subtract the right number from the left
*Multiply two numbers
/Divide the left number by the right; returns 0 for the whole expression and prints a warning if the right side is zero
\(new in ARES 0.4.0) Performs division like /, but afterwards converts the result to an integer, rounding toward zero
==Yield 1 if both sides are within 0.001 of each other, otherwise 0
!=Yield 0 if both sides are within 0.001 of each other, otherwise 1
<=Yield 1 if the right side minus the left side is at least -0.001
>=Yield 1 if the left side minus the right side is at least -0.001
<Yield 1 if the right side minus the left side is at least 0.001
>Yield 1 if the left side minus the right side is at least 0.001

As with the set ... = ... syntax, every token must be surrounded by space characters.

As suggested by the table above, in both set and if statements, arithmetic is done using floating-point numbers, limited to no more than three points of decimal precision past the period. The tests for inequality are slightly "fuzzy" to ensure that practical evaluations complete as the scripter intends.

Operators are parsed from left to right, with no order of precedence and no way to group them. Thus 10 == 9 + 2 will evaluate to 2 (as 10 == 9 becomes 0 before + 2 is considered), and if will erroneously interpret this as true.

To avoid these deficiencies, use the xset and calc utilities, as described at the end of this chapter.

Rounding properly

To round a fraction using the standard rule (>= 0.5 round away from zero, < 0.5 round toward zero), use:

if $a > 0 then set b = $a + 0.5 \ 1 if $a < 0 then set b = $a - 0.5 \ 1

Since no operator precedence applies, the division will be performed after the addition or subtraction of 0.5.

Parsing strings with set

The set keyword has another big trick up its sleeve: it can be used to count and extract elements of strings.

Consider the following sample string:

set s According to all known laws of aviation,\nthere is no way a bee should be able to fly.\nIts wings are too small to get its fat little body off the ground.\nThe bee, of course, flies anyway,\nbecause bees don't care what humans think is impossible.

(Note that this is close to the 256-character line limit for an SL notecard, and that use of \n to insert a linebreak.)

With this sample, we can do the following:

Extract a single character:

set x %char 3 in $s

This assigns the value o to $x. (0 would be A, 1 would be c, and 2 would also be c.)

Extract a single word:

set x %word 2 in $s

This assigns the value all to $x. (Prior to version 0.3.1, this would not consider linebreaks, only spaces, resulting in mistakes.)

Extract a single line:

set x %line 4 in $s

This assigns the value because bees don't care what humans think is impossible. to $x.

We can also count the elements:

set x %count chars $s

This assigns the value 243 to $x.

set x %count words $s

This assigns the value 47 to $x. (Prior to version 0.3.1, this would not consider linebreaks, only spaces, resulting in mistakes.)

set x %count lines $s

This assigns the value 5 to $x.

Parsing JSON with set

Starting with ARES 0.3.1, scripts may use set and if for certain JSON manipulations:

Iterating over JSON objects

set x %keys $json

where $json is a JSON object. This will create a space-separated list of keys from the JSON object. For example,

set x %keys {"alpha":1,"beta":2}

will assign LSD:env.x the value: alpha beta

To extract a value from a JSON object, use the set <destination> %index <key-name> of <JSON> syntax:

set x %index beta of {"alpha":1,"beta":2}

will assign x the value 2.

This syntax is fairly robust, so the example code:

set json {"alpha":1,"beta":2,"gamma":{"delta":4,"epsilon":5}} set k1 gamma set k2 delta set v %index $k1.$k2 of $json echo $v

...will correctly answer 4 to the user.

Variables are not deleted when the script ends, and remain present in LSD:env. Use set <name> %undefined to remove them at the end of your script.

A concrete example: the pkg script

Combining everything you've learned so far, you should now be able to make sense of how a .pkg script works. Here is the full text (minus the header) for define-1.1.0.pkg:

set parc-file define-1.1.0.ax.parc set files define define.info set help-file define.info set filecount %count words $files if $args == 0 then jump default if $arg.1 is install then jump install if $arg.1 is remove then jump remove if $arg.1 is reinstall then jump remove if $arg.1 is update then jump remove if $arg.1 is cache then jump cache if $arg.1 is uncache then jump uncache @default: echo This is the package installer script $arg.0 echo Syntax: do $arg.0 install|remove|reinstall|update|cache|uncache echo Dependencies: $parc-file echo Files: $files jump cleanup @cache: ax download $parc-file jump cleanup @uncache: fs delete $parc-file jump cleanup @remove: echo Removing $arg.0 set i = 0 @remove-loop: set e %word $i in $files fs delete $e set i = $i + 1 if $i < $filecount then jump remove-loop if $arg.1 is reinstall then jump install fs delete $parc-file fs delete $arg.0 if $arg.1 is update then jump cleanup help forget $help-file pkg removed $arg.0 jump cleanup @install: echo Installing $arg.0 ax download $parc-file pkg open $parc-file set i = 0 @install-loop: set e %word $i in $files pkg extract $parc-file $e set i = $i + 1 if $i < $filecount then jump install-loop pkg close $parc-file help index $help-file pkg installed $arg.0 @cleanup: set files %undefined set filecount %undefined set parc-file %undefined set help-file %undefined set e %undefined set i %undefined echo Done.

Note the following:

  • The use of indentation to mark sections of the program.
  • The series of if statements near the start which decide the program's behavior based on the value of $arg.1
  • The structure of the @remove-loop: and @install-loop: sections, which iterate over $files (a space-separated list of filenames) by using set i = $i + 1, set e %word $i in $files, and if $i < $filecount then jump <label>
  • The exact meaning of set i = $i + 1. What does it do? Why is there only one $ in this line? Why is it safe to write $i + 1 on the right side of = but not ==?
  • The @cleanup: section at the end, which deletes each of the variables used. Without this step, variables remain in LSD:env and can cause clutter!

Other exec Features

Aliases

An alias is a user-defined command that is automatically expanded into another command when typed. They are created and destroyed with @alias.

For example, setup is actually an alias of do asu_0.as (a shell script that constructs the main menu of the Setup Wizard), so @setup will tell exec to run that script.

Aliases are expanded in-place, but must be at the start of a command. This means that you may provide additional arguments at the command line. By default, name is an alias of id name. Therefore, @name expands to @id name, and @name ROBOT expands to @id name ROBOT (and will therefore successfully set the unit's name to a new value.)

To see current aliases, type @alias

To see only the definition of a specific alias, type @alias <shorthand>

To create an alias, type @alias <shorthand> <definition> — do not include any @ signs, aside from the one in @alias.

To delete an alias, type @alias delete <shorthand> (Consequently, you may not create an alias named delete.) This command will return OK. even if the alias did not exist.

Aliases are stored in LSD:input.alias. They may be used, created, or deleted inside shell scripts, but it is best practice not to rely on the user's configuration being a certain way.

echo

The echo built-in sends messages directly to the output pipe. It supports two switches, -d (send to debug channel) and -c (send to console). echo -c <message> will send messages via llOwnerSay(), making it equivalent to Arabesque's remark built-in from Companion. Without any switches, output will usually end up sent to the avatar that triggered the script, using llRegionSayTo() on channel 0. For a more flexible way of sending messages, see the tell package.

from and to

These built-ins can be used to override the input and output pipes for a command, making it easier to work with pipes. Use

from <pipe> <command>

to run a program with its input pipe overridden, and

to <pipe> <command>

to run a program with its output pipe overridden.

No @ should be placed before <command>, as the command is run directly.

xset

The xset program is a bundled utility that stores the output of a program in an environment variable. Its syntax is:

xset <variable> <command>

No @ should be placed before <command>, as the command is run directly.

xset and db

xset is highly useful for retrieving arbitrary database entries by using db with the json flag:

xset version db json kernel.version echo $version set version %undefined

This will print the version number of the ARES kernel (e.g. 0.3.1) without any other text.

Note that without the json flag, the db utility will attempt to provide human-readable text, i.e. kernel.version 0.3.1, which is somewhat less convenient to evaluate.

Starting with ARES version 0.4.0 (Beta 2) and xset 1.2.0, the most common usage of xset:

xset <name> db json <value>

can be abbreviated, to:

xset <name> = <value>

This skips the invocation of db, and is less likely to trip up when used several times in a row.

xset and fs:root

To check if a file exists anywhere in the filesystem, the if exists ... then ... syntax is insufficient, as (for performance and memory reasons) it only checks local files. Fortunately, the filesystem stores a complete list of all extant files in LSD:fs:root, and the fs program provides an easy way to check it.

set filename asu_0.as xset filetype fs info $filename if not $filetype is %undefined then echo File $filename exists! set filetype %undefined set filename %undefined

xset and calc

Starting in ARES 0.4.0, the calc program is packaged with the standard ARES distribution. calc supports operator precedence, bitwise and boolean operators, and parentheses. It can also be used to convert to and from hexadecimal, and to generate random numbers.

Example calculation:

xset result calc (0x1100 & 0x0101) * (3 ** 2) echo $result set result %undefined # yields 2304.000000

Random number generation:

xset result calc -r 1 100 echo A random number between 1 and 100: $result set result %undefined

See @help calc for more examples.

At present, boolean and bitwise OR (represented with | and ||) are not usable in calc because exec will interpret the vertical bar character as a pipe. Alas!