How can EXPECT interpret an escaped character to a command character - linux

I'd like to be able to pass in a long command to expect. It's a multiple command somehow. First here's my expect script
#!/usr/bin/expect -f
set timeout -1
spawn telnet xxx.xxx.xxx.xxx
expect "*?username:*"
send "someusername\r"
expect "*?assword:*"
send "somepassword\r"
# Here's the command I'd like to pass from the command prompt
set command [lindex $argv 0]
send "$command\r"
send "exit\r"
I would then run this script as so:
./expectscript "mkdir /usr/local/dir1\ncd /usr/local/dir1\ntouch testfile"
Notice that I put "\n" to initiate an enter as though I'm processing the command before moving to the next.
I know you could separate the commands with ";", but for this particular exercise, I'd like to be able have expect interpret the "\n" with a "\r" so that, expect would behave as though it were like this:
send "mkdir /usr/local/dir1\r"
send "cd /usr/local/dir1\r"
send "touch testfile\r"
The question then becomes how can expect interpret the "\n" to be "\r"? I've tried putting the "\r" in the argument instead of "\n", but that doesn't work.
Thanks for the input.

When I do a simple experiment, I find that the \n in the argument is not converted by my shell (bash) into a newline; it remains a literal. You can check this out for yourself by just using puts to print out the command line argument, like this:
puts [lindex $argv 0]
Working around this requires a little bit of work to split things. Alas, Tcl's split command does not split on multi-character sequences (it splits on many different characters at once instead) so we'll need a different approach. However, Tcllib has exactly what we need: the splitx command. With that, we do this (based on #tensaix2j's answer):
#!/usr/bin/expect -f
package require Expect;   # Good practice to put this explicitly
package require textutil::split; # Part of Tcllib
# ... add your stuff here ...
foreach line [textutil::split::splitx [lindex $argv 0] {\\n}] {
send "$line\r"
# Wait for response and/or prompt?
}
# ... add your stuff here ...
If you don't have Tcllib installed and configured for use with Expect, you can also snarf the code for splitx directly out of the code (find it online here) as long as you internally acknowledge the license it's under (standard Tcl licensing rules).

foreach cmd [ split $command \n ] {
send "$cmd\r\n"
}

Related

"\r" is ignored in TCL Expect in Cygwin

I have a tcl script to access serial port through Expect in Cygwin. I noticed the \r is just ignored causing serial console no reply.
spawn ./plink.exe -serial COM$priuart -sercfg 115200,8,n,1,N
set id $spawn_id
set timeout 30
log_user 1
exp_send -i $id "\r"
expect -i $id -re ".*>" {exp_send -i $id "sys rev\r"}
expect -i $id -re ".*>" {set temp $expect_out(buffer)
Please note that the similar issue was resolved in Cygwin by adding -o igncr. However, calling tcl script the issue existed still.
Any thoughts?
The exact thing you need to exp_send to simulate the pressing of the Return key can vary; the \r (a carriage return) is the right thing for classic Unix systems, but might not be correct in your case, especially since you're ending up talking to a serial line (which can add its own layer of complexity). It's entirely possible that you'll need to send either \n (a newline character) or \r\n (carriage-return/newline sequence) instead. The simplest thing is for you to experiment and see what works.
Don't forget when changing things to change it in all the places where it is used.
Also be aware that Tcl can talk to serial lines directly, and spawn told to use an already opened channel. This might work better for you…
# The name *is* magical, especially for larger port numbers
set channel [open \\\\.\\com$priuart r+]
fconfigure -mode 115200,n,8,1 -buffering none
spawn -open $channel

Tcl expects output from tclsh to shell script

I'm searching for last few days for solution to my problem so finally I came here.
I got to write a Tcl script that should be opened from a Linux/Debian system. The problem is, that the script should log into a Cisco router and then go into the special tclsh mode, where it should get the SNMP value which represents txload. After a moment I get the value in tclsh mode (which supports SNMP), but I can't get it back to my script. It still exists only in router tclsh mode. My script should change some other values depending on the value I already have in tclsh, but tclsh is not needed anymore.
So here's my question: How can I get the $ifnum value from tclsh but I could still use in that script for some loops, because I need to make some loops where the $ifnum will be the condition?
As you can see, the script is not finished yet, but as I said, I'm stuck here and trying to figure it out:
#!/usr/bin/expect -f
set gateway [lindex $argv 0]
set serial [lindex $argv 1]
set timeout 5
spawn telnet $gateway
expect "Password:"
send "cisco\r"
expect "*>"
send "en\r"
expect "Password:"
send "cisco\r"
expect "*#"
send "set ifnumstr \[snmp_getone odczyt 1.3.6.1.4.1.9.2.2.1.1.24.6\]\r"
expect "*}"
send "regexp \{val='(\[0-9\]*)'\} \$ifnumstr \{\} ifnum \r"
expect "*#"
send "puts \$ifnum \r"
expect "*#"
I'd make it print out the thing you're looking for between markers, and then use expect in its RE matching mode to grab the value.
send "puts \"if\\>\$ifnum<if\"\r"
expect -re {if>(.*?)<if}
# Save the value in a variable in the *local* script, not the remote one
set ifnum $expect_out(1,string)

Passing variable to `expect` in bash array

I am trying to use a FOR loop to iterate over IP addresses (in a bash array), logs in, runs a script and then exits. The array is called ${INSTANCE_IPS[#]}. The following code doesn't work though, as expect doesn't seem to be able to accept the variable $instance.
for instance in ${INSTANCE_IPS[#]}
do
echo $instance
/usr/bin/expect -c '
spawn ssh root#$instance;
expect "?assword: ";
send "<password>\r";
expect "# ";
send ". /usr/local/bin/bootstrap.sh\r";
expect "# ";
send "exit\r" '
done
However, expect complains with:
can't read "instance": no such variable
while executing
"spawn ssh root#$instance"
There is another question on stackoverflow located here, that uses environmental variables to achieve this, however it doesn't allow me to iterate through different IP addresses like I can in an array.
Any help is appreciated.
Cheers
The problem is with quoting. Single quotes surrounding the whole block don't let Bash expand variables ($instance).
You need to switch to double quotes. But then, double quotes inside double quotes are not allowed (unless you escape them), so we are better off using single quotes with expect strings.
Try instead:
for instance in ${INSTANCE_IPS[#]}
do
echo $instance
/usr/bin/expect -c "
spawn ssh root#$instance;
expect '?assword: ';
send '<password>\r';
expect '# ';
send '. /usr/local/bin/bootstrap.sh\r';
expect '# ';
send 'exit\r' "
done
for instance in ${INSTANCE_IPS[&]} ; do
echo $instance
/usr/bin/expect -c '
spawn ssh root#'$instance' "/usr/local/bin/bootstrap.sh"
expect "password:"
send "<password>\r"
expect eof'
done
From the ssh man page:
If command is specified, it is executed on the remote host instead of a login shell.
Specifying a command means expect doesn't have to wait for # to execute your program, then wait for another # just to send the command exit. Instead, when you specify a command to ssh, it executes that command; it exits when done; and then ssh automatically closes the connection.
Alternately, put the value in the environment and expect can find it there
for instance in ${INSTANCE_IPS[&]} ; do
echo $instance
the_host=$instance /usr/bin/expect -c '
spawn ssh root#$env(the_host) ...
Old thread, and one of many, but I've been working on expect for several days. For anyone who comes across this, I belive I've found a doable solution to the problem of passing bash variables inside an expect -c script:
#!/usr/bin/env bash
password="TopSecret"
read -d '' exp << EOF
set user "John Doe"
puts "\$user"
puts "$password"
EOF
expect -c "$exp"
Please note that escaping quotations are typically a cited issue (as #Roberto Reale stated above), which I've solved using a heredoc EOF method, before passing the bash-variable-evaluated string to expect -c. In contrast to escaping quotes, all native expect variables will need to be escaped with \$ (I'm not here to solve all first-world problems--my afternoon schedule is slightly crammed), but this should greatly simplify the problem with little effort. Let me know if you find any issues with this proof of concept.
tl;tr: Been creating an [expect] daemon script with user authentication and just figured this out after I spent a whole day creating separated bash/expect scripts, encrypting my prompted password (via bash) with a different /dev/random salt each iteration, saving the encrypted password to a temp file and passing the salt to the expect script (highly discouraging anyone from easily discovering the password via ps, but not preventative since the expect script could be replaced). Now I should be able to effectively keep it in memory instead.

Shebang causes script to fail

I'm quite bad at bash, and I try to make a script to connect to all my switches with openSSH in order to make some configuration.
I created an array containing all my 25 switches, and then I used a loop to open SSH connection with each of them.
As I'm on Windows and using bash, I've just installed Cygwin.
However, I had to use expect and writing my password in plain text as the switches are quite poor and that is the best way for me (I won't manually put my RSA key on every single switch as it would take me as much time as writing manually the configuration on every switch).
I use the shebang #!/usr/bin/expect -f to make bash recognize expect. When I do this, the expect syntax (spawn, expect, interact) works perfectly, but my array doesn't work.
I get the following error message:
extra characters after close-quote
while executing "arrayname=("172.21.21.20" "172.20.55.55" ... "
When I change the shebang, and use #!/bin/bash, expect is not found anymore :
./stationsnmp.sh: line 20: spawn : command not found couldn't read
./stationsnmp.sh: line 24: send : command not found couldn't read
file "assword": no such file or directory ./stationsnmp.sh: line 27:
send : command not found ./stationsnmp.sh: line 28: interact :
command not found
I'm really not a pro in bash, which explains I can't get this little problem... Some help would be welcome.
EDIT : Below is a part of my code
#!/bin/bash
switch=("172.20.0.229" "172.20.0.232" "172.20.0.233" "172.21.0.15" "172.21.0.16" "172.21.2.1" "172.20.2.250" "172.21.3.1" "172.20.3.250" "172.21.4.1" "172.20.4.250" "172.21.6.1" "172.20.6.250" "172.21.7.1" "172.20.7.250" "172.21.8.1" "172.20.8.250" "172.20.9.250" "172.21.9.1" "172.21.10.1" "172.20.10.250" "172.21.11.1" "172.20.11.250" "172.21.12.1" "172.21.12.250")
nmb=`echo ${#switch[#]}`
set timeout 3
for ((ii=0; ii<=$nmb; ii++))
#for ii in {0..${#switch[#]}}
do
if [ ${switch[$ii]:5:1} -eq 1 ]
then
ipdc=`echo ${switch[ii]} | grep -o -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.'`"10"
spawn ssh admin#switch[$ii]
expect "*assword*"
send "PASS\r"
interact
exit
fi
done
You are mixing bash and expect, those are two entirely different languages. You probably want to have a bash wrapper script with proper option handling (see getopts) which takes a list of IP addresses and execute your expect script for each IP address passed to your bash-wrapper. If your expect script is small you might want to embed it into your shell script as opposed to having it in a separate file.
EDIT:
#!/bin/bash
switches=("172.20.0.229" "172.20.0.232")
for ip in "${switches[#]}"; do
expect "${ip}" <<-'EOT'
set host [lindex $argv 0]
set timeout 3
spawn ssh -l admin $host
expect "*assword*"
send "PASS\r"
interact
exit
EOT
done

parse information from expect command

I need to parse resulting data from a telnet/ssh command and act on the data.
As an example, I want to interact with a spawn session (ssh here), list files in current dir and collect only file of a certain extension to later execute a command on those files only.
What I've got so far:
#!/usr/bin/expect
set timeout 3
match_max 10000
set prompt {$ }
spawn ssh $user#$host
expect "password: "
send $pw\r
expect $prompt
# here's the command I need to parse resulting data
send "ls -1\r"
expect -re {(.*)\.log} {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue
}
That script opens a ssh session, sends the command and displays all the files in current dir (log and others) but I need to process each file matching a given pattern, how can I do this?
script output:
$ DATA:
0_system.log
1_system.log
2_system.log
3_system.log
a.log
a.sh
blah
b.sh
data.csv
First of all... wouldn't ls -1 *.log be easier, and do the trick?
That said, I have found that usually you have to be very careful when using (.*), it can have at times unexpected results.
I would go with "any alphanumeric character plus underscore" (if that works for your filenames) - see my suggested expect block below. Also, keep in mind that by using $expect_out(1,string) you are saving only the filename, without the extension - not sure if that is what you want. If you want the whole thing, $expect_out(0,string) is the way to go in this case.
This will do it:
expect -re "(\[a-zA-Z0-9_\]*)\.log" {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue
}
Hope that helps!
James
Corrected Expect probably should be:
expect {
-re {(.*)\.log} {
set val $expect_out(1,string)
puts "LOG file: $val"
exp_continue;
}
}

Resources