Unable to print array in bash shell - bash

I have been using tcl shell on windows but while assisting someone on bash found a weird issue:
export SERVER_HOSTNAME=server1
export USERNAME=root
export PASSWORD=pswd
export LOG_FILE="a.log"
export LOG_PATH="$env(LOG_PATH)"
export log_search_pattern="Filterable_data"
/usr/bin/expect<<EOF
set lineNum {}
set SERVER_HOSTNAME "$env(SERVER_HOSTNAME)"
set USERNAME "$env(USERNAME)"
set PASSWORD "$env(PASSWORD)"
set LOG_FILE "$env(LOG_FILE)"
set log_search_pattern "$env(log_search_pattern)"
set timeout -1
spawn ssh "$USERNAME#$SERVER_HOSTNAME"
expect "assword:"
puts "$expect_out(buffer)"
parray expect_out
send "$PASSWORD\r"
expect "#"
puts "$expect_out(buffer)"
send "grep -n $log_search_pattern $LOG_PATH/$LOG_FILE|tail -1\r"
expect eof
EOF
Now the issue is that the following command:
puts "$expect_out(buffer)"
prints -> (buffer)
But prints the entire buffer contents
parray expect_out
I also tried adding following lines:
set a(1) val1
set a(2) val2
puts $a(1)
puts $a(2)
parray a
It printed:
(1)
(2)
a(1) = val1
a(2) = val2
I tried various combinations to get the puts $a(1) to print val1 but it always printed (1).
What's the correct method to do so?

Variables expand in HEREDOCs. If you want to avoid that you need to quote some or all of the opening HEREDOC marker (i.e. <<'EOF') (but not the closing one).
From Here Documents in the bash reference manual:
The format of here-documents is:
<<[-]word
here-document
delimiter
If any characters in word are quoted, the delimiter is the result of quote removal on word, and the lines in the here-document are not expanded.
So when you have:
puts "$expect_out(buffer)"
in the HEREDOC and expect expect to see that literal string it actually sees:
puts (buffer)
because the shell has removed the quotes and expanded the $expect_out variable for you.

Related

howto get exit code of script running within expect

for some time I am struggling to get the exit code of a script which I am running from within expect.
It is a BASH script and the questionable part looks like this:
expect -c "
log_file $LOG
spawn su - $ora_user
expect ""
send \"source $oraenv_binary\r\"
expect \"ORACLE_SID = \[$ora_user\] \?\"
send \"$SID\r\"
expect \"The Oracle base has been set to /oracle/$SID\"
send \"$execPATHroot/$subscript $args_subscript\r\"
expect ""
send \"echo \$?\r\"
expect -re \"(\\d+)\" {
set result \$expect_out(1,string)
}
send_user \"subscript exit code: \$result\"
log_file
send \"exit\r\"
expect ""
exit [lindex \$result 3]"
sub_rc=$?
Needed to say that this is one of many tries to get the code, however, unsuccessfully. I guess that my problem lies in incorrectly escaped characters or wrong use of brackets.....
When debugging, I am getting the following:
[336] oraenv_binary=/usr/local/bin/oraenv
[338] expect -c '
log_file /var/opt/osit/oracle/log/ora_sbp_patching_root.bash.log
spawn su - oracle
expect
send "source /usr/local/bin/oraenv\r"
expect "ORACLE_SID = \[oracle\] \?"
send "H95\r"
expect "The Oracle base has been set to /oracle/H95"
send "/opt/osit/oracle/bin/ora_sbp_patching_orausr.bash -s H95 -a CHECK -p /imports/e2r2s48ifde0002/CDSAP/DB/oracle/ORA19/SBP/SBP_1915_220419_202205 -h /imports/e2r2s48ifde0002/CDSAP/DB/oracle/ORA19/SBP/SBP_1915_220419_202205/README19P_2205-70004508.HTM -u oracle\r"
expect
send "echo $?\r"
expect -re "(\d+)" {
set result $expect_out(1,string)
}
send_user "subscript exit code: $result"
log_file
send "exit\r"
expect
exit [lindex $result 3]'
.....subscript runs here OK with exit code 0 in this case
-sh-4.2$ subscript exit code: decho $?
0
-sh-4.2$ exit
logout
expected integer but got ""
while executing
"exit [lindex $result 3]"
[357] sub_rc=0
It seems to me that the regex part "(\d+)" is not OK, but perhaps, it is completely a mess... :-)
Please help.
I have read and tried these recommendations:
Is there a way to expect output without removing it from the buffer in Tcl expect?
https://wiki.tcl-lang.org/page/How+Expect+can+capture+the+exit+code+from+a+remote+command
https://www.unix.com/shell-programming-and-scripting/144812-expect-script-obtain-exit-code-remote-command.html
You have this
expect -re "(\d+)" {
set result $expect_out(1,string)
}
send_user "subscript exit code: $result"
and we can see the output is
-sh-4.2$ subscript exit code: decho $?
0
Because the regular expression "(\d+)" is in double quotes, backslash substitutions will occur, and the pattern becomes (d+) which may not match (do you get a 10 second delay at that point?) -- I suspect this is why $result is empty.
Backslashes are prevalent in regular expressions. Using braces to quote them is the way to go:
expect -re {\d+} {set result $expect_out(0,string)}
Running your expect code with expect -d (or set exp_internal 1 in the code) emits very verbose expect debug output that is extremely useful to see how your patterns are matching (or not).
Using quoted shell heredocs is (IMO) preferable to using quoted strings to encapsulate code.
Consider
expect -c "
send_user \"my home is \$env(HOME)\\n\"
"
versus
expect << 'END_CODE'
send_user "my home is $env(HOME)\n"
END_CODE
With this technique, you pass shell variable to expect through the environment:
export ora_user=oracle
expect << 'END_EXPECT'
#...
spawn su - $env(ora_user)
END_EXPECT
thanks a lot for your answer Glenn, interesting points mentioned.
Regarding the braces versus double quotes - I have changed it like that, but no effect. I have those double quotes escaped by backslash within my code - I think the effect is the same, however, definitely it looks nicer to me and evidently it is safer.
I have played with the debug mode of expect - thanks for that, I can see much more info.
I have noticed that expect holds much more stuff than I "expected" :-)
==> this is just a snippet:
.\r\n-sh-4.2$ " (spawn_id exp7) match regular expression "\d+"? (No Gate, RE only) gate=yes re=yes
expect: set expect_out(0,string) "4"
expect: set expect_out(spawn_id) "exp7"
expect: set expect_out(buffer) "\r\n-sh-4"
can't read "expect_out(1,string)": no such element in array
while executing
"set result $expect_out(1,string)"
invoked from within
"expect -re {\d+} {set result $expect_out(1,string)}"
As you can see, when I am sending the subscript to be executed I am expecting "" i.e. nothing, just the new prompt line.
However, at that point expect is full of stuff, not at all blank - I think, I need to define the prompt exactly:
-sh-4.2$
and then I need to expect it, together with echoed exit code $? and somehow separate exit code integer to get what I want...... I will keep trying.
The final code which works is as follows (much of the credit goes to Glenn for his numerous advices) :
expect -c "
log_file $LOG
spawn su - $ora_user
expect -re {\$ $}
send \"PS1='>'\r\"
expect -re {>$}
send \"source $oraenv_binary\r\"
expect {ORACLE_SID = \[$ora_user\] ? }
send \"$SID\r\"
expect \"The Oracle base has been set to /oracle/$SID\"
send \"$execPATHroot/$subscript $args_subscript\r\"
expect { (subscript) ; exp_continue }
expect -re {>$}
send \"echo \$?\r\"
expect -re {(\d+)\r\n>$} {set result \$expect_out(1,string)}
send_user \"subscript exit code:\$result\n\"
log_file
send \"exit\r\"
expect \"logout\"
exit [lindex \$result 0]"
sub_rc=$?
echo sub_rc:$sub_rc
The first thing after spawn su - $ora_user is to set the prompt by send \"PS1='>'\r\" in order to make new lines with prompt less intrusive to expect.
Then after send \"$execPATHroot/$subscript $args_subscript\r\" I have used the fact, that I have written the subscript to have every line of output populated by (subscript) keyword. So while the subscript produces the output, the expect keeps going by exp_continue.
When the $subscript ends, the prompt > appears into which the expect sends echo $? to get exit code of the $subscript.
This appears on the screen as:
>echo $?
0
>
so the code should expect the integer, return and the new line with prompt - i.e. {(\d+)\r\n>$}. At that time the expect matches the output and expect_out(1,string) is correctly populated:
send: sending "echo $?\r" to { exp7 }
Gate keeper glob pattern for '(\d+)\r\n>$' is '*
>'. Activating booster.
expect: does "" (spawn_id exp7) match regular expression "(\d+)\r\n>$"? Gate "*\r\n>"? gate=no
echo $?
0
>
expect: does "echo $?\r\n0\r\n>" (spawn_id exp7) match regular expression "(\d+)\r\n>$"? Gate "*\r\n>"? gate=yes re=yes
expect: set expect_out(0,string) "0\r\n>"
expect: set expect_out(1,string) "0"
expect: set expect_out(spawn_id) "exp7"
expect: set expect_out(buffer) "echo $?\r\n0\r\n>"
Another thing to mention is \n within send_user \"subscript exit code:\$result\n\" so to have new line next..
The last change to the code in question is:
exit [lindex \$result 0]"
I have changed the index to 0 as variable result has just one item and index 0 stands for 1st item in the list.

