My question is how to use expect in a shell script. For example, here
is a little program, it just telnet to some host, and login root
automatically.
#!/bin/bash
echo "This is to test how to call expect in a shell"
#hostname=10.0.0.1
expect -c '
set timeout 15
spawn telnet 10.0.0.1
expect "login:" { send "root\r" }
expect "assword:" { send "abcd1234\r" }
interact
'
Can I use variable in the body of expect? Such as the 10.0.0.1 is a
variation passed to the shell scripts, like the following, actually
this is wrong, expect can't handle $hostname
#!/bin/bash
echo "This is to test how to call expect in a shell"
hostname=10.0.0.1
expect -c '
set timeout 15
spawn telnet $hostname
expect "login:" { send "root\r" }
expect "assword:" { send "abcd1234\r" }
interact
'
it will issue the following error.
[tina@kgardenia test]$ ./test.expect
This is to test how to call expect in a shell
can't read "hostname": no such variable
while executing
"spawn telnet $hostname"
Does anybody know how to use a variable in the body of the expect
code, which is embedded in a bash?
Many thanks to you all :D
There are two approaches:
1.) per environment:
hostname=10.0.0.1; export hostname
expect -c '
set timeout 15
spawn telnet $env(hostname)
...
'
or:
hostname=10.0.0.1 expect -c '
... ditto with $env(hostname)
'
2.) by concatenating differently quoted strings in sh:
hostname=10.0.0.1
expect -c '
set timeout 15
spawn telnet '"$hostname"'
...
'
More comments on my previous answer:
> There are two approaches:
> 1.) per environment:
> hostname=10.0.0.1; export hostname
> expect -c '
> set timeout 15
> spawn telnet $env(hostname)
> ...
> '
Unless you explicitly unset hostname after the call
to expect, you might pass the hostname also to other utilities
called from your script later. This is probably no problem
for the hostname, but could be for the password.
> hostname=10.0.0.1 expect -c '
> ... ditto with $env(hostname)
> '
In this case, hostname is only passed to expect as an environment
variable, and forgotten afterwards, so if you still need the hostname
afterwards in the sh-script, don't use it this way.
hostname=10.0.0.1
hostname=$hostname expect -c ' ... '
may appear moronic, but isn't at all. It leaves hostname
as a non-exported variable, while still sending it to expect.
> 2.) by concatenating differently quoted strings in sh:
> hostname=10.0.0.1
> expect -c '
> set timeout 15
> spawn telnet '"$hostname"'
> ...
> '
Actually, I'm sorry for having posted it at all, because it is maximally
unsafe. Any magic characters in the value of hostname could create havoc
inside expect, as expect sees its value as unquoted(!) part of the script
rather than as the contents of some variable or array.
For hostnames set explicitly in the shell-script this is a non-issue,
but I don't know, if this wasn't just a simplification of your problem
for posting.
3.) by constructing the expect script programmatically:
hostname=10.0.0.1
password=foobar
printf -v expect_script '
set timeout 15
spawn telnet %s
...
send -- "%s\r"
...
' "$hostname" "$password"
expect -c "$expect_script"
--
Glenn Jackman
Write a wise saying and your name will live forever. -- Anonymous
But my password happens to be:
password='hello\r"; exec nasty_command; #'
> printf -v expect_script '
> ...
> send -- "%s\r"
> ...
> ' ... "$password"
> expect -c "$expect_script"
Ouch. It shares the disadvantages that I wrote about 2nd way
in my previous direct followup.
It may be ok for a given case with a known-harmless hardcoded
password/hostname, but may be dangerous in other cases, like
where this data is entered by a foreign malvolent user.
Point is, that by passing it over the environment,
(the first approaches in my response) all quoting
problems are moot.
Further safe approaches could involve passing in
information through expect's stdin, provided that
newline characters are not required to be allowed
parts of the actual data, and that the expect
script doesn't already read other data from stdin.
expect -c ' gets stdin hostname; ...' <<<"$hostname"
Thanks a lot for your prompt response. Does expect -c take things
between ' and ' as the code of expect? The code outside ' and ' will
be taken as shell, so "$hostname" was actually a shell style variable.
So you can just omit those two ", like
expect -c '
set timeout 15
spawn telnet '$hostname'
...
'
Am I right?
I agree this is dangerous if you don't know whether there are some
magic characters in the hostname.
You're wrong in a couple of minor ways, but, yes,
expect -c $ARG
interprets $ARG as an Expect command line --just as, for example,
<URL: http://www.tcl.tk/man/expect5.31/expect.1.html > promises.
> Thanks a lot for your prompt response. Does expect -c take things
> between ' and ' as the code of expect?
Yes, and expect doesn't see the single quotes themselves.
> The code outside ' and ' will be taken as shell,
The shell, which also interprets the single-quotes according
to it's syntax (namely: "take everything literally, up to the
next single-quote") will also parse the "$hostname" and finally
create a single string that has all the parts concatenated, and
this string is then passed to expect, which takes it as if it
it had read it from a script-file.
> so "$hostname" was actually a shell style variable.
yes.
> So you can just omit those two ", like
> expect -c '
> set timeout 15
> spawn telnet '$hostname'
One principially can, but that has an effect on what the shell
does with the contents of the variable. For sh-like shells,
(unlike tclsh!), there *is* a difference between $var and "$var",
but this difference is only relevant for values of var that
contain certain shell-meta-characters such as e.g. whitespace.