On this page:
1.1 Basic Shell Testing
expect/  shell
1.2 Pattern-Based Shell Automation
expect/  shell/  patterns
1.3 Advanced Pattern Features
1.3.1 Variable Capture
1.3.2 Flow Control
1.3.3 Timeout Handling
1.3.4 Custom Actions
1.4 Pattern Matching Reference
match-pattern
1.5 Error Handling and Debugging
1.6 Migration from Basic Shell Testing
1.7 Pattern and Action Structures
pattern-action
pattern-exact
pattern-regex
pattern-glob
pattern-timeout
pattern-eof
action-send-text
action-continue
action-retry
action-error
action-proc
shell-run-patterns
1.8 Best Practices

1 Shell Commands🔗ℹ

 (require recspecs/shell) package: recspecs-lib

The recspecs/shell module provides tools for testing interactive shell commands, inspired by the Unix expect tool. It supports both simple transcript-based testing and advanced pattern-based automation.

1.1 Basic Shell Testing🔗ℹ

syntax

(expect/shell cmd-expr expected-str ...)

Run cmd-expr as a subprocess and compare the interaction against expected-str .... Lines in the expectation that begin with > are sent to the process as input (without the prompt). The command’s responses are captured and the full transcript is checked against the expectation.
(require recspecs/shell)
(expect/shell "cat"
"> hi""\n"
"hi""\n"
"> there""\n"
"there")

1.2 Pattern-Based Shell Automation🔗ℹ

For complex interactive scenarios, expect/shell/patterns provides a declarative pattern-matching approach similar to the Unix expect tool.

syntax

(expect/shell/patterns cmd-expr option ... [pattern action] ...)

 
option = #:timeout timeout-expr
  | #:strict? strict?-expr
     
pattern = string-expr
  | (exact string-expr)
  | (regex regex-expr)
  | (glob glob-string-expr)
  | (timeout seconds-expr)
  | eof
     
action = (send-input text-expr)
  | continue
  | retry
  | (error message-expr)
  | procedure-expr
Runs cmd-expr as an interactive subprocess and processes output using pattern/action pairs. Each pattern is matched against the accumulated output, and when matched, the corresponding action is executed.

Options:
  • #:timeout — Sets the default timeout in seconds for the entire session (default: 30)

  • #:strict? — Controls whether whitespace normalization is applied (default: #f)

Patterns:
  • string-expr or (exact string-expr) — Exact string matching

  • (regex regex-expr) — Regular expression matching with capture group support

  • (glob glob-string-expr) — Glob pattern matching with * and ? wildcards

  • (timeout seconds-expr) — Matches when the specified timeout is reached

  • eof — Matches when the process terminates

Actions:
  • (send-input text-expr) — Send text as input to the process

  • continue — Proceed to the next pattern

  • retry — Retry the current pattern

  • (error message-expr) — Raise an error with the given message

  • procedure-expr — Call a custom procedure with session and variables

Examples:

Simple command interaction:
(expect/shell/patterns "bash"
  ["$" (send-input "echo hello")]
  ["hello" (send-input "exit")])

