ReadArgs, ReadItem and FindArg

ReadArgs() is the standard command line parser of AmigaDOS. It knows the Shell's quoting and escaping rules, matches arguments to a human-readable template, and gives every command the same built-in ? help. Because the parser lives in dos.library, a command that uses it needs no C startup code to split its arguments, which keeps small tools small. This article covers the template language, the RDArgs/CSource structures, the help protocol, resource lifetime, the lower-level ReadItem() and FindArg(), and the version-specific traps.

For the Shell side of quoting see The AmigaDOS Shell; for how the arguments reach a command see Shell Internals.

Where the command line comes from

When a Shell runs a command through RunCommand(), it copies the argument line into the buffer of the command's input filehandle (pr_CIS). ReadArgs(template, array, NULL) reads it from there. The line must end with a line feed (0x0a); anything after the first line feed is ignored.

A side effect: the first buffered read from Input() returns the command line, not data from the stream. To read real input after parsing, switch the handle to buffered mode and drop the buffer:

FGetC(Input());   /* make the stream buffered          */
Flush(Input());   /* discard the command line parameters */

The template

A template is a comma-separated list of keywords. Each keyword may have an abbreviation after = and options after /:

FROM/A/M,TO/A,QUICK=Q/S,BUFFER=BUF/K/N,MODE/T
Option Array entry Meaning
(none) STRPTR or NULL Optional string, matched by keyword or by position
/S LONG: 0 or non-zero Switch: set if the keyword appears at least once
/T LONG: DOSTRUE/DOSFALSE Switch with an explicit value: On or Y sets it, Off or N clears it. The words are fixed and not localised. It does not toggle.
/K per type Filled only if the keyword appears on the command line
/A per type Required; missing → failure
/N LONG * or NULL Decimal number; NULL tells you it was not given
/M STRPTR * (NULL-terminated) or NULL Collects every argument not matched elsewhere; at most one per template
/F STRPTR The whole rest of the line, keywords included (used by Alias)

Note: the NDK 3.2 autodoc for ReadArgs() describes /T as a "toggle" that flips the value each time it appears.

Matching rules:

  • On the command line a keyword is followed by its value after a space, tab or =: FROM=org and FROM org are the same.
  • Either the full keyword or its abbreviation is accepted (QUICK or Q for QUICK=Q/S).
  • Arguments without a keyword fill the non-/K, non-/S slots from left to right.
  • Unfilled /A slots are taken from the end of the /M list. This is how Copy FROM/A/M,TO/A treats the last name as the destination.
  • /M example: with FROM/M,TO, the line a b c TO d gives TO = "d" and FROM = {"a","b","c",NULL}.

The NDK limits a template to MAX_TEMPLATE_ITEMS (100) keywords and a /M list to MAX_MULTIARGS (128) items; more /M items fail with ERROR_LINE_TOO_LONG (dos/rdargs.h).

Basic use

#include <exec/types.h>
#include <dos/dos.h>
#include <dos/rdargs.h>
#include <proto/dos.h>

#define TEMPLATE "FROM/A/M,TO/A,QUIET/S,BUFFER/K/N"
enum { ARG_FROM, ARG_TO, ARG_QUIET, ARG_BUFFER, ARG_COUNT };

int main(void)
{
    LONG args[ARG_COUNT] = { 0 };       /* must start zeroed */
    struct RDArgs *rda;
    STRPTR *from;
    LONG bufsize = 4096;

    rda = ReadArgs(TEMPLATE, args, NULL);
    if (rda == NULL) {
        PrintFault(IoErr(), "MyCopy");
        return RETURN_FAIL;
    }
    if (args[ARG_BUFFER])
        bufsize = *(LONG *)args[ARG_BUFFER];
    for (from = (STRPTR *)args[ARG_FROM]; *from; from++)
        if (!args[ARG_QUIET])
            Printf("%s -> %s (%ld)\n", *from, (STRPTR)args[ARG_TO], bufsize);

    FreeArgs(rda);                      /* strings in args[] die here */
    return RETURN_OK;
}

Everything args[] points to (strings, /M arrays, /N numbers) belongs to the RDArgs and is freed by FreeArgs(). Copy what you need to keep before freeing.

The RDArgs and CSource structures

Pass your own RDArgs to parse a string instead of the command line, to supply a buffer, to add extended help or to set flags. Always allocate it with AllocDosObject(DOS_RDARGS, NULL) (see DOS Objects Lifecycle).

