Expect: how to spawn a command containing a backslash? - bash

I have the following script:
#!/bin/bash
echo -n "Enter user name: "
read USER
echo -n "Enter password: "
read -s PWD
cat $HOME/etc/switches.txt | while read IP SWITCH
do
echo ${SWITCH}
/usr/bin/expect <<EOD
# Change to 1 to Log to STDOUT
log_user 1
# Change to 1 to enable verbose debugging
exp_internal 1
# Set timeout for the script
set timeout 20
spawn ssh -l {$USER} -oCheckHostIP=no -oStrictHostKeyChecking=no -q $IP
match_max [expr 32 * 1024]
expect "Password:"
send $PWD
send "\n"
expect "#"
send "show fcip summary | grep TRNK\n"
EOD
echo
done
When I run it, the backslash in the username disappears, giving these result:
Enter user name: corp\user
Enter password:
=== ss3303-m-esannw-m01a ===
spawn ssh -l corpuser -oCheckHostIP=no -oStrictHostKeyChecking=no -q 10.247.184.70
[...]
I suspect my problem is due in part to embedding my expect script inside a bash script. I've tried using $USER and "$USER" as well, with the same results. Using corp\\\\user (yes, four backslashes!) does work but is inconvenient. I'm seriously considering using sed or something to multiply the backslashes, but would love to hear other ideas.

You might have better luck passing the variables through the environment so expect can access them directly, instead of relying on the shell to substitute the values into the heredoc:
#!/bin/bash
read -p "Enter user name: " USER
read -sp "Enter password: " PWD
export USER PWD IP
while read IP SWITCH
do
echo ${SWITCH}
# the heredoc is single quoted below
/usr/bin/expect <<'EOD'
# Change to 1 to Log to STDOUT
log_user 1
# Change to 1 to enable verbose debugging
exp_internal 1
# Set timeout for the script
set timeout 20
match_max [expr {32 * 1024}]
spawn ssh -l $env(USER) -oCheckHostIP=no -oStrictHostKeyChecking=no -q $env(IP)
expect "Password:"
send -- "$env(PWD)\r"
expect "#"
send "show fcip summary | grep TRNK\r"
expect eof
EOD
echo
done <$HOME/etc/switches.txt
Notes:
the heredoc is single-quoted: the shell will not try to interpolate variables
exported the shell variables used in the expect code
use \r to "press enter" for the send command.
tidied up the input of the username and password
tidied up reading the text file

Related

How to use encrypted password with sftp login?

I want to write a shell script where user interaction is not allowed and I want to use sftp login with in the script. Now I have few challenges to execute this approach.
There is no user interaction so I can only provide the options as a argument while executing the script. Script can be like this.
#!/bin/bash
if [[ "${#}" -lt 4 ]]; then
echo -e "Usage: ${0} <sftpUser> <sftpPassword> <sftpHost> <sftpPort>"
exit 1
fi
sftpUser=${1}
sftpPassword=${2}
sftpHost=${3}
sftpPort=${4}
remote_dir="/home/vagrant"
source_dir="/home/vagrant/sftpDir"
sftpFile="/tmp/tempfile"
echo "cd ${remote_dir}" >> ${sftpFile}
echo "mget * ${source_dir}" >> ${sftpFile}
echo "quit" >> ${sftpFile}
expect -c "
spawn sftp -P ${sftpPort} -o "BatchMode=no" -b "${sftpFile}" ${sftpUser}#${sftpHost}
expect -nocase \"*Password:\" { send \"${sftpPassword}\r\"; interact }
"
rm -rf ${sftpFile}
$ ./shellscript.sh user1 password#123 192.168.0.1 22
Here, we need to provide the argument with the script itself and here we are using the plain text format for password password#123
How can we use the encrypted password in the argument as this can be a risk to expose the password?
Is there any other approach to execute this scenario?
I am not able to find any approach to pass the encrypted password with SFTP login.

Testing account existence using expect

