Shell Variables

AmigaDOS has two kinds of shell variables. Local variables belong to one shell process and live in memory on its pr_LocalVars list, together with the shell's aliases. Global variables are plain files in the ENV: assign, visible to every process. The Shell expands both on the command line, and programs read and write them through GetVar(), SetVar(), FindVar() and DeleteVar() (V36). This article covers the expansion syntax, the variables the Shell itself sets or reads, the four functions with their flags, and the bugs and limits of each release.

Where variables live

Kind Storage Scope Persistence
Local variable struct LocalVar on pr_LocalVars (type LV_VAR) The shell and the commands it runs Copied into shells it creates (NP_CopyVars, default TRUE)
Alias struct LocalVar on the same list (type LV_ALIAS) Always local In memory only
Global variable File ENV:name System-wide Lost on reboot unless also written to ENVARC:

ENV: is normally an external link in the RAM-Handler that copies entries from ENVARC: on demand; ENVARC: is a late-binding assign to SYS:Prefs/Env-Archive. Saving a variable permanently means writing both.

Names are case-insensitive and may contain / to build a hierarchy. For global variables each level is a directory in ENV:; the hierarchy also works for local variables, whose ln_Name then holds the full path.

Local beats global: when a name exists in both, the local one wins, both in Shell expansion and in GetVar() without flags.

Expansion in the Shell

Syntax Result
$name Value. The name runs over 0-9, A-Z, a-z; the first other character ends it.
${name} Value; any characters except } allowed, so ${dir/sub} works.
$?name 1 if the variable exists (local or global), else 0
$??name 1 if a global variable exists, else 0
$!name Binary expansion of a global variable, control characters included. Always put it in double quotes.
*$ A literal $

Rules:

  • An undefined variable is not expanded: $foo stays $foo.
  • The Shell adds no quotes. A value containing spaces, tabs or = becomes several arguments unless the reference is inside double quotes.
  • Inside double quotes the Shell escapes * and " in the value with *, so that ReadArgs() gets back the original text. Outside quotes nothing is escaped.
  • $! inserts control characters such as line feeds into the tokenizer; the surrounding quotes make the Shell escape them so the command receives them intact.
  • The prompt is expanded too, so a long prompt can be kept in a local variable.
Set target "RAM:My Dir"
MakeDir "$target"          ; one argument: RAM:My Dir
If $?target EQ 1
  Echo "target is ${target}"
EndIf

Variables the Shell sets

Variable Contents Also in
process CLI number of this shell (unique; same as pr_TaskNum and the Status number) %N in the prompt
RC Return code of the last command; compared with FailAt to abort scripts cli_ReturnCode
Result2 Error code of the last failed command (printed by Why) cli_Result2

Variables the Shell reads

Boolean variables count as on for on, 1, yes or TRUE (any case); every other value means off.

Variable Type Effect
VIEWER path Program used to show a non-executable file that datatypes.library recognises (V45). No default: unset means such a file gives an error.
echo Boolean Echo each command before running it
debug Boolean Send every command to the serial port (9600 baud, 8N1); captured in RAM:syslog when boot-menu logging is on
oldredirect Boolean Redirection only right after the command name (V33 BCPL CLI syntax); List C: >Out then lists a file called >Out
interactive Boolean Trace scripts: Return/y runs a command, Del/n skips it, Esc/q stops tracing and deletes the variable. Set by the boot menu's trace option.
simpleshell Boolean Read lines in cooked mode: no TAB expansion and, since V47 (history moved from the console into the Shell), no history
histsize number History length in lines (ignored with simpleshell)
histskipdups Boolean Do not store a command again if it repeats (ignored with simpleshell)

Note: the NDK 3.2 shell release notes (47.37) state that skipping duplicates became the default and that the variable NOLISTSKIPDUPS turns it off.

GetVar

LONG GetVar(CONST_STRPTR name, STRPTR buffer, LONG size, LONG flags);   /* V36 */

Looks for a local variable first, then a global one, unless flags restrict it. size includes the terminating NUL.

  • A normal (text) read stops at the first line feed or NUL and always NUL-terminates.
  • With GVF_BINARY_VAR the whole value is copied; it is still NUL-terminated unless GVF_DONT_NULL_TERM is also set.
  • Truncation is not an error.
  • Success: returns the number of characters stored (without the NUL) and sets IoErr() to the full size of the variable, including anything after a line feed or NUL.
  • Failure: returns −1. IoErr() = ERROR_BAD_NUMBER if size is 0, ERROR_OBJECT_NOT_FOUND if there is no such variable, or any error from reading ENV:.
UBYTE buf[256];
LONG len = GetVar("Language", buf, sizeof(buf), GVF_GLOBAL_ONLY);
if (len >= 0) {
    if (IoErr() > len)
        PutStr("value was truncated\n");
    Printf("Language = %s\n", buf);
} else if (IoErr() == ERROR_OBJECT_NOT_FOUND) {
    PutStr("not set\n");
}

To size a buffer exactly, read once into a small buffer and use IoErr() as the full length (add 1 for the NUL).

