How to create sh script with expect? - embedded

I'm trying to create a sh script using expect script for embedded system (I don't want to change firmware to include this script). So, I have the following script, which doesn't work because of wrong usage of val in if-block:
#!/usr/bin/env expect
set timeout 20
set ipaddr [lindex $argv 0]
spawn telnet $ipaddr
expect "soc1 login: "
send "root\n"
expect "prompt # "
send "val=`some_command`\n"
expect "prompt # "
send "if [ \$val -eq 0 ]; then echo Good; fi\n"
# its here ^^^^^
expect "prompt # "
send "exit\n"
interact
I've tried to use $$ and it doesn't help.
How to fix this script to allow usage of variables inside sh script?

The problem of the script is in the interpretation by Tcl of [ and ] - these brackets should be escaped:
send "if \[ \$val -eq 0 \]; then echo Good; fi\n"
# its here ^^^^^

Related

Unable to accept multiple commandline arguments into one variable within expect

Using cygwin 32bit, expect v5.45 (what comes with the latest cygwin, it seems).
COMMAND="$WIN_BUILD_ROOT\\scripts\\signBinaries.bat $BUS $NET_DRIVE $WIN_SIGNING_ROOT"
$CLIENT_BUILD_ROOT/scripts/runCommand.sh $arg1 $arg2 $arg3 $arg4 $COMMAND
runCommand.sh:
#!C:\cygwin\bin\expect.exe -f
set timeout 9
set arg1 [lindex $argv 0]
set arg2 [lindex $argv 1]
set arg3 [lindex $argv 2]
set arg4 [lindex $argv 3]
set COMMAND [lrange $argv 4 end]
send -- "$COMMAND\r"
Gives me:
{s:\git\builds\scripts\signBinaries.bat} 64 s {s:\git\builds\}
The filename, directory name, or volume label syntax is incorrect.
The scenario is, the first four arguments are fixed. There could then be a variable number of commands following those, which I want to have as one command to be executed by expect. If I just use [lindex argv 4], I only get the signBinaries script name, whether or not it's enclosed in quotes. Using lrange (found via googling) instead, it's enclosing the string arguments in braces, as shown. Why would it be modifying my arguments in this fashion and how do I fix it so that $COMMAND contains the command as I intended it to be?
In Tcl, which expect extends, you have to be aware of the datatype of your variables: is it a string or a list? When a list gets stringified, it may quote some of its elements if they contain "metacharacters" (like backslash and braces).
In most cases when you want to use the contents of a list as a single string, it's best to stringify it yourself:
set COMMAND [list "s:\\git\\builds\\scripts\\signBinaries.bat" 64 s "s:\\git\\builds\\"]
puts $COMMAND
# => {s:\git\builds\scripts\signBinaries.bat} 64 s s:\\git\\builds\\
puts [join $COMMAND " "]
# => s:\git\builds\scripts\signBinaries.bat 64 s s:\git\builds\
Aha! Figured it out:
set COMMAND [join [lrange $argv 4 end]]
Gives me:
s:\git\builds\scripts\signBinaries.bat 64 s s:\git\builds
"BUS: 64"
"DRIVE: s"
"WORKSPACE: s:\git\builds"

Expect - Use default variable if no argument is specified

