ZetCode

Algol 68 environment and processes

last modified November 13, 2026

A program does not run in a vacuum. The shell hands it a set of environment variables, it may start other programs and wait for them to finish, and it can measure how much processor time it has used. Algol 68 Genie exposes these facilities through a few predeclared procedures: getenv reads the environment, system runs a command, sleep pauses the program, clock reports processor time, and stop ends the program early.

These procedures are extensions of the implementation, not part of the Revised Report, and a program that uses them depends on Algol 68 Genie. This article describes each one and closes with a small tool that runs a command and reports how it ended.

Reading environment variables

getenv takes the name of a variable and returns its value as a STRING. A name that is not in the environment yields an empty string, which is a row with lower bound 1 and upper bound 0, so UPB answers 0 and the test needs no special case.

env.a68
BEGIN
  STRING path = getenv("PATH");
  STRING missing = getenv("A68G_NO_SUCH_VARIABLE");
  IF UPB path > 0 THEN print(("PATH is set", newline)) FI;
  IF UPB missing = 0 THEN print(("A68G_NO_SUCH_VARIABLE is not set", newline)) FI
END

PATH is almost always set, while the invented name is not. The program does not need the value of a variable to find out whether it exists, and it never has to handle a missing value as an error.

$ a68g env.a68
PATH is set
A68G_NO_SUCH_VARIABLE is not set

A setting with a default

Environment variables are a convenient way to configure a script without editing its source. The declaration initialises name from the environment, and the conditional clause replaces an empty value with the default before the greeting is written.

greet.a68
BEGIN
  STRING name := getenv("A68G_NAME");
  IF UPB name = 0 THEN name := "world" FI;
  print(("Hello, ", name, newline))
END

The program prints Hello, world when A68G_NAME is not set. Starting it as A68G_NAME=Ada a68g greet.a68 prints Hello, Ada, with no change to the program.

$ a68g greet.a68
Hello, world

Starting a process

system passes its string to the shell, runs the command, waits for it to finish, and returns a status. The command writes to the same standard output as the program, so its line appears between the lines of the program.

run.a68
BEGIN
  INT rc := system("echo hello from the shell");
  print(("status ", whole(rc, 0), newline));
  rc := system("exit 3");
  print(("exit 3 gives status ", whole(rc, 0), ", code ", whole(rc OVER 256, 0), newline))
END

The status of a command that succeeds is 0, whatever it wrote. The second call runs exit 3, which is a shell builtin, and the value 768 shows that the status is not simply the exit code.

$ a68g run.a68
hello from the shell
status 0
exit 3 gives status 768, code 3

Exit codes and signals

A wait status packs two numbers into one integer: the exit code is multiplied by 256 and the number of the signal that killed the process is added to it. rc OVER 256 recovers the exit code and rc MOD 256 recovers the signal. A shell reports the same value after dividing by 256, which is why an exit code of 3 is described as a status of 768.

signal.a68
BEGIN
  INT rc := system("kill -TERM $$");
  print(("status ", whole(rc, 0), newline));
  print(("exit code ", whole(rc OVER 256, 0), ", signal ", whole(rc MOD 256, 0), newline))
END

A process killed by a signal has no exit code of its own, so the code is 0 and the signal number is 15 for the termination signal. A program that inspects the status of another program should test both halves before it decides what went wrong.

$ a68g signal.a68
status 15
exit code 0, signal 15

Waiting

sleep suspends the program for the given number of seconds, which is useful when a script has to pace itself or wait for a file to appear. The pause costs no processor time, and the example shows that by reading clock before and after the call.

wait.a68
BEGIN
  REAL before = clock;
  sleep(1);
  REAL after = clock;
  print(("slept for one second", newline));
  print(("cpu time used: ", (after - before < 0.1 | "almost none" | "some"), newline))
END

clock returns the processor time used by the program since it started, in seconds, as a REAL. A sleep does not advance it, because the program is not running, and the difference stays below a tenth of a second even though a second has passed.

$ a68g wait.a68
slept for one second
cpu time used: almost none

Measuring a computation

The same two readings time a piece of work. The declarations of start and finish hold the values returned by clock, and the difference is the processor time that the loop used.

timeit.a68
BEGIN
  REAL start = clock;
  INT total := 0;
  FOR i TO 1000000 DO total +:= i MOD 7 OD;
  REAL finish = clock;
  print(("total ", whole(total, 0), newline));
  print(("cpu time was measurable: ", (finish - start > 0.0 | "yes" | "no"), newline))
END

The exact number is a property of the machine and of the interpreter, so the program prints the result of the work and only asserts that the difference is positive. A program that compares two implementations in the same run can use the difference itself, because the machine conditions cancel out.

$ a68g timeit.a68
total 2999998
cpu time was measurable: yes

Ending a program early

stop ends the program at once, with status 0. It is the way out of a check that fails deep inside a procedure, where returning a value would only push the problem to the caller. The statements after stop, in the procedure and in the block that called it, never run.

stop.a68
PROC check = (INT n) VOID:
  IF n > 2 THEN
    print(("value too large, stopping", newline));
    stop
  ELSE
    print(("accepted ", whole(n, 0), newline))
  FI;

BEGIN
  FOR i TO 4 DO check(i) OD;
  print(("this line never appears", newline))
END

The loop reaches 1 and 2, and the third call prints its message and stops. The final print of the block is never reached. This implementation also accepts abort and halt, which have the same effect.

$ a68g stop.a68
accepted 1
accepted 2
value too large, stopping

A command runner

The arguments of a script and the system procedure combine into a tool that runs one command and reports how it ended. The name comes from argv(4), which is the first argument after the script itself.

report.a68
BEGIN
  IF argc < 4 THEN
    print(("usage: a68g --script report.a68 COMMAND", newline))
  ELSE
    INT rc := system(argv(4));
    print(("command ", argv(4), " finished with code ", whole(rc OVER 256, 0), newline))
  FI
END

The command is passed to system unchanged, so the shell sees it as a line of its own. Running the tool with true reports a code of 0, and running it with exit 2 reports 2, because the status is divided by 256 before it is printed.

$ a68g --script report.a68 true
command true finished with code 0

Next steps

The command-line arguments article explains argc and argv, which the last example uses, and the files article shows how a program reads and writes files of its own. The reading input article covers the values that a program takes from the terminal, and the parallel clause article runs several units at the same time.

Source

Revised Report on the Algorithmic Language Algol 68

The Algol 68 Genie project

In this article we have read environment variables and given them defaults, started processes with system, decoded a wait status into an exit code and a signal, paused a program with sleep, measured processor time with clock, ended a program early with stop, and built a tool that runs a command and reports its status. The command-line arguments article continues with the words that a script receives from its caller.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Algol 68 tutorials.