Bash parameter expansion inside expect - expect

What I'm trying to do:
./script 192.168.1.{1..100}
#!/bin/expect -f
set servers_ip [lindex $argv 0]
set servers_port [lindex $argv 1]
set timeout -1
foreach ip $servers_ip {
puts "\nIP = $ip"
}
expected output:
IP = 192.168.1.1
IP = 192.168.1.2
IP = 192.168.1.3
Actual output:
IP=192.168.1.1
I just can't make the parameter expansion work, and I CANNOT use external files.

Your shell has already expanded the brace expansion before launching expect. The first 100 elements of $argv are individual IP addresses.
Here, you want
set servers_ip $argv
then, iterating over them should give you what you want

Related

Import expect script into existing expect script

Is it possible to import common expect functions into another expect script?
The use case is I have a server accessed via serial console port, so there is a long list of steps to connect and access the serial console. There are several expect scripts that all need this initial setup and login. Ie a sample script (ignoring the serial connect steps) might be:
#!/usr/bin/expect
set host [lindex $argv 0]
set user [lindex $argv 1]
set pass [lindex $argv 2]
spawn ssh $user#$host
set timeout 5
# Login to host
expect {
"assword:" {
send -- "$pass\r"
}
}
expect {
// DO OTHER EXPECT STUFF
}
Is it possible to put the login into a common file that is imported/included in every script to setup the initial shell connection and login?
login.exp:
# Login to host
expect {
"assword:" {
send -- "$pass\r"
}
}
foo.exp:
#!/usr/bin/expect
set host [lindex $argv 0]
set user [lindex $argv 1]
set pass [lindex $argv 2]
spawn ssh $user#$host
set timeout 5
####include login.exp
expect {
// DO OTHER EXPECT STUFF
}

How to do string substitution in expect?

Say I have the following variable s:
set s [lindex $argv 0]
How can I make sure s does not contain any "-" characters?
So, basically I want to replace all occurrences of "-" in s with "".
How can I achieve it?
Use set s [string map { - {} } [lindex $argv 0]]
As pynexj says, details can be found at http://www.tcl-lang.org/man/tcl/TclCmd/string.htm#M34

Expect script not executing after ssh login

I'm trying to write an expect script to:
Connect to a remote server providing the user and password
Loop through a local file reading each line
Execute a specific command on the remote server for each of those lines
I could successfully achieve the step #1 and was testing the #3 with a simple scenario, but couldn't make it work yet. Unfortunately in the line 8 of the script, after sending the password, I just get logged into the server as I would have been logged manually (I can interact with the console) and the rest is not executed.
How can I circumvent this problem?
This is the script:
#!/usr/bin/expect
set timeout 20
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh -t -t "$user\#$ip"
expect "Password:"
send "$password\r";
expect "NYXOOBPN402(config)$"
send "delete decoders:ASX-Trade24-FIX.mdp\r"
expect "Are you sure you want to delete 'decoders:ASX-Trade24-FIX.mdp' (y/n)?"
send "y\r";
And this is how I'm executing it:
./test_expect.sh 172.18.250.20 admin admin
The problem is that my expect was incorrect in the line 17. Instead of "NYXOOBPN402(config)$", I should just put "*(config)*", since there's a lot of text before this part that was not being matched.
This is my final script for someone that runs into this same issue:
#!/usr/bin/expect
set timeout 9
# Check if the parameters are correct
if {[llength $argv] == 0} {
send_user "Usage: ./test_expect.sh ip username password\n"
exit 1
}
# Read the file with all the decoders names to be deleted
set f [open "decoders.txt"]
set decoders [split [read $f] "\n"]
close $f
# debug mode - very useful:
#exp_internal 1
# Connect to the server
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh -t "$user\#$ip"
expect "Password: "
send "$password\r";
sleep 3
# send ctrl+c since this terminal shows a lot of decoders
#send \x03
expect {
default { send_user "\nCould not find the expected value.\n"; exit 1 }
"*(config)*" {
# Loop through all the decoders
foreach decoder $decoders {
#send_user "Removing $decoder\n"
send "delete decoders:$decoder\r"
expect {
"Are you sure you want to delete*" { send "y\r" }
"*decoder will still be active*" { send_user "\nRemoved $decoder successfully\n" }
"*no such file or directory" { send_user "\nDecoder $decoder already deleted.\n" }
default { send_user "\nNot expected value with $decoder, please debug.\n"; exit 1 }
}
}
}
}

How to pass multiple arguments to foreach from a file