I have a list of 400 servers and I like to check unix account existence with expect to loop it
I wrote a bash script that uses expect command but it returns me error message that I don't understand the meaning
#!/bin/bash
fic_serv="test.txt"
echo "Passwd"
stty -echo
read -s passwd
stty echo
suffix="suffix"
account="acc"
for server in `cat $fic_serv`
do
prompt="[$acc#$server ~]$ "
expect -c "
spawn ssh -o StrictHostKeyChecking=no $account#$server.$suffix
expect "Password: "
send "$passwd\r"
expect $prompt
send "logout\r"
"
done
[acc#serv ~]$ couldn't read file "
send "passwd\r"
expect [acc#server ~]$
send "logout\r"
": no such file or directory
(I modified the value)
You should use while, not for, to parse files in Bash. Use a "redirect" to treat a file as standard input and read one line at a time.
while read server; do
...
done < $fic_serv
Your major problem is Expect interprets your "s as "end of script". Escape them, as in \", or use {}, as in:
expect -c "
spawn ssh -o StrictHostKeyChecking=no $account#$server.$suffix
expect {Password: }
send {$passwd\r}
expect $prompt
send {logout\r}
"
If you have 400 servers to manage, I strongly recommend you use ansible.
You could just put the list of hosts into a file, let's call it inventory, and run the following command:
ansible -i inventory -m shell -a "id acc" all
Using here-docs in the shell to embed code for another language is usually better than quoting hell, and sharing variables through the environment is easier and safer than parameter expansion:
export account passwd
while IFS= read -r server; do
export prompt="[$acc#$server ~]$ "
export host="$server.$suffix"
expect << 'END_EXPECT'
spawn ssh -o StrictHostKeyChecking=no $env(account)#$env(host)
expect "Password: "
send "$env(passwd)\r"
expect $env(prompt)
send "logout\r"
expect eof
END_EXPECT
done < "$fic_serv"
As shown, I like to indent the heredoc to make it more obvious.
And depending on the error message or login prompt, there can be more logic to indicate that the account name and/or password are incorrect.

can you run expect command in a bash shell script for loop [duplicate]

This question already has an answer here:
How to use a here document in a loop? [duplicate]
(1 answer)
Closed 5 years ago.
Goal: i have multiple linux based device on the network. i am trying to pass a password to my usernames login for multiple device. after i have logged in i want to extract information from the user. output that information to a log file on my machine. (typically a cat or tail of a log file) then i want to move on to the next device on the network. repeat until all on the network have been ran through.
My result:
35: xmgEXfinder.sh: Syntax error: end of file unexpected (expecting "done")
here is the code i currently have:
#!/bin/bash
IP=$(sudo arp-scan --localnet --numeric --ignoredups --quiet | grep -i ac:3f:a4 | awk '{print$1}')
Dumpdir='/home/location/logs/'
for x in $IP
do
/usr/bin/expect<<EOF
spawn ssh -i /home/location/id_rsa root#172.17.26.$x
expect "Enter passphrase for key 'id_rsa':"
send "password\n"
sleep 3
log_file XMGcheck.log
expect "~"
send "cat /reg/nv/system/serial|sed \x22s/\x24/,/g\x22 ; tail -n 50 /usr/log/ams.log|grep -i xmg|wc -l \n"
expect eof
EOF
done
When using a here document (<<EOF), the matching string must start at the beginning of a line. You have:
for x in $IP
do
/usr/bin/expect<<EOF
spawn ssh -i /home/location/id_rsa root#172.17.26.$x
expect "Enter passphrase for key 'id_rsa':"
send "password\n"
sleep 3
log_file XMGcheck.log
expect "~"
send "cat /reg/nv/system/serial|sed \x22s/\x24/,/g\x22 ; tail -n 50 /usr/log/ams.log|grep -i xmg|wc -l \n"
expect eof
EOF
done
You need:
for x in $IP
do
/usr/bin/expect<<EOF
spawn ssh -i /home/location/id_rsa root#172.17.26.$x
expect "Enter passphrase for key 'id_rsa':"
send "password\n"
sleep 3
log_file XMGcheck.log
expect "~"
send "cat /reg/nv/system/serial|sed \x22s/\x24/,/g\x22 ; tail -n 50 /usr/log/ams.log|grep -i xmg|wc -l \n"
expect eof
EOF
done
Or you need to use the <<-EOF format and then use tabs instead of spaces for identing.
You can read more in the Here Documents section of the bash man page.

calling expect script inside a while loop of shell script

I am trying to automate password-less ssh setup from master account to other slave accounts.I have a script named AddSSH.ksh which does this setup.When this script is run manually,it asks for same password same times,it basically copied keys using scp. All the slave accounts are saved in a file env.txt.So Now, I have a shell script(run.ksh) which reads the accounts from this file(env.txt) one by one and then uses expect script auto_ssh.ksh to handle the interaction and it enters the password accordingly.
env.txt
account1#machine1
account2#machine2
account3#machine3
account4#machine4
run.ksh:
#!/usr/bin/ksh
while read env
do
username=`echo $env | cut -d"#" -f1`;
hostname=`echo $env | cut -d"#" -f2`;
password='Unix_11'
ssh -n -o PasswordAuthentication=no ${env} ' ' 2>/dev/null
if [ $? -eq 0 ]; then
printf "\nConnection OK for : $env \n"
else
expect auto_ssh.ksh $username $hostname $password
fi
done<env.txt
auto_ssh.ksh:
#!expect
set timeout 6
set user [lindex $argv 0]
set machine [lindex $argv 1]
set password [lindex $argv 2]
spawn AddSSH.ksh $user $machine
expect "password:"
send "$password\r";
expect "password:"
send "$password\r";
interact
If a run the script auto_ssh.ksh like
./auto_ssh.ksh account1 machine1 password
It runs fine but when I call it inside shell script,this expect script exits at the second password.when I ran the shell script in debug mode, I see that instead of sending the password the second time it moves to reading the next env from env.txt and exits.
This is the line of the output in debug mode where it fails.
account1#machine1's password: + read env
Add exp_internal 1 to the expect script for additional debugging. I suspect you might need to refine what you expect: expect -re {password:\s*$}
If you don't need to actually interact with addSSH.ksh, change interact to expect eof
Why does your expect script have a ".ksh" extension?

How to pass commands through ssh to dd-wrt with a loop using a variable from a text file?

So far I have been able to create a small script using ssh combined with expect to pass a single command through to the dd-wrt router that I am working with. Now that this has been accomplished I wish to pass the same command several times through the ssh log-in instead of just one from a text file, if it is possible.
The other way to accomplish this would be to create a loop and pass the command over, and over again. I would have to use a variable though because the data for the command in the text file changes.
Here is what I have so far
#!/bin/expect -f
set password password
spawn ssh -l root x.x.x.x -p "command"
expect "*password:*"
send -- "$password\r"
send -- "\r"
From what I can see creating a loop would be the easiest way, but I may be wrong. NOTE that the "command & variables" that I want to pass through are in a separate text file, and that it needs to read/take each line and insert each one into the loop. Unless there is a way to send them through all at once.
#!/bin/expect -f
set password password
spawn ssh -l root x.x.x.x -p "command Variable" <-- Command to be passed through
expect "*password:*"
send -- "$password\r"
send -- "\r"
It is the same command every time in the text file, only the variable changes.
test.txt
command xxxxxxx
command xxxxxxx
command xxxxxxx
command xxxxxxx
Thank-you
I think you should do something like this.
start.sh
#!/bin/bash
password="your_password"
cat test.txt|while read line
do
for i in $line
do
ssh.exp $i $password
done
done
ssh.exp
#!/usr/bin/expect
set command [lrange $argv 0 0]
set password [lrange $argv 1 1]
spawn ssh -l root x.x.x.x -p "$command"
expect "*password:*"
send -- "$password\r"
send -- "\r"
And test.txt with list of your commands. Each on the different line.

Resources