Using regex patterns with timeout:
(expect/shell/patterns "slow-server" #:timeout 60
  [(regex #rx"Server started on port ([0-9]+)")
   (send-input "connect")]
  [(timeout 30) (error "Server startup timeout")]
  ["Connected" continue])

Glob patterns and error handling:
(expect/shell/patterns "deployment-script"
  [(glob "*$ ") (send-input "deploy app")]
  [(glob "*Success*") continue]
  [(glob "*Error*") (error "Deployment failed")]
  [(glob "*Warning*") continue])

Variable capture and substitution:
(expect/shell/patterns "bash"
  ["$" (send-input "echo 'port: 8080'")]
  [(regex #rx"port: ([0-9]+)") (send-input "connect $0")]
  ["connected" (send-input "exit")])

1.3 Advanced Pattern Features🔗ℹ

1.3.1 Variable Capture🔗ℹ

When using (regex regex-expr) patterns, capture groups are automatically extracted and made available for variable substitution in subsequent actions. Variables are referenced as $0, $1, $2, etc., where $0 is the first capture group.

(expect/shell/patterns "date"
  [(regex #rx"([A-Z][a-z]+) ([0-9]+)")
   (send-input "echo Month: $0, Day: $1")])

1.3.2 Flow Control🔗ℹ

  • continue — Advances to the next pattern in the sequence

  • retry — Repeats the current pattern (useful for polling)

  • (error msg) — Terminates with a controlled error

(expect/shell/patterns "build-system"
  ["Building..." continue]
  [(regex #rx"Progress: ([0-9]+)%") retry]
  ["Build complete" continue]
  [(glob "*failed*") (error "Build failed")])

1.3.3 Timeout Handling🔗ℹ

Individual patterns can specify timeouts, and a global session timeout can be set:

(expect/shell/patterns "long-running-process" #:timeout 300
  ["Starting..." continue]
  [(timeout 60) (send-input "status")]
  ["Progress: 100%" continue]
  [(timeout 300) (error "Process timeout")])

1.3.4 Custom Actions🔗ℹ

For complex logic, actions can be procedures that receive the session state and captured variables:

(define (analyze-output session vars)
  (if (> (length vars) 0)
      (printf "Captured: ~a~n" (car vars))
      (printf "No captures~n"))
  'continue)
 
(expect/shell/patterns "analyzer"
  [(regex #rx"Result: (.+)") analyze-output])

1.4 Pattern Matching Reference🔗ℹ

procedure

(match-pattern pattern text vars)  
boolean? list? string?
  pattern : pattern?
  text : string?
  vars : list?
Tests whether pattern matches text. Returns three values: whether the pattern matched, updated variable list with any captures, and the text.

This function underlies the pattern matching in expect/shell/patterns and can be used directly for testing pattern logic.

(define-values (matched? vars text)
  (match-pattern (pattern-regex #rx"port: ([0-9]+)") "port: 8080" '()))

1.5 Error Handling and Debugging🔗ℹ

When patterns fail to match or timeouts occur, expect/shell/patterns provides detailed error messages including:

  • The accumulated output at the time of failure

  • The pattern that was being matched

  • Suggestions for common issues

For debugging complex interactions, enable verbose mode with recspecs-verbose? or the RECSPECS_VERBOSE environment variable to see real-time output.

1.6 Migration from Basic Shell Testing🔗ℹ

Existing expect/shell tests can be gradually migrated to the pattern-based approach for enhanced functionality:

(expect/shell "interactive-app"
"> start""\n"
"Ready""\n"
"> process data.txt""\n"
"Processing...""\n"
"Done""\n"
"> quit")
 
 
 
(expect/shell/patterns "interactive-app"
  ["$" (send-input "start")]
  ["Ready" (send-input "process data.txt")]
  [(glob "*Processing*") continue]
  ["Done" (send-input "quit")])

1.7 Pattern and Action Structures🔗ℹ

The pattern-based shell automation is built on the following structures, which can be used directly for advanced scenarios:

struct

(struct pattern-action (pattern action vars)
    #:extra-constructor-name make-pattern-action)
  pattern : pattern?
  action : action?
  vars : list?
Combines a pattern with its corresponding action. The vars field stores captured variables from previous pattern matches.

struct

(struct pattern-exact (text)
    #:extra-constructor-name make-pattern-exact)
  text : string?
Matches exact string content within the output.

struct

(struct pattern-regex (regex)
    #:extra-constructor-name make-pattern-regex)
  regex : regexp?
Matches using regular expressions and captures groups for variable substitution.

struct

(struct pattern-glob (pattern)
    #:extra-constructor-name make-pattern-glob)
  pattern : string?
Matches using glob patterns with * and ? wildcards.

struct

(struct pattern-timeout (seconds)
    #:extra-constructor-name make-pattern-timeout)
  seconds : number?
Triggers when the specified number of seconds have elapsed.

struct

(struct pattern-eof ()
    #:extra-constructor-name make-pattern-eof)
Triggers when the subprocess terminates or reaches end-of-file.

struct

(struct action-send-text (text)
    #:extra-constructor-name make-action-send-text)
  text : string?
Sends the specified text to the subprocess as input.

struct

(struct action-continue ()
    #:extra-constructor-name make-action-continue)
Proceeds to the next pattern in the sequence.

struct

(struct action-retry ()
    #:extra-constructor-name make-action-retry)
Retries the current pattern without advancing.

struct

(struct action-error (message)
    #:extra-constructor-name make-action-error)
  message : string?
Raises an error with the specified message.

struct

(struct action-proc (proc)
    #:extra-constructor-name make-action-proc)
  proc : procedure?
Executes a custom procedure with signature (-> shell-session? list? symbol?).

procedure

(shell-run-patterns cmd    
  patterns    
  [#:timeout timeout])  void?
  cmd : (or/c string? (listof string?))
  patterns : (listof pattern-action?)
  timeout : number? = 30
Low-level function that runs the pattern-based shell interaction. This function underlies expect/shell/patterns and can be used for programmatic control.

(shell-run-patterns "bash"
  (list (pattern-action (pattern-exact "$")
                       (action-send-text "echo test")
                       '())
        (pattern-action (pattern-exact "test")
                       (action-send-text "exit")
                       '())))

1.8 Best Practices🔗ℹ

  • Use specific patterns to avoid false matches: prefer (exact "$ ") over "$"

  • Include timeout patterns for long-running operations

  • Use continue judiciously to handle intermediate output

  • Capture important values with regex patterns for reuse

  • Test pattern logic in isolation using match-pattern

  • Enable verbose mode during development for better visibility

  • Consider the order of patterns carefully: more specific patterns should come before general ones

  • Use eof patterns to handle unexpected process termination gracefully