why this interact not exec? (expect) - linux

expect -c "
log_user 0
set timeout 60
spawn ssh ${user}#${ip} -p ${port} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
log_user 1
expect {
\"password:\" {send \"${password}\r\";exp_continue}
default {}
eof {exit 1}
}
set timeout -1
interact
"
when password not ok, eof effect.
when password is ok, interact not effect, i can't put command by keyboard, it is blocking, i just don't want use "#" or "$" to check login status, because my shell use zsh and prefix is "🚀 ~ ❯", please help me, thinks.

Related

Bash+Expect script not running properly in crontab

I have a bash script that is supposed to run periodically. the script should connect to a remote SFTP server and get a file from there.
Since this is a SFTP server I had to use expect with the bash script.
the script runs well when I run it manually but fails when running via crontab.
the problematic function is the get_JSON_file()
please advise...
this is the code:
#!/bin/bash
export xxxxx
export xxxxx
export PATH=xxxxx
check_if_file_is_open(){
while :
do
if ! [[ `lsof | grep file.txt` ]]
then
break
fi
sleep 1
done
}
get_JSON_file(){
/usr/bin/expect -f <(cat << EOF
spawn sftp -P port user#ip
expect "Password:"
send "password\r"
expect "$ "
send "get path/to/file/file.json\r"
send "exit\r"
interact
EOF
)
}
get_JSON_file
check_if_file_is_open
cp file.txt /path/to/destination/folder
Expect's interact works only when stdin is on a tty/pty but cron job is not running on tty/pty. So replace interact with expect eof (or expect -timeout 12345 eof if necessary).
That's a very awkward way to pass expect commands to the expect interpreter. Use a (quoted) heredoc instead, and you would drop the -f option for expect
get_JSON_file(){
/usr/bin/expect <<'EOF'
spawn sftp -P port user#ip
expect "Password:"
send "password\r"
expect "$ "
send "get path/to/file/file.json\r"
send "exit\r"
expect eof
EOF
}
The most important tip for debugging expect scripts is to invoke expect's debug output. While you're working out the kinks, use
expect -d <<'EOF'
and in the crontab, you'd want to redirect stderr to stdout so you get the debugging output
* * * * * /path/to/script.sh 2>&1
To run a function within a shell script, no parentheses should be used.
Your code then becomes:
#!/bin/bash
export xxxxx
export xxxxx
export PATH=xxxxx
function check_if_file_is_open(){
while :
do
if ! [[ `lsof | grep file.txt` ]]
then
break
fi
sleep 1
done
}
function get_JSON_file(){
/usr/bin/expect -f <(cat << EOF
spawn sftp -P port user#ip
expect "Password:"
send "password\r"
expect "$ "
send "get path/to/file/file.json\r"
send "exit\r"
interact
EOF
)
}
get_JSON_file
check_if_file_is_open
cp file.txt /path/to/destination/folder
create a new script with screen command and add it in crontab
new_script.sh
#!/bin/bash
cd script_path
screen -dm -S screen_name ./your_script.sh

Issues when spawn'ing ssh on expect ( {$var} vs. "$var" )

I have a expect script that so far it is working fine ... it spawn a ssh session over an existing ssh tunnel like this:
spawn ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o {ProxyCommand=nc -X 5 -x localhost:8888 %h %p} "$user_ip"
$user_iphas the username and destination IP, as expected by ssh, like username#IP
The problem is that sometimes, port 8888 is being used by another ssh tunnel, and every time this happens I have to tweak the code and change the tunnel port.
So, I am trying to send the port in a variable, to avoid touch the code all the time.
I am getting the tunnel port from command line, as:
set proxy_port [lindex $argv 2]
and then
spawn ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o {ProxyCommand=nc -X 5 -x localhost:"$proxy_port" %h %p} "$conn"
I see that proxy_portvariable is properly set, but when the script try to spawn the ssh, I get:
spawn ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o {ProxyCommand=nc -X 5 -x localhost:8888 %h %p} USERID#10.0.0.1
command-line line 0: garbage at end of line; "-o".
send: spawn id exp5 not open
while executing
"send "$pwd\r""
(file "./amm-qev.new.exp" line 36)
In the error above, I see that the port was properly replaced, but it is complaining about ssh syntax.
What is wrong there that my weak eyes are not catching ?
Tcl's {...} syntax is like single-quoted string ('...') in shell where the $var would not be expanded. You should use double quotes.
spawn ssh -o UserKnownHostsFile=/dev/null \
-o StrictHostKeyChecking=no \
-o "ProxyCommand=nc -X 5 -x localhost:$proxy_port %h %p" \
"$conn"

Unable to SSH a script of bash commands via expect

