expect in bash: quoting with special characters and output as VAR - bash

I have this bash scipt
#!/bin/bash
Login="bla 0, xy;Z" ##the spaces and the ',' and ';' are important!
expect -c "
spawn telnet *HOST*
expect "Done."
send '$Login'
expect "Done."
send "command 0". ## the ' 0' is important! Same prob as with the password
??? ## what do I have to do to get the output in a VAR?
expect "Done."
"
The two questions are:
the $Login is not interpreted as text from bash, so it thinks with 0 a new command starts... and ',' as well as ';' seems to be a problem too. escaping with '' did not work!?
How do I get the output as a $VAR to use it later?
Is this possible at all in bash? or do I have to use tcl (which I have never ever used or read anything about before)?
Thanks for advices!
andy

In addition to the useful comments above, single quotes have no special meaning in Tcl/expect, they are just ordinary characters that are being sent with the password.
As Charles writes, passing data via the environment is helpful.
Also, Tcl comments are a bit annoying: the # is only recognized as a comment if it occurs where a command starts, so you'll see me use ;# below.
I find using a quoted heredoc removes quoting difficulties.
When you say "result in a VAR", I assume you want a shell variable?
untested
export Login="bla 0, xy;Z"
export HOST=something
VAR=$(expect << 'END_EXPECT'
log_user 0 ;# turn off normal stdout of the interaction
# so we can capture only the output you want
spawn telnet $env(HOST)
expect "Done."
send "$env(Login)\r" ;# don't forget to "hit enter"
expect "Done."
send "command 0\r"
# what do I have to do to get the output in a VAR?
# use a regex capturing the command's output. Note that the command
# itself appears in the output
expect -re "command 0\r\n(.+)Done."
# the shell will capture this output
puts $expect_out(1,string)
send "exit\r" ;# or whatever you need to do to quit
expect eof
END_EXPECT
)

Related

Embedding an Expect script inside a Bash script

I have the following script:
#!/bin/bash
if [ `hostname` = 'EXAMPLE' ]
then
/usr/bin/expect << EOD
spawn scp -rp host:~/outfiles/ /home/USERNAME/outfiles/
expect "id_rsa':"
send "PASSWORD\r"
interact
spawn scp -rp host:~/errfiles/ /home/USERNAME/errfiles/
expect "id_rsa':"
send "PASSWORD\r"
interact
expect eof
EOD
echo 'Successful download'
fi
Unfortunately it doesn't seem to work and I get an error message:
spawn scp -rp host:~/outfiles/ /home/USERNAME/outfiles/
Enter passphrase for key '/home/USERNAME/.ssh/id_rsa': interact: spawn id exp0 not open
while executing
"interact"
I don't know what it means and why it doesn't work. However, when I wrote the above code using a not-embedded Expect script:
#!/usr/bin/expect
spawn scp -rp host:~/outfiles/ /home/USERNAME/outfiles/
expect "id_rsa':"
send "PASSWORD\r"
interact
spawn scp -rp host:~/errfiles/ /home/USERNAME/errfiles/
expect "id_rsa':"
send "PASSWORD\r"
interact
It worked without any problems. So what am I doing wrong?
NOTE: Often when someone posts a question about using Expect to use scp or ssh the answer given is to use RSA keys. I tried, unfortunately on one of my computers there is some crappy bug with the GNOME keyring that means that I can't remove my password from the RSA key, which is exactly why I'm trying to write the above script with an if statement. So please don't tell me to use RSA keys.
Your Bash script is passing the Expect commands on the standard input of expect. That is what the here-document <<EOD does. However, expect... expects its commands to be provided in a file, or as the argument of a -c, per the man page. Three options are below. Caveat emptor; none have been tested.
Process substitution with here-document:
expect <(cat <<'EOD'
spawn ... (your script here)
EOD
)
The EOD ends the here-document, and then the whole thing is wrapped in a <( ) process substitution block. The result is that expect will see a temporary filename including the contents of your here-document.
As #Aserre noted, the quotes in <<'EOD' mean that everything in your here-document will be treated literally. Leave them off to expand Bash variables and the like inside the script, if that's what you want.
Edit Variable+here-document:
IFS= read -r -d '' expect_commands <<'EOD'
spawn ... (your script here)
interact
EOD
expect -c "${expect_commands//
/;}"
Yes, that is a real newline after // - it's not obvious to me how to escape it. That turns newlines into semicolons, which the man page says is required.
Thanks to this answer for the read+heredoc combo.
Shell variable
expect_commands='
spawn ... (your script here)
interact'
expect -c "${expect_commands//
/;}"
Note that any ' in the expect commands (e.g., after id_rsa) will need to be replaced with '\'' to leave the single-quote block, add a literal apostrophe, and then re-enter the single-quote block. The newline after // is the same as in the previous option.

expect response seen in bash debug but I cant get it.

