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" } } }