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.
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.
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 4to 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,
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
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:
Name | Example | Purpose |
---|---|---|
$user | d45552db-1a7d-4a57-b97d-c435474bd39d | The UUID of the user (the avatar that triggered the script) |
$self | d69ca06e-aa22-49e4-86e1-42677e26f3f5 | The unit's own UUID |
$name | Serena | The display name of the user (the avatar that triggered the script) |
$me | SXD rhet0rica | The 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:
Operator | Meaning |
---|---|
+ | 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.
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 usingset i = $i + 1
,set e %word $i in $files
, andif $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.