I have a set of commands to install my product. The installation process shoots up a few prompts. The problem is we have 2 versions of the product and the prompts are different during installation of each of these.
I have put a if condition and added expect inside it. But the script keeps failing.
#!/bin/bash
#In this mode any command this script runs which returns a non-zero
exitcode - an error in the world of shell - will cause your script to
itself terminate immediately with an error.
set -e
set -x
#Below varibles is used to set flag '0=Stage Pass' and '1=Stage Fail'
f="Fail" gotoStage="Pass" sdiclistage="Pass"
# An error exit function
error_exit()
{
echo "$1" 1>&2
exit 1
}
# Using error_exit : Goto data insight folder
if cd /opt/datainsight/; then
echo "inside /opt/datainsight directory."
else
{
error_exit "Cannot change directory! Aborting."
gotoStage=$f;
}
fi
echo "Worker ip: $1"
echo "NMS ip: $2"
echo "DI tar name: $3"
echo "DI version: $4"
echo "executing sdi-cli cluster command"
if [[ "1.5.3" == "1.5"* ]];
then {
echo "inside compa"
/usr/bin/expect <<EOD
set timeout 600
spawn sdi-cli cluster provision -y
expect {
timeout { send_user "\nFail to get password promt\n"; exit
1 }
"password for pwd:"
}
send "pwd\r";
expect {
timeout { send_user "\n Fail to continue connecting \n";
exit 1}
"Are you sure you want to continue connecting"
}
send "yes\r"
expect {
timeout { send_user "\n Fail to get second password promt
\n"; exit 1}
"password for pwd:"
}
send "pwd\r"
expect {
timeout { send_user "\n Error for completing provision\n";
exit 1}
"Provisioning complete."
}
EOD
echo "\nOutside sdi-cli cluster provision\n";
}
else
{
echo "In else"
/usr/bin/expect <<EOD
set timeout 600
spawn sdi-cli cluster provision -y
expect {
timeout { send_user "\n Fail to continue connecting
\n"; exit 1}
"Are you sure you want to continue connecting"
}
send "yes\r"
expect {
timeout { send_user "\n Error for completing provision\n"; exit
1}
"Provisioning complete."
}
EOD
echo "\nOutside sdi-cli cluster provision\n";
}
fi
if [ $? = 1 ]; then
{
echo "\nError for executing sdi-cli script\n";
sdiclistage=$f;
}
else
{
echo "\n sdi-cli script executed successfully\n";
}
fi
if [ $gotoStage = "Pass" ] && [ $sdiclistage = "Pass" ] ; then
{
echo "PASS" >> result.txt
echo "\n All installation stages Passed\n ";
}
else
{
echo "FAIL" >> result.txt
echo "\n All/Some installation stages failed \n";
}
fi
sleep 300 #5min
ui=$(kubectl get pods |grep ui |awk '{print $1}') #get ui pod name
kubectl exec -it $ui cat GITINFO.json >>buildinfo.txt
echo "\n Build info file created successfully";
This always fails with
echo 'DI version: 1.5.3'
+ echo 'executing sdi-cli cluster command'
./di_install14_deepika.sh: line 134: warning: here-document at line 41 delimited by end-of-file (wanted `EOD')
./di_install14_deepika.sh: line 135: syntax error: unexpected end of file
Related
I have a bash expect script like this, that I use for some operations on Jamf:
spawn firmwarepasswd -setpasswd
expect {
"Enter password:" {
send "$oldpass\r"
exp_continue
}
"Enter new password:" {
send "$newpass\r"
exp_continue
}
"Re-enter new password:" {
send "$newpass\r"
exp_continue
}
}
If the password fails, the script will not exit and jamf will keep trying to execute it. How can I get it to return and exit when the password is wrong?
I don't know Jamf, but I do have a little example for you:
function _cmd {
local cmd="${#?No command?}"
echo -ne "Testing $cmd\t: "
expect 2>&1 <<-EOF
set timeout -1
spawn ${cmd}
expect eof
catch wait result
exit [lindex \$result 3]
EOF
echo $?
}
function _ssh {
local status="${#?No command?}"
read -sp "remote password? " remote_pass
echo -ne "\nTesting ssh\t: "
expect 2>&1 <<-EOF
set timeout -1
spawn ssh $USER#127.0.0.1
expect {
"yes/no" { send "yes\r"; exp_continue }
"*password: " { send "${remote_pass}\r" }
}
expect "*#" { send "exit $status\r" }
expect eof
catch wait result
exit [lindex \$result 3]
EOF
echo $?
}
_cmd false
_cmd true
_ssh 3
exit 0
The last part after expect eof makes sure that the exit status is shared. The _ssh command will exit with status 3.
TARGET
Until some sub-task in script completes its job:
stop echo;
disable cursor;
consume all user input;
do not block interrupts (Ctrl+C, etc.).
WHAT HAVE DONE
For now, using this answer, I create few functions for that, here they are:
function hide_input()
{
if [ -t 0 ]; then
stty -echo -icanon time 0 min 0
fi
}
function reset_input()
{
if [ -t 0 ]; then
stty sane
fi
}
function stop_interactive()
{
trap reset_input EXIT
trap hide_input CONT
hide_input
tput civis
}
function start_interactive()
{
tput cnorm
reset_input
}
function consume_input()
{
local line
while read line; do line=''; done
}
And here is the way they are used:
echo "Warn the user: the job will be started."
read -p "Continue? [yes/no] > "
if [ "$REPLY" == "yes" ]; then
stop_interactive # <== from here all input should be rejected
echo "Notify the user: job starting..."
# << ------ here goes some long job with output to terminal ------>
echo "Notify the user: job done!"
consume_input # <== here I trying to get all user input and put nowhere
start_interactive # <== from here restore normal operation
else
echo "Aborted!"
exit 0
fi
THE PROBLEM
The problem is: current 'solution' does not work. When I press keys during long running job they appears on the screen, and pressing 'Enter' ruins all output with cursor movements. Furthermore, after 'start_interactive' function call all input appears on the terminal screen.
What is the right solution for this task?
SOLUTION
Final working solution is:
function hide_input()
{
if [ -t 0 ]; then
stty -echo -icanon time 0 min 0
fi
}
function reset_input()
{
if [ -t 0 ]; then
stty sane
fi
}
function consume_input()
{
local line
while read line; do line=''; done
}
function stop_interactive()
{
trap reset_input EXIT
trap hide_input CONT
hide_input
tput civis
}
function start_interactive()
{
consume_input
trap - EXIT
trap - CONT
tput cnorm
reset_input
}
echo "Warn the user: the job will be started."
read -p "Continue? [yes/no] > "
if [ "$REPLY" == "yes" ]; then
stop_interactive
echo "Notify the user: job starting..."
do_the_job &
pid=$!
while ps $pid > /dev/null ; do
consume_input
done
echo "Notify the user: job done!"
start_interactive
else
echo "Aborted!"
exit 0
fi
Based in your question, there are many questions "why" if I look at your code. If you do not want to change the behavior of ^C etc., then don't use traps. All your functions test whether the file descriptor 0 is a terminal. Do you plan to use the script in a pipe? Also, your consuming of user input will continue until end-of-file, so the script may never end.
Based on your question, I would write something like this:
echo "Warn the user: the job will be started."
read -p "Continue? [yes/no] > "
if [ "$REPLY" == "yes" ]; then
stty -echo
echo "Notify the user: job starting..."
program_to_execute &
pid=$!
while ps $pid > /dev/null ; do
read -t 1 line
done
echo "Notify the user: job done!"
else
echo "Aborted!"
fi
stty sane
I would like the following expect script to set a variable in the main bash script. In other words I would like to set $SSH_SUCCESS to a pass/fail string. I have tried using $expect_out and $::env(SSH_SUCCESS) but have been unsuccessful. How do I set a bash variable from expect?
expect << EOF
log_user 0
log_file $TEST_LOG
set timeout 5
spawn ssh root#$RADIO_IP
.....
....expect script, echoing the return of an SSH command...
send "echo\$?\n"
expect {
"0" {
send_user "SSH test: PASSED\r"
SSH_SUCCESS="PASSED"
}
"1" {
send_user "SSH test: FAILED\r"
SSH_SUCCESS="FAILED"
}
sleep 1
send_user "\n"
exit
EOF
echo $SSH_SUCCESS
I don't know Expect, but I think it's something like this
SSH_SUCCESS=$(expect <<EOF
...
expect {
"0" {
puts "PASSED"
}
"1" {
puts "FAILED"
}
...
EOF
)
echo $SSH_SUCCESS
There is no way to set a variable.
This is another way to determine the exit status of Expect.
expect << EOF
log_user 0
log_file $TEST_LOG
set timeout 5
spawn ssh root#$RADIO_IP
.....
....expect script, echoing the return of an SSH command...
send "echo\$?\n"
expect -re "\[0-9\]+$"
#set latest command exit status to valiable "exit_code"
set exit_status \$expect_out(0,string)
if { \$exit_status == 0 } {
send_user "SSH test: PASSED\r"
} else {
send_user "SSH test: FAILED\r"
}
sleep 1
send_user "\n"
# exit expect as latest command exit status
exit \$exit_status
EOF
if [ $? -eq 0 ];then
SSH_SUCCESS="PASSED"
else
SSH_SUCCESS="FAILED"
fi
echo ${SSH_SUCCESS}
I'm using expect to establish a persistent ssh connection
set stb_ip [lindex $argv 0]
spawn -noecho ssh -o ControlMaster=auto -o ControlPath=/tmp/ssh-master-%r#%h:%p -o ConnectTimeout=1 -O exit root#$stb_ip
spawn -noecho ssh -fN -o ControlMaster=yes -o ControlPath=/tmp/ssh-master-%r#%h:%p -o ControlPersist=360 -o ConnectTimeout=1 root#$stb_ip
expect {
-re ".*password:" {send "\r"; interact}
}
Unfortunately I can't manage to put this into background, I triend expect_background, fork+disconect but no luck.
Even triend running this from another script with
excpect -f script.ex param1 param2 &
but with no luck. Any help ?
Heres a proc you can use to login and then interact. I have not tried it with all the ssh Options but I don't see any reason it would not work. Since I use the 8.6 command "try" this is for 8.6 tcl only but you can modify the try to use catch for earlier versions pretty easily.
#!/bin/sh
# the next line restarts using wish \
exec /opt/usr8.6b.5/bin/tclsh8.6 "$0" ${1+"$#"}
if { [ catch {package require Expect } err ] != 0 } {
puts stderr "Unable to find package Expect ... adjust your auto_path!";
}
proc login { user password cmdline } {
set pid [spawn -noecho {*}$cmdline ]
set bad 0;
set done 0;
exp_internal 0; # set to one for extensive debug
log_user 0; # set to one to watch action
set timeout 10
set passwdcount 0
set errMsg {}
# regexp to match prompt after successfull login you may need to change
set intialpromptregexp {^.*[\$\#>]}
expect {
-i $spawn_id
-re $intialpromptregexp {
send_user $expect_out(0,string);
set done 1
}
-re {.*assword:} {
if { $passwdcount >= 1 } {
lappend errMsg "Invalid username or password for user $user"
set bad 1
} else {
exp_send -i $spawn_id "$password\r"
incr passwdcount
exp_continue;
}
}
-re {.*Host key verification failed.} {
lappend errMsg "Host key verification failed."
set bad 1
}
-re {.*onnection refused} {
lappend errMsg "Connection Refused"
set bad 1
}
-re {.*onnection closed by remote host} {
lappend errMsg "Connection Refused"
set bad 1
}
-re {.*Could not resolve hostname (.*): Name or service not known} {
lappend errMsg "Host invalid: Could not resolve hostname in $cmdline : Name or service not known"
set bad 1
}
-re {\(yes/no\)\?} {
exp_send -i $spawn_id "yes\r"
exp_continue;
}
timeout {
lappend errMsg "timeout \[[expr { [clock seconds] - $start } ]\]"
set bad 1
}
fullbuffer {
lappend errMsg " buffer is full"
exp_continue;
}
eof {
puts "Eof detected "
set bad 1
set done 1 ;
}
}
if { $bad } {
throw CONNECTION_ERROR [join $errMsg \n ]
}
return $spawn_id
}
# get login information in somehow in this case from command line
set user [lindex $argv 0]
set passwd [lindex $argv 1]
set host [lindex $argv 2 ]
try {
set spawn_id [login $user $passwd "ssh -X $user#$host" ]
} trap CONNECTION_ERROR a {
puts "CONNECTION ERROR: $a"
exit 1
}
interact
set exitstatus [ exp_wait -i $spawn_id ];
catch { exp_close -i $spawn_id };
# more clean up here if you want
Assuming your script works in the "foreground"...
nohup expect -f script.ex param1 param2 &
Here's a script I made a long time ago. It does what you want but doesn't use Expect (which I loathe). I don't use it any more, I can't guarantee that it even still works but it should get you going.
#!/bin/sh
#
# Persistent ssh: Automatically create persistent ssh connections using OpenSSH 4.0
[ -z "$USER" ] && USER=`whoami`
MASTERSOCKDIR="/tmp/pssh-$USER"
MASTERSOCK="$MASTERSOCKDIR/%r-%h-%p"
# Check if master is running
output=`ssh -o ControlPath="$MASTERSOCK" -O check "$#" 2>&1`
if [ $? -ne 0 ]; then
case "$output" in
Control*)
# Master not running, SSH supports master
# Figure out socket filename
socket=`echo "$output" | sed -n -e 's/[^(]*(\([^)]*\)).*/\1/p' -e '1q'`
# Clean old socket if valid filename
case "$socket" in
"$MASTERSOCKDIR"/*) rm -f "$socket" >/dev/null 2>&1 ;;
esac
# Start persistent master connection
if [ ! -d "$MASTERSOCKDIR" ]; then
mkdir "$MASTERSOCKDIR"
chmod 700 "$MASTERSOCKDIR"
fi
ssh -o ControlPath="$MASTERSOCK" -MNf "$#"
if [ $? -ne 0 ]; then
echo "$0: Can't create master SSH connection, falling back to regular SSH" >&2
fi
;;
*)
# SSH doesn't support master or bad command line parameters
ERRCODE=$?
echo "$output" >&2
echo "$0: SSH doesn't support persistent connections or bad parameters" >&2
exit $ERRCODE
;;
esac
fi
exec ssh -o ControlPath="$MASTERSOCK" -o ControlMaster=no "$#"
To execute an expect script in the background use expect eof at the end of your expect script. In case you have defined interact remove it from your script.
Changed script of OP
set stb_ip [lindex $argv 0]
spawn -noecho ssh -o ControlMaster=auto -o ControlPath=/tmp/ssh-master-%r#%h:%p -o ConnectTimeout=1 -O exit root#$stb_ip
spawn -noecho ssh -fN -o ControlMaster=yes -o ControlPath=/tmp/ssh-master-%r#%h:%p -o ControlPersist=360 -o ConnectTimeout=1 root#$stb_ip
expect {
-re ".*password:" {send "\r"; interact}
}
expect eof
An other example [1].
#!/usr/bin/expect -f
set host "host"
set password "password"
spawn ssh $host
expect {
"(yes/no)?" {
send -- "yes\r"
exp_continue
}
"*password:*" {
send -- "$password\r"
}
}
##Removing this:
#interact
##And adding this:
expect eof
exit
I'd like to be able to put log messages in the middle of bash functions, without affecting the output of those very functions. For example, consider the following functions log() and get_animals():
# print a log a message
log ()
{
echo "Log message: $1"
}
get_animals()
{
log "Fetching animals"
echo "cat dog mouse"
}
values=`get_animals`
echo $values
After which $values contains the string "Log message: Fetching animals cat dog mouse".
How should I modify this script so that "Log message: Fetching animals" is outputted to the terminal, and $values contains "cat dog mouse"?
choroba's solution to another question shows how to use exec to open a new file descriptor.
Translating that solution to this question gives something like:
# Open a new file descriptor that redirects to stdout:
exec 3>&1
log ()
{
echo "Log message: $1" 1>&3
}
get_animals()
{
log "Fetching animals"
echo "cat dog mouse"
}
animals=`get_animals`
echo Animals: $animals
Executing the above produces:
Log message: Fetching animals
Animals: cat dog mouse
More information about using I/O redirection and file descriptors in Bash can be found at:
Bash Guide for Beginners, section 8.2.3, Redirection and file descriptors
Advanced Bash-Scripting Guide, Chapter 20, I/O Redirection
You can redirect the output to the sdterr error file on file handle 2 using >&2
example :
# print a log a message
log ()
{
echo "Log message: $1" >&2
}
get_animals()
{
log "Fetching animals"
echo "cat dog mouse"
}
values=`get_animals`
echo $values
the `` only take the output on stdout, not on stderr. The console on the other hand displays both.
If you really want the Log message on the stdout you can redirect error back to stdout after assigning to the variable :
# print a log a message
log ()
{
echo "Log message: $1" >&2
}
get_animals()
{
log "Fetching animals"
echo "cat dog mouse"
}
values=`get_animals` 2>&1
echo $values
#
#------------------------------------------------------------------------------
# echo pass params and print them to a log file and terminal
# with timestamp and $host_name and $0 PID
# usage:
# doLog "INFO some info message"
# doLog "DEBUG some debug message"
# doLog "WARN some warning message"
# doLog "ERROR some really ERROR message"
# doLog "FATAL some really fatal message"
#------------------------------------------------------------------------------
doLog(){
type_of_msg=$(echo $*|cut -d" " -f1)
msg=$(echo "$*"|cut -d" " -f2-)
[[ $type_of_msg == DEBUG ]] && [[ $do_print_debug_msgs -ne 1 ]] && return
[[ $type_of_msg == INFO ]] && type_of_msg="INFO " # one space for aligning
[[ $type_of_msg == WARN ]] && type_of_msg="WARN " # as well
# print to the terminal if we have one
test -t 1 && echo " [$type_of_msg] `date "+%Y.%m.%d-%H:%M:%S %Z"` [$run_unit][#$host_name] [$$] ""$msg"
# define default log file none specified in cnf file
test -z $log_file && \
mkdir -p $product_instance_dir/dat/log/bash && \
log_file="$product_instance_dir/dat/log/bash/$run_unit.`date "+%Y%m"`.log"
echo " [$type_of_msg] `date "+%Y.%m.%d-%H:%M:%S %Z"` [$run_unit][#$host_name] [$$] ""$msg" >> $log_file
}
#eof func doLog
You could redirect log output to the standard error stream:
log()
{
echo 1>&2 "Log message: $1"
}