I'm attempting to push a single set of commands to multiple remote hosts. I can use the following on an individual basis:
ssh user#remoteHost "bash -s" <./commands.sh
I can put this into a loop, but then I'm stuck typing in the password n number of times. Disabling the password prompts in the SSH config files is not an option for me.
I've attempted to use expect within a loop, but I'm unable to get it working.
#!/bin/bash
HOSTS="host1 host2"
read -sp "Password: " PASSWORD
for HOST in $HOSTS; do
expect -c "
spawn /usr/bin/ssh user#$HOST "bash -s" <./commands.sh
expect_before {
"*yes/no*" {send "yes"\r;exp_continue}}
expect {
"*Password*" {send $PASSWORD\r;interact}}
exit"
done
I get the following error:
spawn /usr/bin/ssh root#host1 bash
expect: invalid option -- 's'
usage: expect [-div] [-c cmds] [[-f] cmdfile] [args]
spawn /usr/bin/ssh root#host2 bash
expect: invalid option -- 's'
usage: expect [-div] [-c cmds] [[-f] cmdfile] [args]
Any ideas? It appears as though expect is trying to interpret the bash commands. I'm unsure how to stop this.
Solution:
replace
spawn /usr/bin/ssh user#$HOST "bash -s" <./commands.sh
with
spawn sh -c {ssh root#$HOST 'bash -ls' < /tmp/commands.sh}
Final Code:
#!/bin/bash
HOSTS="host1 host2"
read -sp "Password: " PASSWORD
for HOST in $HOSTS; do
expect -c "
spawn sh -c {ssh root#$HOST 'bash -ls' < /tmp/commands.sh}
expect_before {
"*yes/no*" {send "yes"\r;exp_continue}}
expect {
"*assword*" {send $PASSWORD\r;interact}}
exit"
done
I'd suggest this:
#!/bin/bash
hosts=(host1 host2)
read -sp "Password: " password
for host in "${hosts[#]}"; do
env h="$host" p="$password" expect <<'END_EXPECT'
spawn sh -c "/usr/bin/ssh user#$env(h) 'bash -s' <./commands.sh"
expect {
"*yes/no*" {send "yes\r"; exp_continue}
"*Password*" {send "$env(p)\r"}
}
interact
END_EXPECT
done
notes
uses lower case variable names: leave upper case varnames for the shell's use
uses a quoted heredoc to contain the expect code
that lets you use single and double quotes within expect without having to worry about quoting hell in the shell
uses env to pass shell variables to expect via the environment
simplifies your expect statement
The danger with using
expect -c " ...; expect "*Password*" ..."
is that the inner double quotes get matched with the outer quotes, and are removed by the shell. That leaves *Password* as a bare glob that the shell can expand based on the files in your current directory and the shell settings. For example, create a file named "The Password" (with a space) and you'll get an
error.

Transmit commands via ssh with password using expect

I need to iterate on a sequence of servers(many type of servers, each type of servers stored in separate files), run on them some commands(stored in different files accordingly to server type) and log the output on the local machine, using ssh with password. Since sshpass, ssh key authentication is not a solution for my case please don't recommend them.
Here is my code:
#!/usr/local/bin/expect -f
#Set path to nodes files
NODES=nodes/*
#Set path to commands files
CMD=commands/*
for fn in $NODES
do
echo "Working in $fn"
for fc in $CMD
do
echo "Working in $fc"
if [ ${fn:6:3} = ${fc:9:3} ]
then
# read Nodes from file
while read fn_line; do
#extracting substrings of user, host, password separated by comma
IFS=', ' read -a uhp <<< $fn_line
#establish ssh session to the node
eval spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no ${uhp[0]}#${uhp[1]}
echo ${uhp[0]} ${uhp[1]} ${uhp[2]}
#use correct prompt
set prompt "assword:*"
interact -o -nobuffer -re $prompt return
send "${uhp[2]}\r"
set prompt ":|#|\\\$"
interact -o -nobuffer -re $prompt return
#execute and logging HC commands on the node
while read fc_line; do
#set prompt ":|#|\\\$"
#interact -o -nobuffer -re $prompt return
echo "$fc_line\r" >> logs/${fn:6:3}.log
$fc_line\r >> logs/${fn:6:3}.log
#interact -o -nobuffer -re $prompt return
done < $fc
done < $fn
fi
#cat $f
done
done
I know in my code the problem is combination of bash and expect interpreter. Please help me to do it only in expect style or show me how can i combine bash with expect. Other problem is the while after establishing of ssh connection, but i think it can be solved by storing it previously in an array and looping through it after establishing of ssh connection.
How about writing a small utility in expect which spawns ssh command:
#!/usr/bin/expect
set HOST [lindex $argv 0]
set PORT [lindex $argv 1]
set USER [lindex $argv 2]
set PASSWORD [lindex $argv 3]
set COMMAND [join [lrange $argv 4 end] " "]
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no -p $PORT $USER#$HOST $COMMAND
expect "assword:"
send "$PASSWORD\r"
expect eof
exit
and using it in a Bash script like this:
ssh-util <host> <port> <user> <pass> <command>
e.g.
ssh-util 10.0.0.10 22 root s3cr3t ls -la

How to one-line this "expect" command?

I would like to one-line this
#!/usr/bin/expect
spawn ssh-copy-id -i .ssh/id_dsa.pub root#testip
expect "Are you sure you want to continue connecting (yes/no)?"
send -- "yes\r"
expect eof
which I would assume should be
/usr/bin/expect -c 'expect "\n" { eval spawn ssh-copy-id -i .ssh/id_dsa.pub root#testip; expect "Are you sure you want to continue connecting (yes/no)?"; send -- "yes\r" }'
but it is not.
Can anyone see how it should be?
Maybe you will not need it anymore, but it should be like this:
/usr/bin/expect -c 'spawn ssh-copy-id -i .ssh/id_dsa.pub root#testip ; expect "Are you sure you want to continue connecting (yes/no)?" ; send -- "yes\r" ; expect eof'

Resources