Capture exit code of Expect script - error-handling

I have an expect script that will transfer the file from one server to another. This expect script is being kicked off by the shell script. The expect script can transfer the file if it is available in source and it will give a zero exit code. However, when the file is not available in source, the script will not transfer the file but it will still give a zero exit code.
How can I capture the correct exit code of the expect script.
test.sh:
#!/bin/sh
DT=`date +%Y%m%d`
RXHOME=/tmp'
x_server=A1234
x_file="test_${DT}.dat"
cd $RXHOME/scripts
./test.exp ${x_server} ${x_file}
if [ $? -ne 0 ]
then
echo "ERROR : Expect script encountered an error."
exit 1
fi
test.exp:
#!/usr/local/bin/expect -f
set timeout -1
set XSERVER [lindex $argv 0]
set XFILE [lindex $argv 1]
set uid "user"
set pwd "password"
set srcDir "/tmp/src"
set trgDir "/tmp/trg"
spawn /usr/ldir/bin/sftp ${uid}#${XSERVER}
expect -re "Are you sure you want to continue connecting (yes/no)?" {
send -- "yes\r"
expect -re "assword: "
send -- "$pwd\r"
} -re "assword: " {
send -- "$pwd\r"
}
expect -re "sftp> "
send -- "put ${srcDir}/${XFILE} ${trgDir}/test_file.txt\r"
expect "sftp> "
send -- "bye\r"
expect eof
catch wait result
exit [lindex $result 3]
Sample Output when there is no file available in source:
+ + date +%Y%m%d
DT=20160919
+ RXHOME=/tmp
+ x_server=A1234
+ x_file=test_20160919.dat
+ cd /tmp/scripts
+ ./test.exp A1234 test_20160919.dat
spawn /usr/ldir/bin/sftp user#A1234
Connecting to A1234...
user#A1234's password:
sftp> put /tmp/src/test_20160919.dat /tmp/trg/test_file.txt
File "/tmp/src/test_20160919.dat" not found.
sftp> bye
+ [ 0 -ne 0 ]
$
As you can see, even if there is no file transferred to target since it's not available in source, the script still exited with zero exit code.

You have to handle the error by yourself. Try like this:
set err 0
send -- "put ${srcDir}/${XFILE} ${trgDir}/test_file.txt\r"
expect {
-re "File .* not found" {
set err 1
exp_continue
}
"sftp> "
}
send -- "bye\r"
expect eof
exit $err

Why don't you just check that the file exists before spawning sftp?
set srcFile [file join $srcDir $XFILE]
if {! [file exists $srcFile]} {
puts stderr "File not found in source: $XFILE"
exit 1
}
spawn /usr/ldir/bin/sftp ${uid}#${XSERVER}
...

Related

Using Expect Script to read two columns from text file

