This is my simple expect script:
#!/usr/local/bin/expect
puts "Argu 1 : [lindex $argv 0]"
Run
---
expect example.expt Testing
Output
------
Argu 1: Tesing
I need to count the arguments, which are passed by command line. Like this:
expect example.expt 1 2 3 4
I need the script for the following output
Total: 4 arguments
Arg1: 1
Arg2: 2
Arg3: 3
Arg4: 4
How can I do this? Thanks!
expect uses Tcl as its scripting language. You're looking for the llength command:
puts "Total: [llength $argv] argument(s)"
Late but will be useful i guess.
This prints the number of command line arguments and the arguments list as well.
#!/usr/bin/expect -f
puts "No of Command Line Arguments : [llength $argv]"
puts "Arguments List "
set argsCount [llength $argv];
set i 0
while {$i < $argsCount } {
puts [lindex $argv $i]
set i [expr $i+1];
}
More details about expect scripting can be found here
Related
I tried posting this in the codereview community, but there is no expect tag and I don't have enough karma to create tags.
I have written an expect script to either login to a server or run a simple (usually single) command and return the output.
I have two problems and a wish.
Commands that return nothing--i.e., ssh2server user host false--time out with an error (because I'm not capturing a timeout, though I suppose I should) instead of just returning nothing.
I can capture the return code of the program but I can't get it to exit with the appropriate code.
Is there a way I can take the output of the called program and return it the same way (remote stdout goes to local stdout and remote stderr goes to local stderr)?
Also, any comments or (constructive) criticisms would be appreciated.
#!/usr/bin/expect -f
if {[info exists ::env(SSH2SERVER_PASSWORD)]} {
set password "$env(SSH2SERVER_PASSWORD)"
} else {
puts "SSH2SERVER_PASSWORD not set"
exit 1
}
if {[llength $argv] < 2} {
puts "usage: ssh2server user server"
exit 1
}
set user [lindex $argv 0]
set server [lindex $argv 1]
set command [lrange $argv 2 end]
set pwd_prompt "*assword:"
set prompt "\$ "
set rc 0
expect_before {
#timeout { send_user 'timeout' ; exit 2 }
timeout { send_user 'timeout' ; set rc 2}
}
log_user 0
spawn ssh $user#$server
expect "$pwd_prompt" { send -- "$password\r" }
if { $command == "" } {
interact
} else {
expect {
"$prompt" {
send -- "PROMPT_COMMAND=\rPS1='_MYPROMPT_'\r$command\r"
#expect -re "$command\r\n(.*)\r\n\[^\r]*\[#\$%]"
expect -re "$command\r\n(.*)\r\n\[^\r]*_MYPROMPT_"
set results $expect_out(1,string)
puts $results
send -- "^D"
expect eof
#catch wait ec
#set rc [lindex $ec 3]
#puts [lindex $ec 3]
#exit [lindex $ec 3]
}
#eof { send_user $expect_out(buffer); exit 3}
eof { send_user $expect_out(buffer); set rc 3}
}
}
log_user 1
lassign [wait] pid spawnid os_flag rc
#puts $rc # outputs correct value
exit $rc
I suspect this is the problem:
send -- "^D"
You are not sending a Ctrl-D, you are sending the characters ^ and D.
To send a Ctrl-D
send -- "\04"
To solve the "no output, timeout" problem, you need to alter your expected regex: you have too many newlines for that case. Using expect -d would have revealed this to you. Like this:
send -- "unset PROMPT_COMMAND; PS1='_MYPROMPT_'\r"
expect -re "_MYPROMPT_$"
send -- "$command\r"
expect -re "$command(.*)\r\n_MYPROMPT_$"
The content of the capturing parentheses may now be empty.
I split off setting the prompt for clarity.
To capture the exit status of the command, you may have to do this:
send -- "$command; echo $?\r"
expect -re "$command(.*)\r\n(\d+)\r\n_MYPROMPT_$"
set results [regsub {^\r\n} $expect_out(1,string) ""]
set status $expect_out(2,string)
I don't think you can separate stdout and stderr with the expect command. I think both streams are captured as "output". (I don't have my Exploring Expect book nearby to confirm)
If that's important, you might want to invoke the command redirecting stdout and/or stderr to file(s), and then cat and capture the file contents.
Does anyone know a standard package for tcl to easily parse the input arguments ? or a ready proc ? ( I have only 3 flags but something general is preferable ).
The documentation includes an example. Here is a simple example:
package require cmdline
set parameters {
{server.arg "" "Which server to search"}
{debug "Turn on debugging, default=off"}
}
set usage "- A simple script to demo cmdline parsing"
array set options [cmdline::getoptions ::argv $parameters $usage]
parray options
Sample runs:
$ tclsh simple.tcl
options(debug) = 0
options(server) =
$ tclsh simple.tcl -server google.com
options(debug) = 0
options(server) = google.com
$ tclsh simple.tcl -server google.com -debug
options(debug) = 1
options(server) = google.com
$ tclsh simple.tcl -help
simple - A simple script to demo cmdline parsing
-server value Which server to search <>
-debug Turn on debugging, default=off
-help Print this message
-? Print this message
while executing
"error [usage $optlist $usage]"
(procedure "cmdline::getoptions" line 15)
invoked from within
"cmdline::getoptions ::argv $parameters $usage"
invoked from within
"array set options [cmdline::getoptions ::argv $parameters $usage]"
(file "simple.tcl" line 11)
Discussion
Unlike most Linux utilities, TCL uses single dash instead of double dashes for command-line options
When a flags ends with .arg, then that flag expects an argument to follow, such as in the case of server.arg
The debug flag does not end with .arg, therefore it does not expect any argument
The user defines the command-line parameters by a list of lists. Each sub-list contains 2 or 3 parts:
The flag (e.g. debug)
The default value (e.g. 0), only if the parameter takes an argument (flag ends with .arg).
And the help message
Invoke usage/help with -help or -?, however, the output is not pretty, see the last sample run.
Update: Help/Usage
I have been thinking about the message output when the user invoke help (see the last sample run above). To get around that, you need to trap the error yourself:
set usage "- A simple script to demo cmdline parsing"
if {[catch {array set options [cmdline::getoptions ::argv $parameters $usage]}]} {
puts [cmdline::usage $parameters $usage]
} else {
parray options
}
Sample run 2:
$ tclsh simple.tcl -?
simple - A simple script to demo cmdline parsing
-server value Which server to search <>
-debug Turn on debugging, default=off
-help Print this message
-? Print this message
Tcllib has such a package, cmdline. It's a bit underdocumented, but it works.
Here is a simple, native, no-package argument parser:
#
# arg_parse simple argument parser
# Example `arg_parse {help version} {with-value} {-with-value 123 positional arguments}`
# will return:
# `positionals {positional arguments} with-value 123`
#
# #param boolean_flags flags which does not requires additional arguments (like help)
# #param argument_flags flags which requires values (-with-value value)
# #param args the got command line arguments
#
# #return stringified array of parsed arguments
#
proc arg_parse { boolean_flags argument_flags args } {
set argsarr(positionals) {}
for {set i 0} {$i < [llength $args]} {incr i} {
set arg [lindex $args $i]
if { [sstartswith $arg "-" ] } {
set flag [string range $arg 1 end]
if { [lsearch $boolean_flags $flag] >= 0 } {
set argsarr($flag) 1
} elseif { [lsearch $argument_flags $flag] >= 0 } {
incr i
set argsarr($flag) [lindex $args $i]
} else {
puts "ERROR: Unknown flag argument: $arg"
return
}
} else {
lappend argsarr(positionals) $arg
}
}
return [array get argsarr]
}
USE argument parser
#
# USE argument parser:
#
proc my_awesome_proc { args } {
array set argsarr [arg_parse "help version" "with-value" {*}$args]
parray argsarr
}
USE my_awesome_proc :
% my_awesome_proc -help
argsarr(help) = 1
argsarr(positionals) =
% my_awesome_proc -with-value 123
argsarr(positionals) =
argsarr(with-value) = 123
% my_awesome_proc -wrong
ERROR: Unknown flag argument: -wrong
% my_awesome_proc positional arguments
argsarr(positionals) = positional arguments
%
I need a script to modify the same data in multiple servers. For now the for loop generate the command lines, but i'm experiencing some problems with expect and pssh.
The for loop:
<code>
for ((var1=1;var1<=14; var1++))
{
cda stm add "$var1/$var2/$var3" ss 1
for ((var2=1;var2<=8;var2++))
{
cda stm add "$var1/$var2/$var3" ss 1
for ((var3=1; var3<64; var3++))
{
cda stm add "$var1/$var2/$var3" ss 1
}
}
}
</code>
I'm using pssh instead ssh in expect script.
The full code:
<code>
#!/usr/bin/expect
set timeout 20
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
for ((var1=1;var1<=14; var1++))
{
cda stm add "$var1/$var2/$var3" ss 1
for ((var2=1;var2<=8;var2++))
{
cda stm add "$var1/$var2/$var3" ss 1
for ((var3=1; var3<64; var3++))
{
cda stm add "$var1/$var2/$var3" ss 1
}
}
}
spawn pssh "$user\#$ip"
expect "yes/no" {
send "yes\r"
expect "*?assword" { send "[lindex $argv 2]\r" }
} "*?assword" { send "[lindex $argv 2]\r" }
expect "SH"
interact
</code>
I'm getting the following error:
<code>
wrong # args: should be "for start test next command"
while executing
"for ((var1=1"
(file "./ssh" line 9)
</code>
for loops in expect and Tcl have to look like this:
for {set var1 1} {$var1<=14} {incr var1} {
commands...
}
In other words, the for command requires four arguments: the start code, the condition, the "next" code and the loop body. Note that newlines are command separators in Tcl, so the open brace for the loop body must be on the same line as the for command (or you must use a backslash-newline line continuation).
You used bash/ksh syntax instead.
Apart from the syntax error in for loop, there's a logical problem with the code snippet in the question:
var2 isn't available for printing in the outermost loop (for the first time).
var3 isn't available for printing in the 2nd loop (for the first time).
Here's probably what you need:
for {set var1 1} {$var1 <=14} {incr var1} {
puts "LEVEL_1: $var1"
# cda stm add "$var1" ss 1
for {set var2 1} {$var2 <=8} {incr var2} {
puts "LEVEL_2: $var1/$var2"
# cda stm add "$var1/$var2" ss 1
for {set var3 1} {$var3 <64} {incr var3} {
puts "LEVEL_3: $var1/$var2/$var3"
# cda stm add "$var1/$var2/$var3" ss 1
}
}
}
Hej all,
I want to use expect code in a bash script. For background information here I got my solution for the expect script: Save terminal output to variable in expect/tcl
The script works fine when I use it stand alone. But I want to use it in a bash script for processing multiple files, I have problems with the variables inside the expect part.
I got these error:
set new error bounds? (0: no, 1: yes): missing operand at _#_
in expression " _#_> 0.3 || > 0.3 "
(parsing expression " > 0.3 || > 0.3 ")
...
Code:
#! /bin/bash
MLI_offs=$1
MLI_snr=$2
MLI_diff_par=$3
/usr/bin/expect <<EOF
spawn offset_fitm $MLI_offs $MLI_snr $MLI_diff_par MLI_coffs MLI_coffsets 7.0 6 1
set range 1.5
set azimuth 1.5
while {true} {
expect "enter minimum SNR threshold:"
send "7.0\r"
expect "enter the range and azimuth error thresholds:"
send "$range $azimuth\r"
expect -re {range: ([0-9.]+) azimuth: ([0-9.]+)} {
set range $expect_out(1,string)
set azimuth $expect_out(2,string)
}
expect "set new error bounds? (0: no, 1: yes):" {
if { $range > 0.3 || $azimuth > 0.3 } {
send "1\r"
} else {
send "0\r"
break
}
}
}
interact
EOF
Thanks,
Bjoern
The problem is that the variables are being substituted inside bash as well as Tcl/Expect. Since bash replaces unknown variables with the empty string, this leaves an entirely wrong script (which in turn complains because it can't figure out what's going on). It will have broken other things too (the use of expect_out) but it happens you've not hit it.
The simplest thing is to stop using bash as a wrapper as Tcl's quite adept at doing that sort of thing itself via the argv global. Thus:
#! /usr/bin/expect
set MLI_offs [lindex $argv 0]
set MLI_snr [lindex $argv 1]
set MLI_diff_par [lindex $argv 2]
# Alternatively, replace the preceding three lines with:
# lassign $argv MLI_offs MLI_snr MLI_diff_par
spawn offset_fitm $MLI_offs $MLI_snr $MLI_diff_par MLI_coffs MLI_coffsets 7.0 6 1
set range 1.5
set azimuth 1.5
while {true} {
expect "enter minimum SNR threshold:"
send "7.0\r"
expect "enter the range and azimuth error thresholds:"
send "$range $azimuth\r"
expect -re {range: ([0-9.]+) azimuth: ([0-9.]+)} {
set range $expect_out(1,string)
set azimuth $expect_out(2,string)
}
expect "set new error bounds? (0: no, 1: yes):" {
if { $range > 0.3 || $azimuth > 0.3 } {
send "1\r"
} else {
send "0\r"
break
}
}
}
interact
you can escape the special '$' with a '\' for all expect(tcl) variables, to prevent Bash interpreting it. eg. $range should be writen as \$range in your Bash script.
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.