I have the following expect script within a bash script and I'm unsure as to why the interact command is not working.
expect <<-EOS
#!/usr/bin/expect
set timeout $EXP_TIMEOUT
send_user "\n The timeout being used is $EXP_TIMEOUT \n"
send_user "\nLogging into remote host via SSH:\n"
spawn ssh -q -o ConnectTimeout=$SSH_TIMEOUT -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${hostname}
expect "*assword*"
send -- "$secret\r"
expect {
"*assword*" {
send \x03
puts "\nIncorrect Password\n"
}
"$prompt" {
send -- "/usr/seos/bin/sesu - $user\r"
expect "*assword*"
send -- "$secret\r"
expect "$prompt"
send -- "id\r"
expect "$prompt"
send -- "hostname -s\r"
interact
}
}
expect eof
EOS
Thank you all for your help!
interact can't let the user enter data via stdin because you are already redirecting stdin for the here document.
Instead, you can save your here document with all expansions to a variable, and then pass that to -c. Here's a simplified example:
script=$(cat << EOF
spawn vi
send "iHello $(hostname)"
interact
EOF
)
expect -c "$script"
Related
I'm using an expect script (within a bash script) to ssh into a remote server and execute a script, the local server will know that the remote server's script is finished as it (remote) will echo "finished" at the end of execution.
However, the script can be in one of three locations, so I've designed the following expect script, although in the code example it successfully executes the remote server script - it's unable to detect the printed "finished" and consequently hangs.
Local Server Expect Script:
versionScriptLoc1="/path/to/script1"
versionScriptLoc2="/path/to/script2"
versionScriptLoc3="/path/to/script3"
expect <<-EOS |& tee logfile.${hostname}.log
#!/usr/bin/expect
set timeout $EXP_TIMEOUT
puts "\nLogging into remote host via SSH..\n"
spawn ssh -q -tt -o ConnectTimeout=$SSH_TIMEOUT -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${hostname}
expect "*assword*"
send -- "$secret\r"
expect {
"*assword*" {
send \x03
puts "\nIncorrect Password\n"
exit 1
}
"$prompt" {
send -- "/usr/seos/bin/sesu - $user\r"
expect "*assword*"
send -- "$secret\r"
expect "$prompt"
send -- "${versionScriptLoc1}\r"
expect {
"finished" {
}
"No such file or directory" {
puts "Location 1 execution Failed"
send -- "${versionScriptLoc2}\r"
expect {
"No such file or directory" {
puts "Location 2 execution Failed"
send -- "${versionScriptLoc3}\r"
}
}
exp_continue
}
}
send -- "exit\r"
expect "$prompt"
send -- "exit\r"
}
}
expect eof
exit 0
EOS
I thought exp_continue would make the expect loop and look for "finished". In the above code, the script is located in location 2, it sucessfully triggers but even though "finished" is printed, it still hangs.
Any help in why it's hanging/ potentially a better design would be highly appreciated.
PS: I know I should be spawning the scripts instead of triggering them like a user, but I've tried in the past and it didn't work.
I'd recommend you don't write your expect code so deeply nested.
The problem is that, after running script2, you hit the exp_continue whether
you're successful or not. Too bad expect doesn't have an exp_break or a
goto.
So a bit of restructuring is needed. I'm using some boolean variables to
manage the control flow.
Since I've introduces some Tcl variables, it will make separating the shell
vars from the tcl vars more difficult. We'll turn the shell vars into
environment vars, and quote the heredoc:
# ...
export EXP_TIMEOUT SSH_TIMEOUT
export hostname user secret prompt
export versionScriptLoc1 versionScriptLoc2 versionScriptLoc3
expect <<'EOS' 2>&1 | tee "logfile.${hostname}.log"
set timeout $env(EXP_TIMEOUT)
puts "\nLogging into remote host via SSH..\n"
spawn ssh -q -tt -o ConnectTimeout=$env(SSH_TIMEOUT) -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $env(hostname)
expect "*assword*"
send -- "$env(secret)\r"
expect {
"*assword*" {
send \x03
puts "\nIncorrect Password\n"
exit 1
}
"$env(prompt)"
}
send -- "/usr/seos/bin/sesu - $env(user)\r"
expect "*assword*"
send -- "$env(secret)\r"
expect "$env(prompt)"
set run2 false
send -- "$env(versionScriptLoc1)\r"
expect {
"No such file or directory" {
puts "Location 1 execution Failed"
set run2 true
}
"finished"
}
set run3 false
if {$run2} {
send -- "$env(versionScriptLoc2)\r"
expect {
"No such file or directory" {
puts "Location 2 execution Failed"
set run3 true
}
"finished"
}
}
if {$run3} {
send -- "$env(versionScriptLoc2)\r"
expect {
"No such file or directory" {
puts "Location 3 execution Failed"
}
"finished"
}
}
send -- "exit\r"
expect "$env(prompt)"
send -- "exit\r"
expect eof
exit 0
EOS
I wrote an expect script to create a user in unix server. It basically connects via SSH to a server using my credential and su to root to do useradd and etc. (I understand there are other methods to accomplish the same but I am restricted with such settings and environment currently.)
set prompt "(%|#|>|\\\$ )"
set prompt [string trim $prompt]
spawn ssh -o StrictHostKeyChecking=no -l $my_user $hostname
expect "?assword: "
send "$my_pass\r"
expect -re $prompt
send "/usr/bin/su - \r"
expect "?assword: "
send "$root_pass\r"
expect -re $prompt
send "/usr/sbin/useradd -d /export/home/$user -m -s /bin/sh $user \r"
expect -re $prompt
send "/usr/bin/passwd $user \r"
expect "?assword:"
send "$new_pass\r"
expect "?assword:"
send "$new_pass\r"
send "exit\r"
expect -re $prompt
send "exit\r"
expect -re $prompt
However if I am stuck at adding a logic to check whether a user already exists in the system. If it were in bash, I would have added grep -c '^USER' /etc/passwd to check for the returned number. But I am unable to capture the return number from expect. There is so much information returned once I added:
send "egrep -c '^$user' /etc/passwd \r"
set output $expect_out(buffer)
Could someone tell me how to parse out all the output? I know it is a very simple task. It is probably a simple if ... then .. else but I am unable to produce anything useful in the past week.
Assuming your shell on the remote host is sh-based, and the remote system is linux:
set cmd [format {getent passwd %s >/dev/null 2>&1; [ "$?" -eq 2 ] && /usr/sbin/useradd -d /export/home/%s -m -s /bin/sh %s} $user $user $user]
send "$cmd\r"
I'm using format (known as sprintf in other languages) to ease quoting.
After spending another few hours studying tcl, this is working now.
I replace this block of code after I enter the root_pass.
send "\r"
expect -re $prompt
expect *;
send "egrep -c '^$user:' /etc/passwd \r"
expect -re $prompt
set output $expect_out(buffer);
set ans [ split $output \n ]
set var [lindex $ans 1]
if { $var >= 1 } {
puts "Found.\r"
send "exit\r"
expect eof
} else {
puts "Not found.\r"
send "/usr/sbin/useradd -d /export/home/$user -m -s /bin/sh $user \r"
.....
}
while [ $FileLine -le $FileListLines ];
do
# extract each line from FileList
str=$(tail -n+$FileLine ../$FileList | head -n1)
hostpath=$username#$ip:$str
export hostpath ip
expect -c '
spawn bash -c "scp -pr $env(hostpath) $env(ip)"
expect {
"(yes/no)?"{
send "yes\r"
expect "*?assword:*"
send "password\r"
}
"*?assword:*"{
send "password\r"
}
}
'
FileLine=$(( $FileLine + 1 ))
done
The above is a part of a bash script. The scp command in the expect block is not working, that is, files from the remote machine are not getting copied to the local machine.
The same scp command with the path and hostname is working fine when being run from the terminal.
Add expect eof at the end of the expect code otherwise the scp process would be killed right after the password is sent. (Also add a space between the pattern and { in the expect {} block though not sure if that's a problem.)
expect -c '
spawn bash -c "scp -pr $env(hostpath) $env(ip)"
expect {
"(yes/no)?" {
send "yes\r"
expect "*?assword:*"
send "password\r"
}
"*?assword:*" {
send "password\r"
}
}
expect eof
'
UPDATE
Just tried and "(yes/no)?"{ would not work. The space between the pattern and { is required so it should be "(yes/no)?" {.
The question is to preserve a variable and to perform actions after closing ssh within expect script inside bash.
This is what I`ve got so far:
echo "Getting package name..."
getPackageName=$(expect -c '
exp_internal 1
log_user 1
global expect_out
# puts "Getting package name..."
spawn ssh -q -o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no -o RSAAuthentication=no -l user 10.20.30.40
sleep 1
expect {
"*sword*" {
send "12341234\r"
}
timeout {
send_user "Error: timeout\n"
exit 1
}
}
expect {
"*user#*>*" {
# getting name of the latest modified file
send "cd /export/home/user/Releases/build/1.3.32.0 && find * -type f -printf '"'"'%T# %p\\n'"'"' | sort -n | tail -1 | cut -f2- -d\" \"\r"
}
timeout {
send_user "Error: timeout\n"
exit 1
}
}
expect {
"BUILD_MAIN*" {
# assigning value to variable
set result_lines [split $expect_out(0,string) \r\n]
set package_filename [lindex $result_lines 0]
puts "package_filename: $package_filename"
}
timeout {
send_user "Error: timeout\n"
exit 1
}
}
expect "*#"
send "exit\r"
# here I need to perform some actions on local machine after ssh logout
expect "Connection*"
send "export LATEST_BUILD=$package_filename\r"
send_user "Message sent to user"
')
So, in the bottom block I am trying to set environment variable (LATEST_BUILD) on the local machine after closing ssh, and also to paste there a value of variable (package_filename) which has been defined earlier during ssh session.
The point here is that I see the last "Message sent to user" in the output, but the previous send "export LATEST_BUILD=12345\r" obviously does not work.
#!/bin/bash
getPackageName=$(expect -c '
# A common prompt matcher
set prompt "%|>|#|\\\$ $"
# To suppress any other form of output generated by spawned process
log_user 0
### Spawning ssh here ###
spawn ssh user#xxx.xx.xxx.xxx
expect "password"
send "welcome!2E\r"
expect -re $prompt
# Your further code
send "exit\r"
expect eof
##### The below segment is not needed ######
##### if your intention is to get only the 'package_filename' value #####
# spawn bash
# expect -re $prompt
# send "export LATEST_BUILD=54.030\r"
# expect -re $prompt
# send "echo \$LATEST_BUILD\r"
# expect -re $prompt
# send "exit\r"
# expect eof
#
##### The End ######
# Enabling logging now ...
log_user 1
# Print only the value which you want to return
puts "$package_filename"
')
echo $getPackageName
eof is used to identify the end-of-file event i.e. closure of connection.
Note : The exported variable LATEST_BUILD only be available for the spawned bash session.
Update :
log_user is used to turn off/on the logging generated by Expect at any time.
log_user 0; # Turn off logging
log_user 1; # Turn on logging
I hope that your only intention is to get the package_filename. So, we don't even need to spawn bash shell. Instead, simply print the value at last, thereby making it to be available to the parent bash script.
I'm trying to use expect in an bash script to provide the SFTP password and put other commands.
I'am try to get the return code of binary SFTP .
My test script :
#!/bin/bash
USER=$1
HOST=$2
PASSWD=$3
PORT=$4
FILEIN=$5
FILEOUT=$6
ACTION=$7
CR_FTP=`/usr/bin/expect <<EOF | tee -a log.log
spawn sftp -v -oPort=$PORT $USER#$HOST
expect "password:"
send "$PASSWD\r"
expect "sftp>"
send "ls\r"
expect "sftp>"
send "$ACTION $FILEIN $FILEOUT\r"
expect "sftp>"
send "bye\r"
EOF`
echo " -------------------- $CR_FTP --------------------------"
...
send "bye\r"
expect eof
set details [wait]
puts "sftp exit status=[lindex $details 3]"
EOF`
See http://www.tcl.tk/man/expect5.31/expect.1.html