I've searched on stackoverflow and haven't really found an answer to this.
I'm pretty new to scripting and I have created a fully functional Expect script but I would like to improve it a bit. Currently I have created 3 lindex values of argv 0, 1 and 2 for hostaddress, username and password.
I would like to create a default username and password if argv 1 and 2 is NOT specified. I tried solving this through some if statements but after searching through stackoverflow it seems that TCL/Expect does not support NULL or empty values. Instead you have to make a query for it. Currently my code looks like this:
#!/usr/bin/expect
#Variables
set HOSTADDRESS [lindex $argv 0]
set USER [lindex $argv 1]
set PASSWORD [lindex $argv 2]
spawn ssh $USER#$HOSTADDRESS
set timeout 100
expect {
"(yes/no)?" {send "yes\n"; exp_continue}
"assword:" {send "$PASSWORD\n"}
}
expect {
"%" {send "cli\r"; exp_continue}
">" {sleep 1}
}
send "show interfaces st0 terse | match st0. | count \r"
expect "Count:???"
puts [open $HOSTADDRESS.op5.vpn.results w] $expect_out(0,string)
expect ">"
send "exit\r"
expect {
"%" {send "exit\r"; exp_continue}
"closed." {exit}
}
exit
Can you guys please help me create a default variable for $USER and $PASSWORD if it's not specified in the argv 1 or argv2?
Tcl doesn't support NULL at all. Or rather, it actually maps it to the variable being unset (that's exactly what happens with local variables under the hood; global variables are different). To query whether a variable exists, you use info exists (and yes, that's actually a NULL check in its implementation).
However, for handling defaulting of values from users on the command line, it is better to do it like this:
proc parseArgv {hostAddress {user "TheDefaultUser"} {pass "TheDefaultPassword"}} {
variable ::HOSTADDRESS $hostAddress
variable ::USER $user
variable ::PASSWORD $pass
}
parseArgv {*}$argv
If you're using 8.4 (upgrade, man!) then replace that last line with:
eval parseArgv $argv
You could also do it by looking at the llength of $argv (or the value in $argc) and doing conditional stuff based on that, but leveraging Tcl's proc default argument value stuff is easier (and you even get a reasonable error when someone gives too few or too many arguments).

Running shell commands in background, in a tcl proc

I'm trying to create a tcl proc, which is passed a shell command as argument and then opens a temporary file and writes a formatted string to the temporary file, followed by running the shell command in background and storing the output to the temp file as well.
Running the command in background, is so that the proc can be called immediately afterwards
with another arg passed to it, writing to another file. So running a hundred such commands should not take as long as running them serially would do. The multiple temp files can finally be concatenated into a single file.
This is the pseudocode of what I'm trying to do.
proc runthis { args }
{
set date_str [ exec date {+%Y%m%d-%H%M%S} ]
set tempFile ${date_str}.txt
set output [ open $tempFile a+ ]
set command [concat exec $args]
puts $output "### Running $args ... ###"
<< Run the command in background and store output to tempFile >>
}
But how do I ensure the background'ing of the task is done properly? What would need to be done to ensure that the multiple temp files get closed properly?
Any help would be welcome. I'm new at tcl and finding to get my mind around this. I read about using threads in tcl but I'm working with an older version of tcl which doesn't support threading.
How about:
proc runthis { args } {
set date_str [clock format [clock seconds] -format {+%Y%m%d-%H%M%S}]
set tempFile ${date_str}.txt
set output [ open $tempFile a+ ]
puts $output "### Running $args ... ###"
close $output
exec {*}$args >> $tempFile &
}
See http://tcl.tk/man/tcl8.5/TclCmd/exec.htm
Since you seem to have an older Tcl, replace
exec {*}$args >> $tempFile &
with
eval exec [linsert $args 0 exec] >> $tempFile &

Expect script does not work under crontab

I have an expect script which I need to run every 3 mins on my management node to collect tx/rx values for each port attached to DCX Brocade SAN Switch using the command #portperfshow#
Each time I try to use crontab to execute the script every 3 mins, the script does not work!
My expect script starts with #!/usr/bin/expect -f and I am calling the script using the following syntax under cron:
3 * * * * /usr/bin/expect -f /root/portsperfDCX1/collect-all.exp sanswitchhostname
However, when I execute the script (not under cron) it works as expected:
root# ./collect-all.exp sanswitchhostname
works just fine.
Please Please can someone help! Thanks.
The script collect-all.exp is:
#!/usr/bin/expect -f
#Time and Date
set day [timestamp -format %d%m%y]
set time [timestamp -format %H%M]
#logging
set LogDir1 "/FPerf/PortsLogs"
et timeout 5
set ipaddr [lrange $argv 0 0]
set passw "XXXXXXX"
if { $ipaddr == "" } {
puts "Usage: <script.exp> <ip address>\n"
exit 1
}
spawn ssh admin#$ipaddr
expect -re "password"
send "$passw\r"
expect -re "admin"
log_file "$LogDir1/$day-portsperfshow-$time"
send "portperfshow -tx -rx -t 10\r"
expect timeout "\n"
send \003
log_file
send -- "exit\r"
close
I had the same issue, except that my script was ending with
interact
Finally I got it working by replacing it with these two lines:
expect eof
exit
Changing interact to expect eof worked for me!
Needed to remove the exit part, because I had more statements in the bash script after the expect line (calling expect inside a bash script).
There are two key differences between a program that is run normally from a shell and a program that is run from cron:
Cron does not populate (many) environment variables. Notably absent are TERM, SHELL and HOME, but that's just a small proportion of the long list that will be not defined.
Cron does not set up a current terminal, so /dev/tty doesn't resolve to anything. (Note, programs spawned by Expect will have a current terminal.)
With high probability, any difficulties will come from these, especially the first. To fix, you need to save all your environment variables in an interactive session and use these in your expect script to repopulate the environment. The easiest way is to use this little expect script:
unset -nocomplain ::env(SSH_AUTH_SOCK) ;# This one is session-bound anyway
puts [list array set ::env [array get ::env]]
That will write out a single very long line which you want to put near the top of your script (or at least before the first spawn). Then see if that works.
Jobs run by cron are not considered login shells, and thus don't source your .bashrc, .bash_profile, etc.
If you want that behavior, you need to add it explicitly to the crontab entry like so:
$ crontab -l
0 13 * * * bash -c '. .bash_profile; etc ...'
$

using command line argument while using expect

Hi I am using expect to mput a file to a remote machine.I am passing the filename as a command line argument to the script.But error is throwing from the line
send -- "mput $1\r"
My code is as follows:
set timeout 1000
spawn ftp $ipaddress
expect "Name "
send -- "$username\r"
expect "Password"
send -- "$passwd\r"
expect "ftp>"
send -- "mput $1\r"//error thrown from this line
expect "mput $1? "
send -- "y\r"
expect "ftp>"
send -- "bye\r"
Could anyone please suggest what is wrong here?
Replace $1 with [lindex $argv 0]. The $argc variable tells the number of given arguments.