Search code examples
terminalexpectttyptyjshell

Can I run jshell inside Unix expect?


I'd like to redirect jshell input using expect, so that I can simulate typing in recorded demonstrations. But although I can spawn a jshell process from an expect script, which can also recognise the jshell prompt, after that nothing works. expect outputs what looks like a control sequence, like ^[[24;9R, and I don't see any output from jshell. Different terminal types produce different character sequences, but none of them work. This behaviour is consistent between expect on Ubuntu and Mac OS. Any suggestions for how to investigate this problem would be welcome. expect -d doesn't help.

Here's a transcript of the jshell session I want to simulate

$ jshell
|  Welcome to JShell -- Version 9.0.1
|  For an introduction type: /help intro

jshell> 3
$1 ==> 3

jshell> 

and here's the script that I think should do it:

#!/usr/bin/expect -f
spawn jshell
expect jshell>
send "3\r"
expect jshell>

When I run that script (on Mac OS 10.11.6, but I get very similar results on Ubuntu), I see this output

spawn jshell
|  Welcome to JShell -- Version 9.0.1
|  For an introduction type: /help intro

jshell> ^[[24;9R

Then expect times out, and the last line of output is overwritten by the shell prompt (so it looks as though at timeout more control characters are being written).

Adding -d to the flags for expect in line 1 of the script results in this output:

expect version 5.45
argv[0] = /usr/bin/expect  argv[1] = -d  argv[2] = -f  argv[3] = ./expectscript
set argc 0
set argv0 "./expectscript"
set argv ""
executing commands from command file ./expectscript
spawn jshell
parent: waiting for sync byte
parent: telling child to go ahead
parent: now unsynchronized from child
spawn: returns {19712}

expect: does "" (spawn_id exp8) match glob pattern "jshell>"? no
|  Welcome to JShell -- Version 9.0.1
|  For an introduction type: /help intro

expect: does "|  Welcome to JShell -- Version 9.0.1\r\n|  For an introduction type: /help intro\r\n" (spawn_id exp8) match glob pattern "jshell>"? no

jshell>
expect: does "|  Welcome to JShell -- Version 9.0.1\r\n|  For an introduction type: /help intro\r\n\r\njshell> " (spawn_id exp8) match glob pattern "jshell>"? yes 
expect: set expect_out(0,string) "|  Welcome to JShell -- Version 9.0.1\r\n|  For an introduction type: /help intro\r\n\r\njshell> "
expect: set expect_out(spawn_id) "exp8"
expect: set expect_out(buffer) "|  Welcome to JShell -- Version 9.0.1\r\n|  For an introduction type: /help intro\r\n\r\njshell> "
send: sending "3\r" to { exp8 }

expect: does "" (spawn_id exp8) match glob pattern "jshell>"? no

expect: does "\u001b[6n" (spawn_id exp8) match glob pattern "jshell>"? no
^[[32;1Rexpect: timed out

Solution

  • Managed to make it work (tested on Debian 9.3 with jshell 9.0 and Expect 5.45):

    [STEP 103] # cat jshell.exp
    proc expect_prompt {} {
        upvar spawn_id spawn_id
    
        expect -ex "jshell> "
    
        # the CPR (cursor position report) code
        expect -ex "\x1b\[6n"
    
        # read the CPR result and send it the application
        expect_tty -re {\x1b\[[0-9]+;[0-9]+R}
        send $expect_out(0,string)
    }
    
    stty raw; # give tty's full control to jshell since it's crazy
    
    spawn jshell
    expect_prompt
    
    send "3\r"
    expect_prompt
    
    send "/exit\n"
    expect eof
    [STEP 104] # expect jshell.exp
    spawn jshell
    |  Welcome to JShell -- Version 9.0.1
    |  For an introduction type: /help intro
    
    jshell> 3
    $1 ==> 3
    
    jshell> /exit
    |  Goodbye
    [STEP 105] #
    

    The magic is about CPR (cursor position report) (search CPR on the page).

    1. The ^[[6n (^[ == ESC == 0x1b == \u001b) is the CPR request (sent by jshell).
    2. Strings like ^[[32;1R (row 32, column 1) is the current cursor position (generated by terminal driver and read back by jshell).