struct CSource {
    STRPTR CS_Buffer;    /* text to parse */
    LONG   CS_Length;    /* its length in characters */
    LONG   CS_CurChr;    /* index of the next character to read */
};

struct RDArgs {
    struct CSource RDA_Source;   /* input source */
    LONG   RDA_DAList;           /* PRIVATE, NULL from AllocDosObject() */
    STRPTR RDA_Buffer;           /* optional parsing buffer */
    LONG   RDA_BufSiz;           /* its size in bytes */
    STRPTR RDA_ExtHelp;          /* optional extended help */
    LONG   RDA_Flags;            /* RDAF_* */
};
  • If CS_Buffer is NULL, input comes from the pr_CIS buffer, exactly as with a NULL rdargs. There is no field for a filehandle source.
  • CS_CurChr is normally 0. To parse one long string in several passes, leave it where the previous call stopped.
  • RDA_Buffer holds parsed strings, /N numbers and /M pointer arrays. If it is NULL or too small, ReadArgs() allocates more, unless RDAF_NOALLOC is set.
  • RDA_DAList is private: never touch it.
Flag Value Effect
RDAF_STDIN 2.0+ 1 Defined but not used
RDAF_NOALLOC 2.0+ 2 Never allocate or grow buffers; overflow fails with ERROR_NO_FREE_STORE
RDAF_NOPROMPT 2.0+ 4 A lone ? is an ordinary argument; no help is shown

Parsing a string

#define TEMPLATE "FILES/M,ALL/S"

LONG ParseLine(STRPTR line)               /* line must end in '\n' */
{
    LONG args[2] = { 0 };
    LONG ok = FALSE;
    struct RDArgs *rda = AllocDosObject(DOS_RDARGS, NULL);

    if (rda == NULL)
        return FALSE;
    rda->RDA_Source.CS_Buffer = line;
    rda->RDA_Source.CS_Length = strlen(line);
    rda->RDA_Source.CS_CurChr = 0;
    rda->RDA_Flags            = RDAF_NOPROMPT;

    if (ReadArgs(TEMPLATE, args, rda)) {
        /* ... use args ... */
        ok = TRUE;
        FreeArgs(rda);                    /* frees the parse results only */
    } else {
        PrintFault(IoErr(), "parse");
    }
    FreeDosObject(DOS_RDARGS, rda);       /* the structure is ours */
    return ok;
}

The ? help protocol

Unless RDAF_NOPROMPT is set, a command line that is just ?:

  1. prints the template on standard output and reads a new line from standard input;
  2. if that line is again ? and RDA_ExtHelp is non-NULL, prints the extended help and reads a third time.

When the input is an extended (V47) Shell, ? input is read through the shell's SHELL_METH_FGETS, so TAB expansion of file names works at that prompt.

Note: the NDK 3.2 dos/rdargs.h comment says RDA_ExtHelp is "displayed instead of the template string".

Return value, errors and cleanup

On success ReadArgs() returns the RDArgs (yours, or one it allocated). Call FreeArgs() in both cases.

On failure it returns NULL and frees everything it allocated. A structure you passed in is not freed: dispose of it with FreeDosObject() (no FreeArgs() needed).

Error codes in IoErr() besides I/O errors:

Code Cause
ERROR_NO_FREE_STORE 1.3+ Out of memory, or RDAF_NOALLOC buffer overflow
ERROR_REQUIRED_ARG_MISSING 2.0+ A /A argument is missing
ERROR_KEY_NEEDS_ARG 2.0+ A keyword was given without its value
ERROR_TOO_MANY_ARGS 2.0+ More arguments than the template can take
ERROR_BAD_NUMBER 2.0+ A /N value is not a valid number
ERROR_BAD_TEMPLATE 2.0+ The template itself is malformed
ERROR_UNMATCHED_QUOTES 2.0+ An opening quote has no closing quote
ERROR_LINE_TOO_LONG 1.3+ Line too long, e.g. too many /M items

The manual is not consistent about a missing /A argument: §10.2.9 assigns it ERROR_REQUIRED_ARG_MISSING, while §15.5.1 names ERROR_KEY_NEEDS_ARG. Treat both as "bad arguments".

FreeArgs

VOID FreeArgs(struct RDArgs *args);   /* V36 */