I need to write a script on Expect to read a text file that contains two columns. Column one is the site name (dns) and the second is a router interface that will be used as the source-interface when I perform an FTP function from a Cisco router. Here is the content of the text file (myfile.txt):
site1 Gi0/0/0.44
site2 GigabitEthernet0/0
site3 GigabitEthernet0/0
site4 GigabitEthernet0/0/0
site5 GigabitEthernet0/0/0
Here is my code. It is only able to read the first column.
You will see a variable named "source-int", it has not been defined anywhere yet. I just put it in the script as a placeholder.
#!/usr/bin/expect
#
#
set workingdir cisco/rtr
puts stdout "Enter TACACS Username:"
gets stdin tacuserid
system stty -echo
puts stdout "Enter TACACS password:"
gets stdin tacpswd
puts stdout "\nEnter enable password:"
gets stdin enabpswd
system stty echo
#
set RTR [open "$workingdir/myfile.txt" r]
#
while {[gets $RTR dnsname] != -1} {
if {[ string range $dnsname 0 0 ] != "#"} {
send_user "The value of the router name is $dnsname\n"
set timeout 10
set count 0
log_file -a -noappend $workingdir/session_$dnsname\_$timestamp.log
send_log "### /START-SSH-SESSION/ IP: $dnsname # [exec date] ###\n"
spawn ssh -o StrictHostKeyChecking=no -l $tacuserid $dnsname
expect -ex "TACACS Password: "
send "$tacpswd\r"
expect ">"
send "enable\r"
expect "assword: "
send "$enabpswd\r"
expect "#"
send "config t\r"
expect "#"
send "no ip ftp passive\r"
expect "#"
send "ip ftp username anonymous\r"
expect "#"
send "ip ftp source-interface $souce-int\r"
expect "#"
send "ip ftp password cisco\r"
expect "#"
send "end\r"
expect "#"
send "copy ftp://10.10.10.1/out/customer/ACL.txt flash:\r"
expect "\?"
send "\r"
expect "#"
send "end\r"
expect "#"
send "exit\r"
send_log "\n"
send_log "### /END-SSH-SESSION/ IP: $dnsname # [exec date] ###\n"
log_file
sleep 5
}
}
exit
Any assistance is greatly appreciated.
Thanks!
You can use tcl's regexp command to split the line into 2 words.
if {[regexp {(\S+)\s+(\S+)} "$dnsname" dummy dns interface] == 1} {
puts "dns=$dns interface=$interface"
}
The regexp code \s matches a whitespace character, and \S a non-whitespace character. The + suffix matches 1 or more of the previous code. The () capture the wanted parts, and they are put by the command into the variables dns and interface.

Apache Airflow | How to check response coming from a command while using SSHOperator?