I have a file which contains a list of Servers ( space ) User and now I want to pass this file as an argument to my expect script so that my script will spawn an ssh session to user#server and execute a bunch of commands and exit.
cat HostsUserFile.txt
Server1 User1
Server2 User2
Server3 User3
cat CollectStats.exp
### Get the list of hosts, one per line, whereas hosts.txt should be a file containing the list of servers and user #####
set password ****
set f [open "HostsUserFile.txt"]
set hosts [split [read -nonewline $f] "\n"]
close $f
### Loop through the hosts listed in host.txt ###
foreach { host user } $hosts {
### spawn ssh process ###
spawn -noecho ssh -q $user#$host -o StrictHostKeyChecking=no
expect "*?assword*"
send "$password\r"
expect "*$*"
send "exit\r"
}
I want Server1 to be substituted to host and User1 to be substituted to user variable during 1st iteration and so on.
Please help me to achieve this. Thanks.
Since the variable hosts is a list of list, (i.e.{Server1 User1} {Server2 User2} {Server3 User3}) you should use single variable for the foreach loop and inside that you can extract the items as
foreach hostinfo $hosts {
# If your 'Tcl' version is 8.5 or above, use lassign as,
# lassign $hostinfo server user
# Else, use 'lindex' to get this done
set server [lindex $hostinfo 0]
set user [lindex $hostinfo 1]
puts "Server : $server , User : $user"
# Your further code on ssh
}

TCL/Expect equivalent of Bash $# or how to pass arguments to spawned process in TCL/Expect

If somebody wants to call external program (which was passed as a Bash argument) from Bash and also pass it command line options (which were also passed as a Bash arguments) the solution is fairy simple:
TCL_SCRIPT="$1"
shift
TCL_SCRIPT_ARGUMENTS="$#"
expect -f "$TCL_SCRIPT" "$TCL_SCRIPT_ARGUMENTS" 2>&1
Is something similar possible in TCL/Expect ?
EDIT:
So far I've come with this hack (there are Bash equivalents in comments), which seems that it is working. Can somebody explain lshift procedure?
# http://wiki.tcl.tk/918#pagetocc7993a2b
proc lshift {inputlist} {
upvar $inputlist argv
set arg [lindex $argv 0]
#set argv [lrange $argv 1 end] ;# below is much faster - lreplace can make use of unshared Tcl_Obj to avoid alloc'ing the result
set argv [lreplace $argv[set argv {}] 0 0]
return $arg
}
# BASH: TCL_SCRIPT="$1"
set script [lindex $argv 0]
# BASH: shift
lshift argv
# BASH: TCL_SCRIPT_ARGUMENTS="$#"
set arguments $argv
To literally translate your example
set program [lindex $argv 0]
set arguments [lrange $argv 1 end]
spawn $program {*}$arguments
{*} is Tcl's "list expansion" syntax (rule 5 of Tcl's 12 rules of syntax). It splits a list into its element in the current command.
If $argv is foo bar baz, then
spawn [lindex $argv 0] [lrange $argv 1 end]
will invoke foo with 1 argument: "bar baz"
spawn [lindex $argv 0] {*}[lrange $argv 1 end]
will invoke foo with 2 arguments: "bar" and "baz"
Tangentially, I would code your lshift proc like this:
proc lshift {varname} {
upvar 1 $varname var
set var [lassign $var first]
return $first
}
Then:
expect1.6> set argv {foo bar baz}
foo bar baz
expect1.7> set script [lshift argv]
foo
expect1.8> set script
foo
expect1.9> set argv
bar baz
I found #glenn jackman's answer to be insufficient for older TCL, namely versions before 8.5, and especially for older Unicies running Expect scipts as well. I went searching and found a note that the "lassign" function is not available in TCL before 8.5: (http://wiki.tcl.tk/1530 , in section "In Tcl prior to 8.5, foreach was used to achieve the functionality of lassign:").
On that same page, under the heading "Example: Perl-ish shift", there is a sentance stating "On the other hand, Hemang Lavana observes that TclXers already have lvarpop ::argv, an exact synonym for shift.") that redirects to this page for "lvarpop": http://wiki.tcl.tk/1965
The code at the end of that page is a simplified version, reproduced here for convenience:
proc lvarpop {upVar {index 0}} {
upvar 1 $upVar list
if {![info exists list]} { return "-1" }
set top [lindex $list $index]
set list [lreplace $list $index $index]
return $top
}
In my testing, this works for zero to practically an infinite number of space-separated args. For the TCL-challenged such as myself, this is a godsend.

Resources