Bash inconsistent escaping of asterisk * in expect?

I've got 2 expect commands, however, I don't understand the expansion that's going on. (In context, I have a script that connects to a server, downloads and blanks all the log files in a specified directory.)
expect -c "
set timeout 1
spawn scp user#hostname:/logdir/\*.log .
expect yes/no { send yes\n ; exp_continue }
expect password: { send $pass\n }
expect 100%
sleep 1
exit
";
In this command, expect displays the spawned command as spawn scp user#hostname:/logdir/*.log . Which means that the \ was removed.
expect -c "
set timeout 1
spawn ssh user#hostname {echo '' | tee /logdir/\*.log > /dev/null}
expect yes/no { send yes\n ; exp_continue }
expect password: { send $pass\n }
expect eof
";
In this command, expect displays the spawned command as spawn ssh user#hostname echo '' | tee /logdir/\*.log > /dev/null Which means that the \ was not removed. Why is it different? (If I don't escape the asterisk, like tee /logdir/*.log, it does work. But I don't understand what is working differently from the above case?)
That's how Tcl deals with backslashes.
[bash] # tclsh
% puts \*
*
% puts "\*"
*
% puts {\*}
\*
%
According to Tcl doc:
If a backslash (\) appears within a word then backslash substitution occurs. In all cases but those described below the backslash is dropped and the following character is treated as an ordinary character and included in the word. The following table lists the backslash sequences that are handled specially, along with the value that replaces each sequence.
[...]
Backslash substitution is not performed on words enclosed in braces, except for backslash-newline as described above.

Expect call shell script

I am trying to call a shell script and store the result in an expect variable. get_pw.sh accepts 2 args and decrypts the file using the provided md5hash. If I execute ./get_pw.sh file.test md5hash from the bash prompt it returns the password string as expected. When called from expect, the password does not get returned. The expect debug shows:
expect: does "" (spawn_id exp0) match regular expression "[^\s]"?
So it looks like the script is not returning the password string when called from expect. Relevant code:
#!/usr/bin/expect
send "./get_pw.sh file.test md5hash \r"
expect -re {[^\s]} {
set password $expect_out(0,string)
}
puts "The password is: $password"
You need to spawn a command first before you can send input and expect output from it.
To set an expect variable to the output of a command, use
set varname [exec command]
If you must do this with expect,
log_user 0
spawn -noecho get_pw.sh file hash
expect eof
set passwd [string trimright $expect_out(buffer) "\r\n"]
puts $passwd
Jens's answer looks pretty good by now ...