How to check response coming from a command while using SSHOperator?
t1 = SSHOperator(ssh_conn_id='conn_box2',
task_id='t1',
command='Rscript /code/demo.R',
do_xcom_push=True,
response_check=lambda response: True if "status:200" in response.text else False,
dag=dag
)
My R scripts returns status:200 if the execution goes well. And I want to track it. My task t1 should only complete if status is 200.
If R script returns status:300 its a failed one. But since the execution is completed without any error in UI task turns into green(which i don't want)
I code above is able to capture the response in xcom, but how do i validate it?
Try the following code:
bash_command = """
set -e;
Rscript /code/demo.R | grep 'status:200' &> /dev/null
if [ $? == 0 ]; then
echo "Task Successful"
else
echo "Task Failed"
exit 1
fi
"""
t1 = SSHOperator(ssh_conn_id='conn_box2',
task_id='t1',
command=bash_command,
dag=dag)
Alternatively, you can also use the following bash_command:
if Rscript /code/demo.R | grep -q 'status:200'; then
echo "Task Successful"
else
echo "Task Failed"
exit 1
fi
The SSHOperator does not have response_check parameter.
Airflow is unable to interpret exit command
[2021-09-07 06:36:58,164] {ssh.py:142} INFO - ps_count is 23, There might be some processes are running
[2021-09-07 06:36:58,169] {taskinstance.py:1455} ERROR - SSH operator error: error running cmd:
code:
set -e;
ps_count=jpsexec | grep -v execute | wc -l
if [ $ps_count -ne 0 ]
then
echo "ps_count is $ps_count, There might be some processes are running"
exit 1
else
echo "All processed were stopped..!"
fi

expect script reading empty line

I have a list of IPs that I am looping through to ssh into each of them and capture some logs. Currently, it will loop through all the IPs and do what I want, the issue occurs when it hits the last IP, after it is done with the last line, it attempts to spawn another empty line resulting in an error. (spawn ssh root#)
How can I prevent this error from happening?
myexpect.sh
set user user
set pass pass
set timeout 600
# Get the list of hosts, one per line #####
set f [open "/my/ip/list.txt"]
set hosts [split [read $f] "\n"]
close $f
# Iterate over the hosts
foreach host $hosts {
spawn ssh $user#$host
expect {
"connecting (yes/no)? " {send "yes\r"; exp_continue}
"assword: " {send "$pass\r"}
}
expect "# "
send "myscript.sh -x\r"
expect "# "
send "exit\r"
expect eof
}
myiplist.txt
172.17.255.255
172.17.255.254
...
error:
[root#172.17.255.255: ]# exit //last ip in the list
Connection to 172.17.255.255 closed.
spawn ssh root#
ssh: Could not resolve hostname : Name or service not known
expect: spawn id exp5 not open
Text files end with a newline
first line\n
...
last line\n
So when you read the whole file into a variable, then split on newlines, your list looks like this:
{first line} {...} {last line} {}
because there is an empty string after the last newline.
The idiomatic way to iterate over the lines of a file in Tcl/expect is this:
set f [open file r]
while {[gets $f host] != -1} {
do something with $host
}
close $f
Or, use the -nonewline option of the read command
set f [open file]
set hosts [split [read -nonewline $f] \n]
close $f
foreach host $hosts {...}

Ignore ORA-28011 in sqlplus in bash script

I am using sqlplus in a shell script and I am using WHENEVER SQLERROR EXIT 8 and WHENEVER OSERROR EXIT 9 so that I can catch errors using $?.
I will be putting this code on a server that I know gets the password expiry warning/error 'ORA-28011'.
My question is, will my script catch on 'ORA-28011' even though it isn't really an error? If so, how would I go about ignoring it?
My (simplified) code, if it helps:
[...]
CONNECTION_STRING=$USER/$PASS#$TNS
RESULT=$(sqlplus -s /nolog <<-EOF
WHENEVER OSERROR EXIT 9;
WHENEVER SQLERROR EXIT 8;
$OPTIONS
CONNECT $CONNECTION_STRING
$DB_SQL
COMMIT;
EOF)
RETURN_CODE=$?
echo "db_exec: Result -> $RETURN_CODE\n$RESULT"
if [ $RETURN_CODE -eq 0 ]
then
echo "$RESULT"
return 0
else
echo "db_exec: Failed"
return 1
fi
In case anyone is interested, I solved this problem.
ORA-28011 and ORA-28002 do not cause SQL*Plus to exit when using WHENEVER SQLERROR EXIT # or WHENEVER OSERROR EXIT # but will appear in the result. Therefore the code in my question will work, but I need to remove these errors. My updated code is below:
# Run the SQL with the options specified
RESULT=$(sqlplus -s /nolog <<-EOF
WHENEVER SQLERROR EXIT 4;
WHENEVER OSERROR EXIT 5;
SPOOL $TEMP_FILE;
$DB_OPTIONS
$DB_CONNECT
$DB_SQL
COMMIT;
EOF)
# Save the return code
RETURN_CODE=$?
# Log the result
echo "Result -> Code: $RETURN_CODE\n$RESULT" 1>&2
if [ $( grep -cE '^ORA-28002:|^ORA-28011:' $TEMP_FILE) -ge 1 ]
then
echo "Warning -> Password Expiry \n$(grep '^ORA-' $TEMP_FILE)" 1>&2
fi
# Check the return code and catch any SQL*Plus (SP2-) errors that might not have presented an error code
if [ $RETURN_CODE -eq 0 ] && [ $(grep -c '^SP2-[0-9][0-9][0-9][0-9]' $TEMP_FILE) -eq 0 ]
then
# Echo the result, but remove any lines regarding password expiry
echo "$RESULT" | grep -v "^ERROR:" | grep -v "^ORA-[0-9][0-9][0-9][0-9][0-9]"
rm $TEMP_FILE
echo "Success" 1>&2
return 0
elif [ $RETURN_CODE -eq 4 ]
then
echo "Failed -> SQL Error \n$(grep '^ORA-' $TEMP_FILE) $(grep '^SP2-' $TEMP_FILE)" 1>&2
return 4
elif [ $RETURN_CODE -eq 5 ]
then
echo "Failed -> OS Error \n$(grep '^ORA-' $TEMP_FILE) $(grep '^SP2-' $TEMP_FILE)" 1>&2
return 5
elif [ $(grep -c "^SP2-[0-9][0-9][0-9][0-9]" $TEMP_FILE) -ne 0 ]
then
echo "Failed -> SQL*Plus Error \n$(grep '^SP2-' $TEMP_FILE)" 1>&2
return 6
else
echo "Unknown error -> $RETURN_CODE\n$RESULT" 1>&2
return 3
fi

Connecting to switches using SSH and Expect

I'm a newbie to expect (lot's of shell programming though).
I'm trying to connect to a switch using expect to be able to dump it's running config. But somehow my send commands appear not be send to the switch.
Script:
#!/usr/bin/expect -f
proc connect {passw} {
expect {
"assword:" {
send "$passw\r"
expect {
"Prompt>" {
return 0
}
}
}
}
# timed out
return 1
}
set user [lindex $argv 0]
set passw [lindex $argv 1]
set host [lindex $argv 2]
#check if all were provided
if { $user == "" || $passw == "" || $host == "" } {
puts "Usage: <user> <passw> <host>\n"
exit 1
}
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no $user#$host
set rez [connect $passw]
if { $rez == 0 } {
send {enable\r}
send {show running\r}
exit 0
}
puts "\nError connecting to switch: $host, user: $user!\n"
exit 1
Alas when I run the script (trigged from a shell script) I can see it login to the switch and see the 'Prompt', but after that the script seems to exit, because I don't see the 'enable' and 'show running' commands being executed.
Anyone can show me how to fix this?
Richard.
You need add further expect statement after sending the commands using send.
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no $user#$host
set rez [connect $passw]
set prompt "#"
if { $rez == 0 } {
send {enable\r}
expect $prompt; # Waiting for 'prompt' to appear
send {show running\r}
expect $prompt; # Waiting for the prompt to appear
exit 0
}
Without the expect command after using send, then expect has nothing to expect from the ssh process and it will simply send the commands in a faster way which is why you are not able to get any output.
One more update :
You are using a separate proc for login purpose. If you are using a separate proc, then you are recommended to pass the spawn handle. Else, the script may fail. The reason is, instead of send/expect from the process, it will send/expect from the stdin. So, you should have used it like this,
proc connect {passw handle} {
expect {
-i $handle
"assword:" {
send "$passw\r"
expect {
-i $handle
"Prompt>" {
return 0
}
}
}
}
# timed out
return 1
}
set handle [ spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no $user#$host ]
set rez [connect $passw $handle ]
set prompt "#"
if { $rez == 0 } {
send -i $handle "enable\r"
expect -i $handle $prompt
send -i $handle "show running\r"
expect -i $handle $prompt
exit 0
}
We are saving the ssh spawn handle into the variable 'handle' and it is being used with send and expect and the flag -i is used to make them to wait for the corresponding process
#Dinesh has the main answer. A couple of other notes:
send {enable\r} -- since you use braces instead of quotes, you are sending the characters \ and r and not a carriage return.
you return 0 and 1 from connect like you're a shell programmer (not an insult, I am one too). Use Tcl's boolean values instead.
A quick rewrite:
#!/usr/bin/expect -f
set prompt "Prompt>"
proc connect {passw} {
expect "assword:"
send "$passw\r"
expect {
$::prompt {return true}
timeout {return false}
}
}
lassign $argv user passw host
#check if all were provided
if { $user == "" || $passw == "" || $host == "" } {
puts "Usage: [info script] <user> <passw> <host>"
exit 1
}
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no $user#$host
if {[connect $passw]} {
send "enable\r"
expect $::prompt
send "show running\r"
expect $::prompt
send "exit\r" # neatly close the session
expect eof
} else {
puts "\nError connecting to switch: $host, user: $user!\n"
}