Releases everything attached to the RDArgs, and the structure too if ReadArgs() allocated it. Afterwards the contents of array are invalid; zero it before reusing it.

From V39 FreeArgs() also clears RDA_Buffer, because it may point at memory being freed. This makes an AllocDosObject() structure safe to reuse, but a custom buffer you installed must be set again before the next ReadArgs().

Note: the NDK 3.2 autodoc for ReadArgs() says RDA_Buffer must be reset (cleared or set) before every call on a passed-in structure.

ReadItem — one argument at a time

LONG ReadItem(CONST_STRPTR name, LONG maxchars, struct CSource *cSource);   /* V36 */

Reads one item into name (at most maxchars bytes) from cSource, or from the pr_CIS buffer if cSource is NULL.

  • Items are separated by space, tab, ; or = outside quotes. Line feed and EOF end an item even inside quotes.
  • In a quoted item the quotes are removed and *", *E, *N and ** are replaced. Outside quotes * is an ordinary character.
  • After the item, a line feed, ; or EOF is pushed back into the source; a space, tab or = is consumed.
  • No buffer is allocated: an overflow returns ITEM_ERROR.
  • ReadItem() never sets IoErr(), not even for ITEM_ERROR.
Result Value Meaning
ITEM_EQUAL 2.0+ −2 Ended by =: the item is probably a keyword
ITEM_ERROR 2.0+ −1 Buffer too small, or end of data/line before the closing quote
ITEM_NOTHING 2.0+ 0 Nothing left: line feed, ; or EOF
ITEM_UNQUOTED 2.0+ 1 Unquoted item read
ITEM_QUOTED 2.0+ 2 Quoted item read
/* Split a line into items, honouring Shell quoting. */
UBYTE item[256];
struct CSource cs;
LONG r;

cs.CS_Buffer = (STRPTR)"copy \"My File\" RAM:\n";
cs.CS_Length = strlen(cs.CS_Buffer);
cs.CS_CurChr = 0;
while ((r = ReadItem(item, sizeof(item), &cs)) > 0 || r == ITEM_EQUAL)
    Printf("%s item: %s\n", r == ITEM_QUOTED ? "quoted" : "plain", item);
if (r == ITEM_ERROR)
    PutStr("unterminated quote or item too long\n");

FindArg — look up a keyword

LONG FindArg(CONST_STRPTR arg_template, CONST_STRPTR keyword);   /* V36: template in d1 */

Returns the zero-based position of keyword in the template (case-insensitive; abbreviations count), or −1. IoErr() is not changed.

FindArg("FROM/A/M,TO/A,QUIET=Q/S", "q");    /* 2 */
FindArg("FROM/A/M,TO/A,QUIET=Q/S", "ALL");  /* -1 */

Note: the NDK 3.2 SFD names the d1 parameter keyword and the d2 parameter arg_template; the autodoc and the manual pass the template first (d1).

Interaction with the Shell and C compilers

  • The Shell leaves *N, *E, *" and ** inside quotes untouched; ReadArgs()/ReadItem() replace them. Echo "a*Nb" prints two lines because Echo uses ReadArgs(). A program that parses pr_Arguments itself sees the raw sequences.
  • Parsers in some C compilers' startup code do not follow these rules exactly and give different results for quoted arguments.
  • = separates a keyword from its value, so a file name containing = must be quoted on the command line.
  • ReadArgs() uses a fair amount of stack (dos/rdargs.h gives this as the reason for its limits); give commands a sensible stack.

Version notes

Version Change
V36 /M/N returned strings; /T did not work; /K and /F interacted; a /K before a non-switch parameter forced that parameter's keyword (NDK autodoc BUGS). Fixed in V37.
V39 FreeArgs() clears RDA_Buffer.
V47 ? input goes through the shell's TAB expansion when available.

See Also


Sources: Thomas Richter, Amiga ROM Kernel Reference Manual: AmigaDOS (2024, v1.27), §15.5 (pp. 341–346), §15.1.4 (pp. 315–316), §15.2.3–15.2.5 (pp. 328–330), §5.6 (p. 40), §10.2.9 (p. 155); NDK 3.2 R4 Include_H/dos/rdargs.h, dos/dos.h; Autodocs/dos.doc (ReadArgs, FindArg); SFD/dos_lib.sfd Updated: 2026-11-13