Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Getting the return codes from a exec in tcl using open “| exec”

Status
Not open for further replies.

vrghost

IS-IT--Management
Sep 28, 2004
6
GB
I am currently writing a bit of code in tcl that is suppose to sit between a scheduling system and a batch processing system to be able to get the scheduling software to fully understand what the batch processing is doing. The problem is that I needed the output from the command to be outputted to the terminal as soon as the information is written to stdout by the batch processing. Sorted this out by using open “| command to process 2>@ stdout” then using fileevent and vwait to write to screen. Now comes the problem, how to get the error code from a command that runs in an open pipe. [catch ] does not work as it seems like catch will only catch the output from open and not from the command it self. Tried to look into bgexec, but that is not available in the version of tcl we use (and I can not upgrade the tcl interpreter). Any suggestions.

Bellow is the source code, test is just a file that prints some stuff to stdout and stderr, with a five second delay, then returns a return code of four.
Test2 will try to call “test” using open | test …., then set a fileevent followed by a vwait.

Code for "test"
Code:
echo "I'm not really an abwroke you know"
sleep 5
echo "You said $1"
sleep 5
echo "Is that a bad word" >&2
sleep 5
echo "This conversation is over"

exit 4
I guess I do not really have to point out that this is actually not output from our batch management system, even though it may make slightly more sence then the batch management system

and the tcl code that I need to change to get the return code.
Code:
#!/apps/TclPro1.4/solaris-sparc/bin/expect -f


proc Write_output { pHandler } {
        global command_running
        flush $pHandler
        set line [ read -nonewline $pHandler]
        if {[ string length $line ] >= 0} {
                puts "OUTPUT   :\"$line\""
        }

        if {[eof $pHandler]} {
                close $pHandler
                set command_running "1"
        }
}


proc ParExec { command_line } {
        global command_running
        set command_running 0
        puts "Im going to exec $command_line"
        #set pHandler  [ open "|[info nameofexecutable] $command_line" ]
        set pHandler  [ open "| $command_line 1>@ stdout 2>@ stdout" r+ ]

        puts "$pHandler "
        fconfigure $pHandler -blocking 0 -buffering line -translation crlf
        fileevent $pHandler readable [ list Write_output $pHandler ]
        vwait command_running


}



ParExec "./test hello"
 
I don't really know much about it but this sounds like exactly what Expect is for.

_________________
Bob Rashkin
 
You could do it in expect I guess, I was about to say that that would be overly fiddly just to get a return code, and I still feel like that, even though I could probably write it using expect in fewer lines of code, and not having to go to a forum to fix it…

I still want to know if anyone knows if it is even possible doing this by using an open process. As I feel it is a bit neater then using expect, and part of me is starting to wonder if it is even possible to be honest.
 
Thank you very much for the link Bong, it did not get the problem solved, but it pointed me in the right direction I think.

If I have understood the problem correctly, it seems like the problem is an effect of the fileeven launching as a separate thread, and therefore there is no way for catch to get a hold of the return code. So basically, I open the program for running, then the fileevent starts as its own thread in the kernel, constantly looping looking for input until it hits the eof on output. The vwait only waits for the variable to change. So neither the file event or the file open fails due to the program failing, and the close file is only gor the pipe. Which it manages to close as such, what ever the underlying program is doing.

Found some stuff on bgerror, stating that you can define a bgerror proc, which will be called by tcl if the thread fails, but I could not get that to work at all.
Here is some additional information if anyone is interested:

The problem is I do not understand how you invoke the bgerror, feels like something in the bit that finds eof must also be able to see what the exit code was, but so far no luck.
 
Think I managed to fin a solution, it is not the perfect solution I do admit, but it does do the trick as a Fire and Forget threading solution for tcl. Instead of asking the process for its return code I used called ksh then whatever the command is followed by echo oarent_pid:returnCode:$? , then let the Write_output function look for that very line, and if found set retCode to that value.
Once pHandler finds eof the command_running will be set to retCode. And vwait will release, with the correct return code stored in command_running. If the code returns -2 that means that the process never found a return code. So something went really wrong.

Also, the ksh command is added and the command line + return $? Is encapsulated in \” \”, this as otherwise the function only the command will be processed in the opened ksh shell followed by eof, and the return $? will never run.

Here is the final code, if anyone needs it.
Code:
proc bgerror err {
        puts "ERROR   : bgexec thread failed with error code: $::errorCode"
        puts "ERROR   : Error message received\n $::errorInfo"
}

proc Write_output { pHandler } {
        global command_running contr_pid retCode
        flush $pHandler
        set line [ read -nonewline $pHandler]
        set errLine [ split $line ":" ]
        if {[ string compare [ lindex $errLine 1 ] "returnCode" ] == 0 } {
                if {[ string compare [ lindex $errLine 0 ] $contr_pid ] == 0 } {
                        set retCode [ lindex $errLine 2 ]
                }
        } elseif {[ string length $line ] >> 0} {
                puts "OUTPUT   : $line"
        }

        if {[eof $pHandler]} {
                close $pHandler
                set command_running $retCode
        }
}


proc ParExec { command_line } {
        global command_running contr_pid retCode
        set contr_pid [ pid ]
        set retCode -2
        set command_running -1
        set command_line "ksh -c \"$command_line ; echo $contr_pid:returnCode:$?\""
        #puts "Im going to exec $command_line"
        set pHandler  [ open "| $command_line 1>@ stdout 2>@ stdout" r+ ]
        fconfigure $pHandler -blocking 0 -buffering line -translation crlf
        fileevent $pHandler readable [ list Write_output $pHandler ]
        vwait command_running
        return $command_running
}



set bgRetCode [ ParExec "./test hello" ]
exit $bgRetCode



I am certain there is a nicer way of doing it, but this one does work somewhat at least.

Also, thanks Bong for your help, always nice to meet someone who is ready to share his skill and answer questions on these forums (know I should try my best to do more of that my self).


 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top