| name | write-arexx |
| description | Write ARexx scripts for AmigaOS automation, testing, and inter-process communication. Use when creating or modifying .rexx files, writing FS-UAE test harnesses, or automating Amiga workflows. |
| allowed-tools | Read, Write, Edit, Bash, Grep, Glob |
Write ARexx Scripts
You are writing ARexx scripts for AmigaOS 3.x. ARexx is the Amiga's built-in scripting language — think AppleScript but for Amiga. It's string-based, has powerful parsing, and can communicate with any Amiga program that has an ARexx port.
Reference
Read docs/references/arexx-reference.md for the complete language reference. It covers syntax, built-in functions, file I/O, IPC, and testing patterns. Read it before writing any ARexx code.
For deeper ARexx internals (RexxMsg structures, function libraries, command interface protocol), see docs/references/amiga-intern/10-arexx-interface.md.
Mandatory Rules
1. First Line Must Be a Comment
Every ARexx script MUST start with a comment on line 1. Without this, AmigaDOS treats it as a shell script, not ARexx:
/* my-script.rexx — description */
2. Use Simple Stem Variables, Not Multi-Level Compounds
ARexx compound variables use a single stem + tail: name.index. Multi-level compounds like tests.i.desc do NOT work as expected — ARexx resolves only the last tail.
/* WRONG — multi-level compound */
tests.i.desc = "hello" /* Actually sets tests.I.DESC */
/* RIGHT — simple indexed stems */
desc.i = "hello"
cmd.i = "echo hello"
expect.i = "hello"
3. String Comparison Is Case-Insensitive by Default
ARexx comparison operators (=, ~=, >, <) are case-insensitive for strings. Use strict operators for case-sensitive comparison:
/* Case-insensitive (default) */
IF "Hello" = "hello" THEN SAY "matches" /* TRUE */
/* Case-sensitive (strict operators) */
IF "Hello" == "hello" THEN SAY "matches" /* FALSE */
4. No Short-Circuit Evaluation
ARexx evaluates ALL parts of a compound boolean expression:
/* WRONG — will crash if ptr is NULL */
IF ptr ~= '' & LENGTH(ptr) > 0 THEN ...
/* RIGHT — nest the conditions */
IF ptr ~= '' THEN DO
IF LENGTH(ptr) > 0 THEN ...
END
5. PULL Reads From Stack First, Not Stdin
PULL reads from the ARexx message stack first. Only if the stack is empty does it read from stdin. Use PARSE PULL for the same behavior, or READLN('STDIN') for explicit stdin reading.
6. ADDRESS COMMAND for Shell Commands
Run AmigaDOS commands with ADDRESS COMMAND:
/* Run a command */
ADDRESS COMMAND 'WORK:grep hello WORK:test.txt >T:output.txt'
/* Check return code */
IF RC > 0 THEN SAY 'Command failed with RC=' || RC
/* Capture output via temp file (no backtick equivalent) */
ADDRESS COMMAND 'WORK:grep -c hello WORK:test.txt >T:count.txt'
IF OPEN('f', 'T:count.txt', 'R') THEN DO
result = READLN('f')
CALL CLOSE('f')
END
7. File I/O Pattern
/* Read a file line by line */
IF OPEN('fh', 'WORK:data.txt', 'R') THEN DO
DO WHILE ~EOF('fh')
line = READLN('fh')
/* process line */
END
CALL CLOSE('fh')
END
/* Write to a file */
IF OPEN('fh', 'RESULTS:output.txt', 'W') THEN DO
CALL WRITELN('fh', 'line 1')
CALL WRITELN('fh', 'line 2')
CALL CLOSE('fh')
END
8. Error Handling
/* Set error trap */
SIGNAL ON ERROR
/* ... script body ... */
EXIT 0
ERROR:
SAY 'Error' RC 'at line' SIGL
EXIT 20
9. PARSE for String Splitting
PARSE is ARexx's most powerful feature — use it instead of manual string manipulation:
/* Split by delimiter */
PARSE VAR line first ':' rest
/* Split by position */
PARSE VAR line name 1 20 age 21 30
/* Split by words */
PARSE VAR line word1 word2 rest
/* Split command output */
PARSE VALUE TIME() WITH hours ':' minutes ':' seconds
Testing Patterns
When writing test harnesses for the FS-UAE testing pipeline:
TAP Output Format
/* TAP (Test Anything Protocol) */
CALL WRITELN('rf', '1..' || testcount) /* Plan line */
CALL WRITELN('rf', 'ok 1 - test description') /* Pass */
CALL WRITELN('rf', 'not ok 2 - test description') /* Fail */
CALL WRITELN('rf', '# expected: foo') /* Diagnostic */
CALL WRITELN('rf', '# actual: bar') /* Diagnostic */
CALL WRITELN('rf', '# passed: N failed: M total: T') /* Summary */
Test Case File Format
The standard test case format for test-fsemu-cases.txt:
TEST: description of test
CMD: WORK:program args WORK:inputfile.txt
EXPECT: expected first-line output (exact match)
EXPECT_RC: 0
TEST: error path test
CMD: WORK:program --bad-flag
EXPECT_CONTAINS: error substring
EXPECT_RC: 10
Assertion modes:
EXPECT: — exact match of first line of stdout
EXPECT_CONTAINS: — substring match (for multi-line output)
EXPECT_RC: — expected Amiga return code (0=OK, 5=WARN, 10=ERROR, 20=FAIL)
See docs/test-coverage-standard.md for mandatory coverage requirements (no happy-path-only testing).
Standard Test Runner Structure
See toolchain/templates/test-runner.rexx for the canonical pattern:
- Read test cases from
WORK:test-cases.txt
- Parse into indexed stems (
desc.i, cmd.i, expect.i)
- Run each command with
ADDRESS COMMAND, redirect output to T:
- Read actual output, compare with expected
- Write TAP results to
RESULTS:tap-output.txt
- Write sentinel file
RESULTS:tests-complete
- Call
C:UAEQuit to shut down the emulator
Sentinel File Format
IF OPEN('sf', 'RESULTS:tests-complete', 'W') THEN DO
CALL WRITELN('sf', 'TESTS_COMPLETE')
CALL WRITELN('sf', 'passed=' || passed)
CALL WRITELN('sf', 'failed=' || failed)
CALL WRITELN('sf', 'total=' || testcount)
CALL CLOSE('sf')
END
AmigaOS Path Conventions
WORK: — mounted work volume (port binaries)
RESULTS: — mounted results volume (test output)
T: — temporary files (maps to RAM:T/)
SYS: — system volume
S: — scripts directory (SYS:S/)
C: — commands directory (SYS:C/)
NIL: — /dev/null equivalent
- Use
/ as directory separator within volumes
- No
. or .. — use / alone for parent directory
Known Limitations / Gotchas
1. No Shell Piping in ADDRESS COMMAND
ADDRESS COMMAND 'echo "hello" | grep hello' does NOT work. AmigaDOS pipe handling through ARexx's ADDRESS COMMAND is unreliable. Instead, write input to a temp file first, then run the command with the file as input.
/* BAD — pipe will not work */
ADDRESS COMMAND 'echo "a:b:c" | cut -d: -f2 >T:out'
/* GOOD — use a temp file */
IF OPEN('tf', 'T:input.txt', 'W') THEN DO
CALL WRITELN('tf', 'a:b:c')
CALL CLOSE('tf')
END
ADDRESS COMMAND 'cut -d: -f2 T:input.txt >T:out'
2. No Command Chaining with && or ;
AmigaDOS does not support && for conditional chaining. The ; separator may work in some shells but not through ARexx's ADDRESS COMMAND. Run commands as separate ADDRESS COMMAND calls instead.
/* BAD — chaining will not work */
ADDRESS COMMAND 'echo >T:f.txt "hi" && cut -c1-2 T:f.txt'
/* GOOD — separate calls */
ADDRESS COMMAND 'echo >T:f.txt "hi"'
ADDRESS COMMAND 'cut -c1-2 T:f.txt >T:out'
3. Default FAILAT Is 10
Commands returning RC >= 10 trigger ARexx's ERROR condition by default. Programs like diff return RC=5 (RETURN_WARN) when files differ, which is fine. But if a command returns RC=10 (RETURN_ERROR), ARexx will jump to the ERROR: label (or abort). Use OPTIONS FAILAT 21 at the top of test harnesses to prevent this.
/* At the top of every test harness */
OPTIONS FAILAT 21
4. Output Capture with Non-Zero RC
When using ADDRESS COMMAND cmd '>' outfile, some ARexx implementations may not write the output file when the command returns non-zero. Use OPTIONS FAILAT 21 to ensure output is always captured regardless of the command's return code.
5. Test Cases Should Use Pre-Created Input Files
For FS-UAE test cases (test-fsemu-cases.txt), write test input data to separate files (e.g., test-grep-input.txt) that get copied to WORK: by the test infrastructure. Do not try to create files on-the-fly in the CMD field.
# BAD — trying to create input inline
TEST: grep finds match
CMD: echo "hello world" | WORK:grep hello
# GOOD — use a pre-created input file
TEST: grep finds match
CMD: WORK:grep hello WORK:test-grep-input.txt
6. Dollar Signs in Execute Scripts
When ARexx runs ADDRESS COMMAND 'Execute scriptfile', the AmigaDOS shell processes the script content. Dollar signs ($) are expanded as AmigaDOS variables (e.g., $RC = return code). If a command argument needs a literal $ (e.g., sed -n '$p' for "last line"), it will be expanded or cause an error. Workaround: use a sed script file (-f) or a different addressing syntax.
# BAD -- $ gets expanded by AmigaDOS Execute
CMD: WORK:sed -n $p WORK:input.txt
# GOOD -- use a script file instead
CMD: WORK:sed -f WORK:test-sed-lastline.sed WORK:input.txt
# Where test-sed-lastline.sed contains: $p
7. ASCII Only -- No UTF-8
ARexx (1987) does not understand UTF-8. Any non-ASCII byte -- including inside comments -- causes "Error 8: Unrecognized token". Use only ASCII (0x00-0x7F). This includes em dashes, smart quotes, and accented characters.
8. Use ~= for Not-Equal, Not =
Both \= and ~= mean "not equal" but \= may not be recognized by all ARexx interpreters. Always use ~= for portability.
9. AmigaDOS Double-Redirect Bug with Run
AmigaDOS parses ALL > redirections at the top level. Run >file1 cmd >file2 applies BOTH redirects to the Run command -- the backgrounded command gets NO redirect and floods the console.
Fix: Write the command + redirect into a temp Execute script, then Run the script:
/* WRONG -- both > apply to Run, cmd gets no redirect */
ADDRESS COMMAND 'Run >T:cli.txt WORK:yes >T:output.txt'
/* RIGHT -- isolate cmd's redirect inside an Execute script */
IF OPEN('sf', 'T:cmd.txt', 'W') THEN DO
CALL WRITELN('sf', 'WORK:yes >T:output.txt')
CALL CLOSE('sf')
END
ADDRESS COMMAND 'Run >T:cli.txt Execute T:cmd.txt'
This pattern is used by per-port ARexx wrappers for testing infinite-output programs. See the test-designer agent for the full wrapper template.
Common Gotchas Checklist
Before finishing any ARexx script, verify: