Tk Library Source Code

Artifact [5280a33ab1]
Login

Artifact 5280a33ab193256cb680c1efb2804f3cbbd0362a:

Attachment "execCmd.txt" to ticket [429388ffff] added by nobody 2001-06-02 00:59:33.
proc execCmd {commandstring {args ""}} {
    # The execCmd procedure replaces Tcl "exec" and "catch {exec}" commands
    # in order to allow external command to be executed with the flexibility
    # of obtaining the return code of the command and processing the
    # standard error and output messages of the command independently.
    #
    # Standard output and error messages may be reported to stdout/stderr
    # as the command runs, may be sent to same or different channel pointers,
    # or may be saved in the same or different variables.
    #
    # Standard error/output messages do not imply success or failure of
    # the command. The procedure may wait for the command to complete or
    # may return immediately and inform the calling proc later when the
    # command completes.
    #
    # Syntax:
    #   execCmd "commandstring" [-arg1 value1] [-arg2 value2] ...
    #      (note that commandstring may include ksh symbols |, >, <, >&, etc)
    #
    # Optional arguments:
    #   -outvar var      variable to receive stdout of command
    #   -errvar var      variable to receive stderr of command
    #   -outerr var      variable to receive stdout or stderr (success/fail)
    #   -outfp fp        channel to receive stdout of command
    #   -errfp fp        channel to receive stderr of command
    #   -waitvar var     variable to receive return code upon completion
    #                    (must be globabl variable)
    #   -pidlistvar var  variable to receive list of process ids from the
    #                    command string (used with -waitvar)
    #
    # Return code:
    #   Return code of the execCmd procedure is that of the command executed.
    #   If the procedure does not wait for the command to complete (using the
    #   (-waitvar argument), this procedure will always return 0.
    #
    # Examples:
    #   1. Run command and send output to stdout/stderr channels:
    #          set rc [execCmd "mycommand"]
    #
    #   2. If command return code is 0, saves stdout in result. Else, saves
    #    stderr to result:
    #          set rc [execCmd "mycommand" -outerr result]
    #
    #   3. Output of command sent to channel fp and stderr is captured
    #    in variable result:
    #          set rc [execCmd "mycommand" -outfp fp -errvar result]
    #
    #   4. Execute command in background and capture return code when complete:
    #          global rc
    #          execCmd "mycommand" -outfp result -errfp result -waitvar rc
    #           (do other stuff here)
    #          vwait rc
    #      or:
    #          global rc
    #          set rc ""
    #          execCmd "mycommand" -outfp result -errfp result -waitvar rc
    #          while 1 {
    #              if {$rc != ""} break
    #              (keep doing other stuff here)
    #          }

    global runcmdrc runcmdout runcmderr

    # Parse arguments
    set outfp ""
    set errfp ""
    set outerr 0
    set errvar 0
    set outvar 0
    set waitvar ""
    set runcmdrcvar runcmdrc
    set outvarname ""
    set errvarname ""
    set runcmderrvar runcmderr
    set runcmdoutvar runcmdout
    set pidlistvar ""
    array set var $args
    foreach ix [array names var] {
        switch -- $ix {
            -waitvar {
                set waitvar $var(-waitvar)
                global $waitvar
                set runcmdrcvar $waitvar
            }
            -pidlist {
                set pidlistvar $var(-pidlist)
                upvar $pidlistvar pidlist
            }
            -outerr {
                set outvarname $var(-outerr)
                set outerr 1
                set outvar 1
                set errvar 1
            }
            -errout {
                # Same as outerr
                set outvarname $var(-errout)
                set outerr 1
                set outvar 1
                set errvar 1
            }
            -errvar {
                set errvarname $var(-errvar)
                set errvar 1
            }
            -outvar {
                set outvarname $var(-outvar)
                set outvar 1
            }
            -outfp {set outfp $var(-outfp)}
            -errfp {set errfp $var(-errfp)}
        }
    }

    if {$waitvar != ""} {
        # If waitvar is set and user provided variables to store the
        # output in, must define them as globals
        if {$outvar} {
            global $outvarname
            set runcmdoutvar $outvarname
        }
        if {$errvar && !$outerr} {
            global $errvarname
            set runcmderrvar $errvarname
        }
        if {$outerr} {
            set runcmderrvar $outvarname
        }
    }

    unset args var
    set $runcmdrcvar ""
    set $outvarname ""
    set $errvarname ""
    set runcmderr ""
    set runcmdout ""

    # If no options provided, output to stdout and stderr
    if {$outfp == "" && $outvar == 0} {set outfp stdout}
    if {$errfp == "" && $errvar == 0} {set errfp stderr}

    pipe runcmdrpipe runcmdwpipe
    fconfigure $runcmdrpipe -blocking 0 -buffering line
    fconfigure $runcmdwpipe -blocking 0 -buffering line
    fileevent $runcmdrpipe readable [list readExecCmd $runcmdrpipe $errfp\
        $runcmdrcvar $runcmdrpipe $runcmdwpipe $runcmderrvar]

    if [catch {open "|$commandstring 2>@$runcmdwpipe"} runcmdfp] {
        if {$errfp != ""} {puts $errfp $runcmdfp}
        {set $errvarname $runcmdfp}
        return 1
    } else {
        # Set callers variable to list of process ids of this command
        if {$pidlistvar != ""} {
            set $pidlistvar [pid $runcmdfp]
        }

        # Call readExecCmd proc to capture command output.
        fconfigure $runcmdfp -blocking 0 -buffering none
        fileevent $runcmdfp readable [list readExecCmd $runcmdfp $outfp\
            $runcmdrcvar $runcmdrpipe $runcmdwpipe $runcmdoutvar]

        if {$waitvar != ""} {
            # Calling proc doesnt want to wait until the command completes.
            # Caller will watch for the return code to be set in the
            # waitvar variable.
            return 0
        }

        # Wait for command to complete
        vwait $runcmdrcvar
    }

    if {$outerr} {
        # Set output to either stdout or stderr depending on the return
        # code of the command
        catch {unset cmdresult}
        upvar $outvarname cmdresult
        if {$runcmdrc} {
            set cmdresult $runcmderr
        } else {
            set cmdresult $runcmdout
        }
    } else {
        # If user provided variables for stdout and stderr of command,
        # set those variables to the output
        catch {unset cmdresult}
        if {$outvar} {
            upvar $outvarname cmdresult
            set cmdresult $runcmdout
        }
        if {$errvar} {
            upvar $errvarname cmdresult
            set cmdresult $runcmderr
        }
    }

    # Return with return code of the command
    return $runcmdrc
}

proc readExecCmd {fp outfp rcvar rpipe wpipe {outvar ""}} {
    # Read output from command and save in variables or send to
    # channels. Capture exit code of command upon completion.
    global errorCode $rcvar
    if {$outvar != ""} {
        global $outvar
    }
    if [eof $fp] {
        # Process no more fileevents for stdout
        catch {fileevent $fp readable {}}

        # Process remaining fileevents in case there is stderr pending,
        # then close stderr channel
        update
        catch {fileevent $rpipe readable {}}
        catch {close $rpipe}
        catch {close $wpipe}

        # Close stdout channel and get command return code
        set errorCode ""
        fconfigure $fp -blocking 1
        catch {close $fp}
        set rc [lindex $errorCode 2]
        if [string match SIG* $rc] {
            set rc 99
        }
        if {$rc == ""} {set rc 0}
        set $rcvar $rc
        return
    }

    if {[gets $fp line] < 0} return

    if {$outfp != ""} {
        # Send output to channel
        puts $outfp $line
    }
    if {$outvar != ""} {
        # Append output to output variable
        if {[returnVar $outvar] == ""} {
            set $outvar $line
        } else {
            append $outvar "\n$line"
        }
    }
}