Expect does not detect string - shell

I need to automate the input of a string in a command called from a shell (sh) script, using expect To accomplish this I have:
#!/bin/sh
MY_ARGS="-a -b -c" expect -c "
spawn mycommand $MY_ARGS
expect 'continue, I know what I am doing' { send -- 'continue, I know what I am doing\r' }
expect eof"
The mycommand output is:
Enter "continue, I know what I am doing" to use the outdated data anyway:
But the string is not being detected and therefore nothing is sent. What is wrong with the expect command?
Thanks.

There's a few things going on here:
When you write var=value some_command, then $var is set in the environment for the command.
Inside expect you need $env(MY_ARGS)
But now you have a quoting problem, you can't put the expect body in double quotes because you
don't want the shell to expand that variable.
#sexpect_Expect.for.Shells is right, single quotes are not special in Tcl (and hence expect). expect sees this:
expect {
{'continue,} {I}
{know} {what}
{I} {am}
{doing'} { send -- "'continue," "I" "know" "what" "I" "am" "doing\r'" }
}
To solve the quoting problems, I recommand using a quoted heredoc in the shell
export MY_ARGS='-a -b -c'
# .......v..........v these quotes are crucial
expect <<'END_EXPECT'
set phrase "continue, I know what I am doing"
set timeout -1
spawn mycommand {*}$env(MY_ARGS)
expect $phrase
send -- "$phrase\r"
expect eof
END_EXPECT
Note how I expanded the env variable: {*}$env(MY_ARGS) -- the {*} syntax splits the variable content into separate words.

Related

How to set a TCL variable as a varible in a bash script

So my goal is to take a variable that is in my TCL file and pass it to my shell script for it to be able to use. Right now I am able to echo to get the result of my variable but I cannot for some reason set that result to a variable in my bash script.
Here is an example of my TCL script:
set file_status "C"
Here is what I have for my bash script:
echo 'source example.tcl; foreach _v {file_status } {puts "\"[set $_v]\""}' | tclsh
file_status='source example.tcl; foreach _v {file_status } {puts "\"[set $_v]\""}' | tclsh
echo $file_status
So the first echo statement above works but after I set the file_status variable for some reason the second echo statement doesn't work.
Doing it in general requires very complex code; Tcl variables are capable of holding arbitrary data (including full binary data) and don't have length restrictions, whereas Shell is a lot more restricted. But it's possible to do something for the common cases where the values are plain text without NULs. (C would be an excellent example of such a value.)
When passing to a subprocess, by far the easiest way is to use an environment variable:
set the_status "C"
set env(file_status) $the_status
exec bash -c {echo "file status is $file_status"} >#stdout
That has length restrictions, but it's extremely easy.
If you're sending the variable to some other process, your best bet is to write a little script (here, I'm sending it to stdout):
puts [format {file_status='%s'} [string map {"'" "'\''"} $the_status]]
That is producing a script that just sets the variable. (string map is turning single quotes into something that works inside single quotes; everything else doesn't need conversion like that.) You run the script in the shell with eval or source/. (depending on whether it is in a string or in a file).
Very large data should be moved around inside a file or via a pipe. It needs much more thought in general.
I would output shell syntax from Tcl and source it into your running shell:
Given
$ echo 'source example.tcl; foreach var {file_status} {puts "$var=\"[set $var]\""}' | tclsh
file_status="C"
then
source <(echo 'source example.tcl; foreach var {file_status} {puts "$var=\"[set $var]\""}' | tclsh)
declare -p file_status
outputs
declare -- file_status="C"
Using /bin/sh, you could:
var_file=$(mktemp)
echo ... | tclsh > "$var_file"
source "$var_file"
rm "$var_file"

Can't run an expect script: invalid command name "Yes/No"

I have this expect script that need to execute some other shell script to accept a Licence agreement
#!/usr/bin/expect
spawn ./xxx.sh
expect -ex "--More--"
send -- " "
expect "Do you agree with this license ?[Yes/No]"
send "Y\r"
But when I run it I get this error
invalid command name "Yes/No"
while executing
"Yes/No"
invoked from within
"expect "Do you agree with this license ?[Yes/No]""
(file "./xxx.sh" line 5)
I don't know what I'm doing wrong
expect is an extension of the Tcl language. In Tcl, you use square brackets for command substitution. Like the bash shell, command substitution occurs within double quoted strings.
To prevent your code from attempting to execute the Yes/No command:
use different quotes: Tcl uses curly braces as the non-interpolating quotes:
expect {Do you agree with this license ?[Yes/No]}
escape the brackets to prevent command substitution:
expect "Do you agree with this license ?\[Yes/No\]"

capture expect ssh output to variable

Hey am new to bash scripts and was wondering how would I capture the output of the ssh command into a bash variable? I looked around and cant seem to get it right. I have tried puts $expect_out(buffer) but when echo it says variable does not exist
I know the response should be just one line and if I want to save that into a variable response and then echo it how would I do that?
A generic idea can be something like as below.
spawn the ssh session
make proper login
Send each commands with send
Wait for desired output with expect
Example:
spawn ssh $user#$domain
expect "password" { send "$pwd\r"}
expect "#"; # This '#' is nothing but the terminal prompt
send "$cmd\r"
expect "#"
puts $expect_out(buffer); #Will print the output of the 'cmd' output now.
The word to wait for after executing the command can vary based on your system. It can be # or $ or > or :; So, make sure you are giving the correct one. Or, you can provide a generalized pattern for the prompt as such
set prompt "#|>|:|\\\$"; # We escaped the `$` symbol with backslash to match literal '$'
While using the expect after sending the commands, it can be used as
expect -re $prompt; #Using regex to match the pattern`

expect script, puting name of file in command

I have problems with expect script. Well when I grep this file I need to put it to line under and it should look like :
/opt/ericsson/arne/bin/import.sh -f bla_bla_bla.xml -val:rall
but I don't know how to put this file in beetween this line. Because when I have put grep command in beetween in didn't work, maybe problem is -val:rall that I have after?
If someone know's how could I put name of file in File1
#!/usr/local/bin/expect --
set env(TERM) vt100
set env(SHELL) /bin/sh
set env(HOME) /usr/local/bin
set PASSWORD ter
set DUL [lindex $argv 0]
set VAR _cus_ipsec
match_max 1000
spawn ssh mashost
expect {
"assword" {send "$PASSWORD\r"}
}
expect "ran#rn23"
send -- "cd /tih/opt/bla/tih/ \r"
expect "ran#rn23"
send -- "grep -il $DUL * \r*"
expect "ran#rn23"
send -- "/opt/bl/arne/bin/imp.sh -f File1 -val:rall\r"
expect "ran#rn03"
send -- "/opt/bl/arne/b/imp.sh -f File1 -import\r"
expect "ran#rn23"
interact
Ok, thanks for the clarification, I believe I do understand what you're trying to do now.
What you need to do is change the expect statement you have after you send the grep command to one that will capture your filename. And you will probably benefit from using the regexp mode of the expect command (-re), and possibly using parenthesis to capture the filename (not used in my sample below). I do not know what are the possible filenames that you can get from your grep, so you will probably need to tweak the below quite a bit, but assuming your grep will give you a single .xml file beginning with "NAME", you could do something like the following:
send -- "grep -il $DUL * \r*"
expect -re "NAME.*\.xml"
send -- "/opt/bl/arne/bin/imp.sh -f $expect_out(0,string) -val:rall\r"
As a suggestion, you should really include some timeout options for your expect statements, and some error checking, otherwise this script will not stop if anything does not go as expected. E.g. only send if you have found what you expected, etc.
Your regexp probably will be more complicated than the one I showed you, but you can get the idea. Also, include exp_internal 1 somewhere near the top of your script to get good, solid debugging info on what your script is matching (or not matching). It will be very useful as you test it.
Let me know how that goes.

Bash: expect + scp : Problem on multiple files

function expect_password {
expect -c "\
set timeout 90
set env(TERM)
spawn $1
expect \"password:\"
send \"$password\r\"
expect eof
"
}
expect_password "scp /home/kit.ho/folder/file1 root#$IP:/usr/bin"
The above expect_password works perfect!
However, I want to transfer multiple files in that directory, so I tried:
expect_password "scp /home/kit.ho/folder/* root#$IP:/usr/bin"
But an error comes up:
/home/kit.ho/folder/*: No such file or directory
Killed by signal 1.
It seems that expect doesn't recognize *. How can I transfer files in that way?
There is a possible answer using rsync but I can't use that.
The manpage of expect says "If program cannot be spawned successfully because exec(2) fails", so I assume that expect uses exec internally. exec doesn't call any shell to do wildcard expansion and such magic, which means that your ssh sees the asterisk and can't handle it. Have you tried to call your shell explicitely like
expect_password "sh -c \"scp /home/kit.ho/folder/* root#$IP:/usr/bin\""
(maybe you need to omit the single quotes)?
edit:
use \" instead of '
Expect is an extension of Tcl, and Tcl does not speak shell-filename-globbing natively. Rather than shoe-horning a Tcl solution withing your framework, try
set -- /home/kit.ho/folder/*
expect_password "scp $* root#$IP:/usr/bin"
Files with spaces won't work properly with this solution.
Can't you leave away the password stuff completely and work with SSH public keys?

Resources