SetVar

BOOL SetVar(CONST_STRPTR name, CONST_STRPTR buffer, LONG size, LONG flags);   /* V36 */
  • size = −1 means buffer is a NUL-terminated string (the NUL is not stored).
  • buffer = NULL deletes the variable (same as DeleteVar()).
  • Without GVF_GLOBAL_ONLY and with a value, SetVar() always creates or updates a local variable, even if a global one of that name exists. To set a global variable you must pass GVF_GLOBAL_ONLY.
  • Without GVF_GLOBAL_ONLY and with buffer NULL, it deletes a local variable, or if there is none, a global one.
  • GVF_LOCAL_ONLY only matters for deletion.
  • GVF_SAVE_VAR (global only) also writes or deletes the copy in ENVARC:. It works, although the 1991 AmigaDOS Manual does not document it.
  • Missing hierarchy levels are created.
  • Returns non-zero on success, with IoErr() undefined. On failure returns DOSFALSE; deleting a variable that does not exist gives ERROR_OBJECT_NOT_FOUND.
/* Make a preference permanent, like "SetEnv SAVE". */
if (!SetVar("MyApp/Mode", "fast", -1, GVF_GLOBAL_ONLY | GVF_SAVE_VAR))
    PrintFault(IoErr(), "SetVar");

Requirements and pitfalls

  • The file system behind ENV: must support MODE_READWRITE opens and SetFileSize(). The FFS and the RAM-Handler do; some third-party file systems do not, and then setting a global variable fails silently.
  • Up to and including V47 there is a race: SetVar() writes the data and then truncates the file in a second step, so a concurrent writer or reader of the same global variable can see a mixed or truncated value.
  • Before V47 the files created in ENV:/ENVARC: kept the e (executable) bit, and a name containing // crashed the system.
  • GVF_GLOBAL_ONLY must not be combined with LV_ALIAS: aliases are always local.

FindVar

struct LocalVar *FindVar(CONST_STRPTR name, ULONG type);   /* V36 */

Finds a local variable (LV_VAR) or alias (LV_ALIAS) of the calling process; globals are never found. Only the low bits of type count: GVF_* flags are ignored. Returns NULL and ERROR_OBJECT_NOT_FOUND if not found; on success IoErr() is untouched.

struct LocalVar {
    struct Node lv_Node;    /* ln_Name = full name; ln_Type = LV_VAR or LV_ALIAS */
    UWORD       lv_Flags;
    STRPTR      lv_Value;   /* NOT NUL-terminated */
    ULONG       lv_Len;     /* length of lv_Value in bytes */
};
  • Never allocate or change this structure yourself; it may grow. Use SetVar().
  • lv_Value is a byte array of lv_Len bytes, not a C string.
  • lv_Flags gets GVF_BINARY_VAR when a non-printable value is stored, but AmigaDOS never reads it: only the flags of GetVar() decide between text and binary.

DeleteVar

LONG DeleteVar(CONST_STRPTR name, ULONG flags);   /* V36 */

Equivalent to SetVar(name, NULL, 0, flags). Without flags it tries a local variable first, then a global one. GVF_GLOBAL_ONLY, GVF_LOCAL_ONLY and GVF_SAVE_VAR work as for SetVar(). It does not delete directories (hierarchy levels). Non-zero on success with IoErr() undefined; ERROR_OBJECT_NOT_FOUND if nothing matched.

Flag values

From dos/var.h:

Constant Value Use
LV_VAR 2.0+ 0 Variable (type byte)
LV_ALIAS 2.0+ 1 Alias (type byte)
LVF_IGNORE 2.0+ 0x80 Entry ignored by GetVar() and friends
GVF_GLOBAL_ONLY 2.0+ 0x100 Global (ENV:) only
GVF_LOCAL_ONLY 2.0+ 0x200 Local only
GVF_BINARY_VAR 2.0+ 0x400 Do not stop at line feed/NUL
GVF_DONT_NULL_TERM 2.0+ 0x800 With GVF_BINARY_VAR: no NUL terminator
GVF_SAVE_VAR 3.1+ 0x1000 With GVF_GLOBAL_ONLY: also update ENVARC:

Version notes

Version Change
V36 GetVar() returned the full variable size instead of the number of characters copied, and did not set IoErr(). Fixed in V37.
V36–V37 GVF_DONT_NULL_TERM worked only for local variables. Fixed in V39.
V45 VIEWER shows data files through datatypes.
before V47 SetVar() left the e bit on ENV: files; // in a name crashed.
V47 History moved from the console into the Shell; simpleshell disables it.
up to V47 SetVar() race on concurrent access to the same global variable.

See Also


Sources: Thomas Richter, Amiga ROM Kernel Reference Manual: AmigaDOS (2024, v1.27), §15.1.5–15.1.6 (pp. 317–318), §15.4 (pp. 337–341); NDK 3.2 R4 Include_H/dos/var.h; SFD/dos_lib.sfd; ReleaseNotes/shell-RelNotes Updated: 2026-11-13