escape $ in expect script - bash

#!/bin/bash
/usr/bin/expect << SSHLOGIN
spawn ssh -o StrictHostKeyChecking=no admin#$host
expect {
Password: {
send "Pass$word\n"
expect {
OK: {
send "xstatus\n"
send "quit\n"
}
}
}
}
SSHLOGIN
It is not able to ssh because it is not escaping the '$' character in "Pass$word\n" since $ is part of the password and there is no variable being passed. How would you escape it? I know in bash, you would add '\', but since the password is in the expect script portion, that does not work.
EDIT:
changing Pass$word\n to Pass\\\$word\n works

Here-documents are bash code, so you'd still use \$.
The expect script is TCL, so you'll need to escape the $ there too. With two levels of escaping, you get:
send "Pass\\\$word\n"

Related

Expect script is not working with SFTP with proxy command

I am trying to execute SFTP command with proxy option, but it is giving error. Script sample is as below
Start
sftpCommand="sftp -o ConnectTimeout=3 -o "ProxyCommand=/usr/bin/nc --proxy-type http --proxy 192.168.20.98:3128 --proxy-auth pxuser:Password#1 %h %p" -oPort=22 user-sftp2#202.89.99.20"
/usr/bin/expect << EOD
set timeout -1
spawn /bin/sh -c $sftpCommand
expect {
"*?assword:*" {
send "$sftpPassword\r"
}
"*>*" {
send "put $datewise_dir $remoteDir\r"
}
"*>*" {
send "bye\r"
}
}
EOD
End
above script is giving error like
line 6: {--proxy-type}: command not found
spawn /bin/sh -c
/bin/sh: -c: option requires an argument
In your first line defining sftpCommand you use double quotes " inside a string which is itself enclosed in double quotes - this nesting will not work. You can fix this by changing the outer quotes to single ones ', i.e.
sftpCommand='sftp -o ConnectTimeout=3 -o "ProxyCommand=/usr/bin/nc --proxy-type http --proxy 192.168.20.98:3128 --proxy-auth pxuser:Password#1 %h %p" -oPort=22 user-sftp2#202.89.99.20'

Read program output from stdin instead of using spawn

I'm trying to write a shell function that spawns a ssh process and authentificates with a password. Then, I'd like to use the spawned process to do further stuff with expect.
Here's the function I have so far:
ssh_cmd() {
ssh $USER#$HOST 2>&1 | expect -c "
log_user 0
set timeout 5
expect password: {
send \"$PASS\n\"
sleep 2
log_user 1
}
"
}
And then I'd like to use given function in other places to interact with the ssh process like this:
ssh_cmd | expect -c "
expect '#' {
send pwd\n
send exit\n
}
expect eof
"
However, running the ssh_cmd function with -d option for expect I get the following result:
expect version 5.45.4
expect: does "" (spawn_id exp0) match glob pattern "password:"? no
ubnt#ui1's password: expect: timed out
From what I understand, the output of ssh does not get piped correctly. I know the common way to do this would be to use spawn, but that would mean the process would get killed after expect exits and I could not have a generic function that authentificates ssh sessions and keeps the process alive for further usage.
What you're designing won't work. Expect needs the process to be spawned from within the expect interpreter using the spawn command. Passing the command's stdout into expect is insufficient.
You could try this:
ssh_cmd() {
# a default bit of code if user does not provide one.
# you probably want some checking and emit an error message.
local user_code=${1:-set timeout 1; send "exit\r"; expect eof}
expect -c "
log_user 0
set timeout 5
spawn ssh -l $USER $HOST
expect password: {
send \"$PASS\n\"
sleep 2
log_user 1
}
$user_code
"
}
and then invoke it like:
ssh_cmd '
expect "#" {
send pwd\r
send exit\r
}
expect eof
'
Note that single quotes have no special meaning in expect, they are just plain characters: you probably don't want to expect the prompt to be the 3 character pattern '#'

Bash inconsistent escaping of asterisk * in expect?

I've got 2 expect commands, however, I don't understand the expansion that's going on. (In context, I have a script that connects to a server, downloads and blanks all the log files in a specified directory.)
expect -c "
set timeout 1
spawn scp user#hostname:/logdir/\*.log .
expect yes/no { send yes\n ; exp_continue }
expect password: { send $pass\n }
expect 100%
sleep 1
exit
";
In this command, expect displays the spawned command as spawn scp user#hostname:/logdir/*.log . Which means that the \ was removed.
expect -c "
set timeout 1
spawn ssh user#hostname {echo '' | tee /logdir/\*.log > /dev/null}
expect yes/no { send yes\n ; exp_continue }
expect password: { send $pass\n }
expect eof
";
In this command, expect displays the spawned command as spawn ssh user#hostname echo '' | tee /logdir/\*.log > /dev/null Which means that the \ was not removed. Why is it different? (If I don't escape the asterisk, like tee /logdir/*.log, it does work. But I don't understand what is working differently from the above case?)
That's how Tcl deals with backslashes.
[bash] # tclsh
% puts \*
*
% puts "\*"
*
% puts {\*}
\*
%
According to Tcl doc:
If a backslash (\) appears within a word then backslash substitution occurs. In all cases but those described below the backslash is dropped and the following character is treated as an ordinary character and included in the word. The following table lists the backslash sequences that are handled specially, along with the value that replaces each sequence.
[...]
Backslash substitution is not performed on words enclosed in braces, except for backslash-newline as described above.

Expect fails but I don't see why

