0

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

glenn jackman
  • 238,783
  • 38
  • 220
  • 352
dukul
  • 3
  • 3

3 Answers3

0

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
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • thanks a lot for your answer and 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. – dukul Jan 13 '23 at 12:57
  • Within the double quoted code block, you'll need to do `expect -re \"(\\\\d+)\"` – glenn jackman Jan 13 '23 at 13:18
  • Thanks! The 10 seconds delay ceased, and the whole run is much swifter, however I am still not getting what I want. I have searched for similar stuff here and elsewhere, and expecting of the prompt is mentioned very often. So I have put `expect \"\\\\$ \"` so to expect the prompt and empty space after subscript run. It is nicer, but not OK still. `-sh-4.2$ echo $? 0 -sh-4.2$ exit logout expected integer but got "" while executing "exit [lindex $result 3]"` – dukul Jan 13 '23 at 13:39
0

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.

dukul
  • 3
  • 3
  • Yes, `expect -re {\d+}` is matching the "4" in the prompt. `spawn su ...` then `expect -re {\$ $}` to match the prompt, then `send "PS1='>'\r"` to make the prompt less intrusive: `expect -re {>$}` – glenn jackman Jan 13 '23 at 15:05
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 15 '23 at 10:16
  • @glennjackman, thanks a lot. This setting up PS1 removed some confusion from the expect.....so I am getting closer to the wanted result :-) While debugging,...it seems like expecting new prompt `expect -re {>$}` after subscript ends, is not working. – dukul Jan 16 '23 at 13:36
  • @glennjackman, It seems like the expect is not waiting for the subscript to finish with prompt. The expect tries to match `{\d}` right after send the subscript to be executed, which results to match first number it finds within subscript parameters, unfortunately. – dukul Jan 16 '23 at 13:55
  • well, it AMOST works now, also I think my current solution is quite rude: my subscript run has the string `(subscript)` in every line. So in order to sort of wait for the subscript to finish, I have put right after `send \"$execPATHroot/$subscript $args_subscript\r\"` one extra line: `expect { (subscript) ; exp_continue }` with the aim to recognize every such a line as "unimportant" so to not filling `expect_out` with confusing stuff. The rest is still little bit confused: `>echo $? 0 >subscript exit code: 0exit` – dukul Jan 16 '23 at 14:18
  • You could match the digits followed by crnl followed by the prompt: that would limit the digits that get matched: `expect -re {(\d+)\r\n\S+\$ $}` -- expect uses `\r\n` for all newlines. – glenn jackman Jan 16 '23 at 15:09
  • Thanks, this helped. I have changed it little bit to match `send "PS1='>'\r"` as you have advised me earlier, so: `expect -re {(\d+)\r\n>$} {set result \$expect_out(1,string)}`. Now the expect matches the output nicely: `expect: does "echo $?\r\n0\r\n>" (spawn_id exp7) match regular expression "(\d+)\r\n>$"? Gate "*\r\n>"? gate=yes re=yes` while populating expect_out with exit code `expect: set expect_out(1,string) "0"`, that's great. However, the variable result is not populated, I am investigating why.... – dukul Jan 17 '23 at 08:43
  • so, the variable `result` is really being populated correctly: `subscript exit code:0`, however when exiting expect with `exit [lindex $result 3]`, the following complain appears: `expected integer but got "" while executing "exit [lindex $result 3]"`. The variable result is defined by: `{set result \$expect_out(1,string)}`, but the expect is then sending other commands and it seems to me, that by changing `expect_out(1,string)` on the way, the variable results in change too - I cannot think otherwise....so I need somehow to keep the variable value till exit. – dukul Jan 17 '23 at 12:52
  • well this TCL is funny :) I have done rtfm by reading [https://wiki.tcl-lang.org/page/exit] and [https://wiki.tcl-lang.org/page/lindex] and I have realized that the number 3 in `exit [lindex \$result 3]` is wrong as it is an index in the list. The list of variable `result` consist of just one item, so I need to take index 0 - i.e. the 1st item: `exit [lindex \$result 0]` .... I will write my answer to my question with the explanation why I did what. – dukul Jan 17 '23 at 14:00
0

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.

dukul
  • 3
  • 3