I am trying to get the information from a keepass database and I can't get past this last step!
#!/usr/bin/env bash
#!/usr/bin/expect
firewall=$1
password="PASSWORD"
echo "connecting to KeepassDB..."
function get_creds {
expect <<- DONE
set timeout 10
spawn kpcli
match_max 100000000
expect "kpcli:/>"
send "open /media/sf_VM_shared/keepass.kdb\n"
expect "password:"
send "$password\n"
expect ">"
send "cd General/Network/Firewalls/SSH\n"
expect "SSH>"
send "ls\n"
expect ">"
DONE
}
credentials=$(get_creds)
echo $credentials
When I do a bash -x I can see what I want, it's a long list of information, but without the -x the script is not returning the list to the terminal.
You are getting the output when using bash -x because then every command output is printed after they are run (debug mode)
The script does not print because your last line of output from get_creds is empty. It should print the last line of output from the method.
When using echo to output your credentials you should use quoutes (") in order to preserve newlines.
Try replacing the last line in your script with this:
echo "$credentials"

Bash: expand variables, but not special characters

I have a bash script that creates and executes an expect script by stitching together dozens of different files containing pieces of expect code. Those files contain environment variables that need to be expanded. Example:
expect.piece:
send "command\r"
sleep $timeout
send "command argument\r"
script.sh:
#let's try it like this
eval echo $(cat expect.piece)
#or maybe like this
eval "echo \"$(cat expect.piece)\""
output:
send command\r sleep 1 send command argument\r
send commandr
sleep 1
send command argumentr
Desired otput:
send "command\r"
sleep 1
send "command argument\r"
I need a solution without sed string substitution (there is a lot of environment variables) and without modifying original expect script files. I guess it could be done line by line, but is there a more elegant solution?
Default field separator in bash is a space so set input file separator as a new line like IFS=$(echo -e '\n') before executing eval echo $(cat expect.piece) .
Final script would be :
#Storing original field separator in variable OFS
OFS=$IFS
#Setting IFS as new line using echo -e. Unfortunately IFS="\n" does not work in bash
IFS=$(echo -e '\n')
eval echo $(cat expect.piece)
#Resetting the field separator as space
IFS=$OFS
There is one another way which you can put your expect code with -c flag in the shell script as shown below.
script.sh
#Calling the expect.piece file code here
expect -c expect.piece
You can make use of the optional command line values as ,
expect -c "set count 1" myscript.exp
where the variable count will be used in the expect script file myscript.exp.
You can directly give the whole code as
expect -c "
send \"command\r\"
sleep $timeout
send \"command argument\r\"
"
Notice the use of backslash to escape the double quotes wherever needed. Single quotes can also be used. But, if you use double quotes, then only shell substitution can happen.
Please refer here to know more about the -c flag in expect.
It's not clear from your question in what context you want this output. If it's okay to embed the Expect script as a here document, what you want is trivial.
#!/bin/sh
timeout=1
cat <<____HERE
send "command\r"
sleep $timeout
send "command argument\r"
____HERE
(Maybe you can even replace the cat with expect but I'm not familiar enough with Expect to make any recommendations.)
If you need to take the input from a file, and only have a limited set of variables you want expanded, you could do that with sed.
sed "s/\$timeout/$timeout/g" file
If you need a more general solution, you might want to swich to Perl:
perl -pe 's/\$(\w+)/$ENV{$1} || "\$$1" /ge' file
but this requires you to export (or otherwise expose to Perl) the shell environment variables you want exported. (This will just not substitute any undefined variables; change the value after || if you want to change that aspect of the behavior.)
I invented the solution for this problem, it is a kludge, but it works.
expect.piece:
sleep $timeout
send "foo bar\r"
send "$(date)\r"
script.sh:
timeout=1
eval echo $(sed " \
s/\\\/\\\\\\\/g; \
s/\"/\\\\\"/g; \
s/\"/\\\\\`/\"/\\\\\`/g; \
" "expect.piece" | tr '\n' '+') | tr '+' '\n'
output:
sleep 1
send "foo bar\r"
send "Wed Feb 18 03:19:24 2015\r"
First, we need to escape all the backslashes, backticks and quotes in the file, because they will be removed during the evaluation. Then, we need to replace all the newline characters with pluses, in order to make it in a single line. After that, we evaluate that line (the evaluation will "expand" all the environment variables and command substitutions) and replace pluses back to newlines.

I am trying to export tcl buffer to bash variable

I am trying to export tcl buffer to bash variable, i cannot get it to work.
I hope my example below will be clear of what i am trying to accomplish.
I definately would like an tcl embeded script
======================================
#!/bin/bash
var=bash_to_tcl
expect -c "
puts lindex $argv 0
expect "xx"
send "123\n"
set $var $expect_out(buffer) <<<< setting the variable to export to bash>>>>>>
}
exit 0
<<<>>
=====================================
echo $var "tcl_to_bash" (THIS IS WHERE I AM HAVING ISSUES) <<<<<<<<<<<<<<<<<<<
=====================================
I have been searching all over for some clue of example but cannot find any.
I got the ecpect working but cannot export the output back to bash
The child process (expect) cannot alter the environment of the parent (bash). Usually information is passed between processes via the stdio channels:
#!/bin/bash
# this is how bash captures the output of the expect program
var=$(expect -c '
spawn ...
expect "xx"
send "123\n"
# here is expect sending the info back to the parent
puts $expect_out(buffer)
')
do something with "$var"

Expect in Bash script: expect_out not printing out buffer

I am trying to capture the output of the "dir" command by logging into a switch, but I am unable to do so. I am using Expect within Bash. I am making use of expect_out to capture output of that command in a buffer and print it out. Actually I want to capture the output and perform some operations on it.
Script:
#!/bin/bash
expect -c "
spawn telnet 1.1.1.1 2000
sleep 1
send \"\r\"
send \"\r\"
expect {
Prompt> { send \"dir\r\" }
}
set output $expect_out(buffer)
"
echo "$output"
Output:
spawn telnet 1.1.1.1 2000
Trying 1.1.1.1...
Connected to 1.1.1.1 (1.1.1.1).
Escape character is '^]'.
Prompt>
Prompt>
After these prompts are displayed, the scripts just exits. How can I fix this problem?
After I split it, I can use parameter substitution as well as single quotes. Now I am facing different error.
Script:
expect -c "
spawn telnet $IP $PORT1
sleep 1
send \"\r\"
send \"\r\"
"
expect -c '
expect {
Prompt> { send \"dir\r\" }
set output $expect_out(buffer)
puts "$output"
}
'
Output:
spawn telnet 172.23.149.139 2033
can't read "expect_out(buffer)": no such variable
while executing
"expect {
Prompt> { send \"dir\r\" }
set output $expect_out(buffer)
puts "$output"
}
"
I changed it to according to the suggestions. But I am still facing errors.
Script:
output=$(expect -c '
spawn telnet '"$IP $PORT1"'
sleep 1
send '"\r"'
send '"\r"'
expect Prompt> { send '"dir\r"' }
expect '"\n"'
expect -indices Prompt>
puts '"[string range $expect_out(buffer) 0 [expr $expect_out(0,end) - 1]]"'
')
echo "======="
echo "$output"
echo "======="
Output:
syntax error in expression "(0,end) - 1"
while executing
"expr (0,end) - 1"
invoked from within
"string range (buffer) 0 [expr (0,end) - 1]"
invoked from within
"puts [string range (buffer) 0 [expr (0,end) - 1]]"
=======
spawn telnet 1.1.1.1 2000
Trying 1.1.1.1...
Connected to 1.1.1.1 (1.1.1.1).
Escape character is '^]'.
Prompt>
Prompt>
=======
Hence to circumvent the error, I changed, the line
puts '"[string range $expect_out(buffer) 0 [expr $expect_out(0,end) - 1]]"'
to
puts '"$expect_out(buffer)"'
But then I am getting no error, but the output of dir is also not getting printed. Something like:
Prompt>
Prompt> (buffer)
The second of your “split” Expect programs does not have access to the spawned telnet process. When you split them like that, you made them independent (one can not access the variables or state of the other; actually by the time the second one has started the first one, and its telnet process, no longer exist).
The shell will automatically concatenate any strings (that are not separated by unquoted/unescaped whitespace) without regard to the kind of quotes (if any) they use. This means you can start put the first part of your Expect program in single quotes, switch to double quotes for the parameter substitution, and then go back to single quotes for the rest of the program (to avoid having to escape any of "$\` that occur in your Expect code).
expect -c '
spawn telnet '"$HOST $PORT"'
sleep 1
⋮ (rest of Expect program)
'
It uses single quotes to protect most of the program, but switches back to double quotes to let the shell substitute the its HOST and IP parameters into the text of the Expect program.
Next, the shell that started expect can not access variable set inside the Expect program. The normal way to collect output from a child process is to have it write to stdout or stderr and have the shell collect the output via a command substitution ($()).
In Tcl (and Expect), you can use puts to send a string to stdout. But, by default, Expect will also send to stdout the normal “interaction” output (what it receives from any spawned commands and what it sent to them; i.e. what you would see if you were running the spawned command manually). You can disable this default logging with log_user 0.
You might write your program like this:
#!/bin/sh
output=$(expect -c '
# suppress the display of the process interaction
log_user 0
spawn telnet '"$HOST $PORT"'
sleep 1
send "\r"
send "\r"
# after a prompt, send the interesting command
expect Prompt> { send "dir\r" }
# eat the \n the remote end sent after we sent our \r
expect "\n"
# wait for the next prompt, saving its position in expect_out(buffer)
expect -indices Prompt>
# output what came after the command and before the next prompt
# (i.e. the output of the "dir" command)
puts [string range $expect_out(buffer) \
0 [expr $expect_out(0,start) - 1]]
')
echo "======="
echo "$output"
echo "======="
Because your Expect script is enclosed in double quotes, the shell is expanding $expect_out to an empty string. Put the body of the expect script in single quotes.
When you set a variable in Expect, the shell will have no idea. When the Expect script completes, its variables vanish too. You have to puts the expect_out buffer.

Resources