I have a bash script that gets info from Heroku so that I can pull a copy of my database. That script works fine in cygwin. But to run it in cron it halts because the shell that it uses halts as Heroku's authentication through Heroku Toolbelt.
Here is my crontab:
SHELL=/usr/bin/bash
5 8-18 * * 1-5 /cygdrive/c/Users/sam/work/push_db.sh >>/cygdrive/c/Users/sam/work/output.txt
I have read the Googles and the man page within cygwin to come up with this addition:
#!/usr/bin/bash
. /home/sam.walton/.profile
echo $SHELL
curl -H "Accept: application/vnd.heroku+json; version=3" -n https://api.heroku.com/
#. $HOME/.bash_profile
echo `heroku.bat pgbackups:capture --expire`
#spawn heroku.bat pgbackups:capture --expire
expect {
"Email:" { send -- "$($HEROKU_LOGIN)\r"}
"Password (typing will be hidden):" { send -- "$HEROKU_PW\r" }
timeout { echo "timed out during login"; exit 1 }
}
sleep 2
echo "first"
curl -o latest.dump -L "$(heroku.bat pgbackups:url | dos2unix)"
Here's the output from the output.txt
/usr/bin/bash
{
"links":[
{
"rel":"schema",
"href":"https://api.heroku.com/schema"
}
]
}
Enter your Heroku credentials. Email: Password (typing will be hidden): Authentication failed. Enter your Heroku credentials. Email: Password (typing will be hidden): Authentication failed. Enter your Heroku credentials. Email: Password (typing will be hidden): Authentication failed.
As you can see it appears that the output is not getting the result of the send command as it appears it's waiting. I've done so many experiments with the credentials and the expect statements. All stop here. I've seen few examples and attempted to try those out but I'm getting fuzzy eyed which is why I'm posting here. What am I not understanding?
Thanks to comments, I'm reminded to explicitly place my env variables in .bashrc:
[[ -s $USERPROFILE/.pik/.pikrc ]] && source "$USERPROFILE/.pik/.pikrc"
export HEROKU_LOGIN=myEmailHere
export HEROKU_PW=myPWhere
My revised script per #Dinesh's excellent example is below:
. /home/sam.walton/.bashrc echo $SHELL echo $HEROKU_LOGIN curl -H "Accept: application/vnd.heroku+json; version=3" -n https://api.heroku.com/
expect -d -c " spawn heroku.bat pgbackups:capture --expire --app gw-inspector expect {
"Email:" { send -- "myEmailHere\r"; exp_continue}
"Password (typing will be hidden):" { send -- "myPWhere\r" }
timeout { puts "timed out during login"; exit 1 } } " sleep 2 echo "first"
This should work but while the echo of the variable fails, giving me a clue that the variable is not being called, I am testing hardcoding the variables directly to eliminate that as a variable. But as you can see by my output not only is the echo yielding nothing, there is no clue that any diagnostics are being passed which makes me wonder if the script is even being called to run from expect, as well as the result of the spawn command. To restate, the heroku.bat command works outside the expect closure but the results are above. The result of the command directly above is:
/usr/bin/bash
{
"links":[
{
"rel":"schema",
"href":"https://api.heroku.com/schema"
}
]
}
What am I doing wrong that will show me diagnostic notes?
If you are going to use the expect code inside your bash script, instead of calling it separately, then you should have use the -c flag option.
From your code, I assume that you have the environmental variables HEROKU_LOGIN and HEROKU_PW declared in the bashrc file.
#!/usr/bin/bash
#Your code here
expect -c "
spawn <your-executable-process-here>
expect {
# HEROKU_LOGIN & HEROKU_PW will be replaced with variable values.
"Email:" { send -- "$HEROKU_LOGIN\r";exp_continue}
"Password (typing will be hidden):" { send "$HEROKU_PW\r" }
timeout { puts"timed out during login"; exit 1 }
}
"
#Your further bash code here
You should not use echo command inside expect code. Use puts instead. The option of spawning the process inside expect code will be more robust than spawning it outside.
Notice the use of double quotes with the expect -c flag. If you use single quotes, then bash script won't do any form of substitution. So, if you need bash variable substitution, you should use double quotes for the expect with -c flag.
To know about usage of -c flag, have a look at here
If you still have any issue, you can debug by appending -d with the following way.
expect -d -c "
our code here
"

Bash- How to pack escape character into a special character ($) inside a string?

I have a variable in bash return from a function call getpassword() which return "apple$123123"
FOO=`getpassword`
I would like to use FOO variable which contains $ inside and pass into expect program
expect -c "\
set timeout 90
set env(TERM)
spawn rdesktop 192.168.11.1
expect \"Password:\"
send -- \"'${FOO}\n'\"
interact
"
}
There is an error coming out as $FOO contain dollar-sign
Password: can't read "123": no such variable
while executing
How can i solve this kind of problem? The way i think is that to pack escape character into FOO, using sed?
Thanks
You could try this:
# below is purposely on one line -- it sets the FOO env var
# only for the duration of the expect command.
FOO=$(getpassword) expect -c '
set timeout 90
set env(TERM) {are you missing something here?}
spawn rdesktop 192.168.11.1
expect "Password:"
send -- "$env(FOO)\r" # you send '\r' not '\n'
interact
'
Using single quotes make it easier to write (and read) the expect script (without all the backslashes). Testing:
$ getpassword() { echo 'abc$123'; }
$ FOO=$(getpassword) expect -c 'puts "pw=$env(FOO)"'
pw=abc$123
$ echo "> $FOO <"
> <

Resources