Bash- How to pack escape character into a special character ($) inside a string?

I have a variable in bash return from a function call getpassword() which return "apple$123123"
FOO=`getpassword`
I would like to use FOO variable which contains $ inside and pass into expect program
expect -c "\
set timeout 90
set env(TERM)
spawn rdesktop 192.168.11.1
expect \"Password:\"
send -- \"'${FOO}\n'\"
interact
"
}
There is an error coming out as $FOO contain dollar-sign
Password: can't read "123": no such variable
while executing
How can i solve this kind of problem? The way i think is that to pack escape character into FOO, using sed?
Thanks
You could try this:
# below is purposely on one line -- it sets the FOO env var
# only for the duration of the expect command.
FOO=$(getpassword) expect -c '
set timeout 90
set env(TERM) {are you missing something here?}
spawn rdesktop 192.168.11.1
expect "Password:"
send -- "$env(FOO)\r" # you send '\r' not '\n'
interact
'
Using single quotes make it easier to write (and read) the expect script (without all the backslashes). Testing:
$ getpassword() { echo 'abc$123'; }
$ FOO=$(getpassword) expect -c 'puts "pw=$env(FOO)"'
pw=abc$123
$ echo "> $FOO <"
> <

Run cat on remote computer and send output a variable using expect

I have a bash+expect script which has to connect via ssh to the remote comp (and i can't use ssh keys, need password identification in here), read the file there, find specific line with the "hostname" (like "hostname aaaa1111") and store this hostname into the variable to be used after while. How can i get the value of the "hostname" parameter? I thought that line content will be in $expect_out(buffer) variable (so i can scan it and analyze), but it's not. My script is:
#!/bin/bash
----bash part----
/usr/bin/expect << ENDOFEXPECT
spawn bash -c "ssh root#$IP"
expect "password:"
send "xxxx\r"
expect ":~#"
send "cat /etc/rc.d/rc.local |grep hostname \r"
expect ":~#"
set line $expect_out(buffer)
puts "line = $line, expect_out(buffer) = $expect_out(buffer)"
...more script...
ENDOFEXPECT
When i try to see line variable, i see only this: line = , expect_out(buffer) = (buffer) What is the right way to get the line from the file into the variable?
Or is it possible to open the file on the remote computer with expect, scan the file and get what i need to the variable?
Here http://en.wikipedia.org/wiki/Expect there is an example:
# Send the prebuilt command, and then wait for another shell prompt.
send "$my_command\r"
expect "%"
# Capture the results of the command into a variable. This can be displayed,
set results $expect_out(buffer)
seems that it doesn't work in this case?
You might just want to try and do it all from expect, as expect can control bash.
The following should do what you've described. Not sure if this is exactly what you are trying to do.
#!/bin/sh
# the next line restarts using tclsh \
exec expect "$0" "$#"
spawn bash
send "ssh root#$IP\r"
expect "password:"
send "xxxx\r"
expect ":~#"
send "cat /etc/rc.d/rc.local |grep hostname \n"
expect ":~#"
set extractedOutput $expect_out(buffer)
set list [split $extractedOutput "\n"]
foreach line $list {
set re {(?x)
.*
(*)
-S.*
}
regexp $re $line total extractedValue
if {[info exists extractedValue] && [string length $extractedValue] > 1} {
set exportValue $extractedValue
break # We've got a match!
}
send "exit\r" # disconnect from the ssh session
if {[info exists exportValue] && [string length $exportValue] > 1}{
send "export VARIABLE $exportValue\r"
} else {
send_user "No exportValue was found - exiting\n"
send "exit\r"
close
exit 1
}
# now you can do more things in bash if you like

Resources