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
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).
The ^[[6n (^[ == ESC == 0x1b == \u001b) is the CPR request (sent by jshell).
Strings like ^[[32;1R (row 32, column 1) is the current cursor position (generated by terminal driver and read back by jshell).
Related
I have this bash scipt
#!/bin/bash
Login="bla 0, xy;Z" ##the spaces and the ',' and ';' are important!
expect -c "
spawn telnet *HOST*
expect "Done."
send '$Login'
expect "Done."
send "command 0". ## the ' 0' is important! Same prob as with the password
??? ## what do I have to do to get the output in a VAR?
expect "Done."
"
The two questions are:
the $Login is not interpreted as text from bash, so it thinks with 0 a new command starts... and ',' as well as ';' seems to be a problem too. escaping with '' did not work!?
How do I get the output as a $VAR to use it later?
Is this possible at all in bash? or do I have to use tcl (which I have never ever used or read anything about before)?
Thanks for advices!
andy
In addition to the useful comments above, single quotes have no special meaning in Tcl/expect, they are just ordinary characters that are being sent with the password.
As Charles writes, passing data via the environment is helpful.
Also, Tcl comments are a bit annoying: the # is only recognized as a comment if it occurs where a command starts, so you'll see me use ;# below.
I find using a quoted heredoc removes quoting difficulties.
When you say "result in a VAR", I assume you want a shell variable?
untested
export Login="bla 0, xy;Z"
export HOST=something
VAR=$(expect << 'END_EXPECT'
log_user 0 ;# turn off normal stdout of the interaction
# so we can capture only the output you want
spawn telnet $env(HOST)
expect "Done."
send "$env(Login)\r" ;# don't forget to "hit enter"
expect "Done."
send "command 0\r"
# what do I have to do to get the output in a VAR?
# use a regex capturing the command's output. Note that the command
# itself appears in the output
expect -re "command 0\r\n(.+)Done."
# the shell will capture this output
puts $expect_out(1,string)
send "exit\r" ;# or whatever you need to do to quit
expect eof
END_EXPECT
)
related: https://github.com/elves/elvish/issues/827
I'm on a prompt implementation and would like to test I correctly support elvish. I already do that for other shells by invoking their prompt variable/method, e.g.
bash
bash -ci 'echo $PS1'
fish
fish -c 'fish_prompt'
How can I print te prompt content in Elvish?
From IRC I got some hint:
edit:prompt = { tilde-abbr $pwd; put '❱ ' }
But output is not the same as what elvish render:
~/.pure❱ $edit:prompt
▶ '~/.pure'
▶ '❱ '
I'm expecting:
~/.pure❱
The outputs from prompts are stringified and concatenated, you can achieve it with:
$edit:prompt | each $print~
However this doesn't work with elvish -c or echo ... | elvish.
elvish doesn't have an interactive mode per se. Unlike POSIX shells. I know it's been discussed before but I can't recall why the edit: namespace isn't available when doing elvish -c '$edit:prompt | each $print~'
I want to use expect at shell script
My code is here
#!/bin/sh
expect << EOF
send [cat hello]
EOF
This command is failed
invalid command name "cat"
while executing "cat hello"
invoked from within "send [cat hello]"
but, send [cat hello] command successed in expect prompt
expect1.1> send [cat hello]
world
expect1.2>
Why do I output the different execution results ?
Inside expect shell:
While you are in expect shell and whenever any executable shell command is given, that will be executed and as if like you have executed it in terminal normally.
expect1.1> ls
A B C D
expect1.2> cat A
I am A
expect1.3> pwd
/home/dinesh/test
expect1.4>
If you have keep them inside square brackets like [cat A], unlike a normal command call, still it will execute shell command and returns empty string.
expect1.4> set val [cat A]
I am A
expect1.5> puts "->$val<-"
-><-
expect1.6>
If you add a procedure which is having a name equivalent as that of any shell command, then only the corresponding command will be called.
expect1.6> proc cat {input} {
+> return "you passed $input\n"
+> }
expect1.7> cat A
you passed A
expect1.8> cat
wrong # args: should be "cat input"
while executing
"cat "
expect1.9> send [cat A]
you passed A
expect1.10>
Unless spawn_id is set (by means of spawning any program), Expect will always expect and send commands to the stdin and stdout respectively.
expect1.11> exp_internal 1
expect1.12> expect hai
expect: does "" (spawn_id exp0) match glob pattern "hai"? no
As you can see in the debug output, exp0 points to the stdin only.
When expect program called
When expect program is called explicitly, at that time, in order to execute any shell command, you have to use exec command, else you will get the invalid command name error message.
expect << EOF
send [exec cat hello]
EOF
Output :
[dinesh#lab test]$ ./baek.sh
I am A
[dinesh#lab test]$
Now, why this difference ?
Technically, when you are using expect shell, you are allowed with the feature of executing shell command in there itself.
I am trying to export tcl buffer to bash variable, i cannot get it to work.
I hope my example below will be clear of what i am trying to accomplish.
I definately would like an tcl embeded script
======================================
#!/bin/bash
var=bash_to_tcl
expect -c "
puts lindex $argv 0
expect "xx"
send "123\n"
set $var $expect_out(buffer) <<<< setting the variable to export to bash>>>>>>
}
exit 0
<<<>>
=====================================
echo $var "tcl_to_bash" (THIS IS WHERE I AM HAVING ISSUES) <<<<<<<<<<<<<<<<<<<
=====================================
I have been searching all over for some clue of example but cannot find any.
I got the ecpect working but cannot export the output back to bash
The child process (expect) cannot alter the environment of the parent (bash). Usually information is passed between processes via the stdio channels:
#!/bin/bash
# this is how bash captures the output of the expect program
var=$(expect -c '
spawn ...
expect "xx"
send "123\n"
# here is expect sending the info back to the parent
puts $expect_out(buffer)
')
do something with "$var"
I am trying to capture the output of the "dir" command by logging into a switch, but I am unable to do so. I am using Expect within Bash. I am making use of expect_out to capture output of that command in a buffer and print it out. Actually I want to capture the output and perform some operations on it.
Script:
#!/bin/bash
expect -c "
spawn telnet 1.1.1.1 2000
sleep 1
send \"\r\"
send \"\r\"
expect {
Prompt> { send \"dir\r\" }
}
set output $expect_out(buffer)
"
echo "$output"
Output:
spawn telnet 1.1.1.1 2000
Trying 1.1.1.1...
Connected to 1.1.1.1 (1.1.1.1).
Escape character is '^]'.
Prompt>
Prompt>
After these prompts are displayed, the scripts just exits. How can I fix this problem?
After I split it, I can use parameter substitution as well as single quotes. Now I am facing different error.
Script:
expect -c "
spawn telnet $IP $PORT1
sleep 1
send \"\r\"
send \"\r\"
"
expect -c '
expect {
Prompt> { send \"dir\r\" }
set output $expect_out(buffer)
puts "$output"
}
'
Output:
spawn telnet 172.23.149.139 2033
can't read "expect_out(buffer)": no such variable
while executing
"expect {
Prompt> { send \"dir\r\" }
set output $expect_out(buffer)
puts "$output"
}
"
I changed it to according to the suggestions. But I am still facing errors.
Script:
output=$(expect -c '
spawn telnet '"$IP $PORT1"'
sleep 1
send '"\r"'
send '"\r"'
expect Prompt> { send '"dir\r"' }
expect '"\n"'
expect -indices Prompt>
puts '"[string range $expect_out(buffer) 0 [expr $expect_out(0,end) - 1]]"'
')
echo "======="
echo "$output"
echo "======="
Output:
syntax error in expression "(0,end) - 1"
while executing
"expr (0,end) - 1"
invoked from within
"string range (buffer) 0 [expr (0,end) - 1]"
invoked from within
"puts [string range (buffer) 0 [expr (0,end) - 1]]"
=======
spawn telnet 1.1.1.1 2000
Trying 1.1.1.1...
Connected to 1.1.1.1 (1.1.1.1).
Escape character is '^]'.
Prompt>
Prompt>
=======
Hence to circumvent the error, I changed, the line
puts '"[string range $expect_out(buffer) 0 [expr $expect_out(0,end) - 1]]"'
to
puts '"$expect_out(buffer)"'
But then I am getting no error, but the output of dir is also not getting printed. Something like:
Prompt>
Prompt> (buffer)
The second of your “split” Expect programs does not have access to the spawned telnet process. When you split them like that, you made them independent (one can not access the variables or state of the other; actually by the time the second one has started the first one, and its telnet process, no longer exist).
The shell will automatically concatenate any strings (that are not separated by unquoted/unescaped whitespace) without regard to the kind of quotes (if any) they use. This means you can start put the first part of your Expect program in single quotes, switch to double quotes for the parameter substitution, and then go back to single quotes for the rest of the program (to avoid having to escape any of "$\` that occur in your Expect code).
expect -c '
spawn telnet '"$HOST $PORT"'
sleep 1
⋮ (rest of Expect program)
'
It uses single quotes to protect most of the program, but switches back to double quotes to let the shell substitute the its HOST and IP parameters into the text of the Expect program.
Next, the shell that started expect can not access variable set inside the Expect program. The normal way to collect output from a child process is to have it write to stdout or stderr and have the shell collect the output via a command substitution ($()).
In Tcl (and Expect), you can use puts to send a string to stdout. But, by default, Expect will also send to stdout the normal “interaction” output (what it receives from any spawned commands and what it sent to them; i.e. what you would see if you were running the spawned command manually). You can disable this default logging with log_user 0.
You might write your program like this:
#!/bin/sh
output=$(expect -c '
# suppress the display of the process interaction
log_user 0
spawn telnet '"$HOST $PORT"'
sleep 1
send "\r"
send "\r"
# after a prompt, send the interesting command
expect Prompt> { send "dir\r" }
# eat the \n the remote end sent after we sent our \r
expect "\n"
# wait for the next prompt, saving its position in expect_out(buffer)
expect -indices Prompt>
# output what came after the command and before the next prompt
# (i.e. the output of the "dir" command)
puts [string range $expect_out(buffer) \
0 [expr $expect_out(0,start) - 1]]
')
echo "======="
echo "$output"
echo "======="
Because your Expect script is enclosed in double quotes, the shell is expanding $expect_out to an empty string. Put the body of the expect script in single quotes.
When you set a variable in Expect, the shell will have no idea. When the Expect script completes, its variables vanish too. You have to puts the expect_out buffer.