If I was running a command that wanted to spew irrelevant rubbish to stderr that I wanted to suppress, I'd ditch it with 2>/dev/null
.
# The “list” is important! It says “build a list here” instead of direct evaluation
set runcmd [list $SCRIPTS_PATH/config $build_tag -u]
exec {*}$runcmd 2>/dev/null
That would still produce an error if $SCRIPTS_PATH/config
has a non-zero exit code, but that's usually the right thing. To catch it:
if {[catch { exec {*}$runcmd 2>/dev/null }]} {
# An error has been trapped
}
Tcl doesn't provide a way of running a subprocess with its stderr outright closed; that's a pretty strange state to be in really. Redirected to /dev/null is far more likely to be useful and sane.
On the other hand, if you were to want to run with the errors going to the outer Tcl script's stderr without causing an error if those messages are generated (a very irritating feature of exec
at times) — so any error from exec
only comes from the subprocess exit code — that's done with a different redirection, like this:
exec {*}$runcmd 2>@stderr
This is because exec
normally traps the subprocess stderr to use as a source for better error messages. It's all made more complex by the fact that some commands use stderr to print error messages but don't actually error out correctly, yet others write logging information to stderr, and not just error messages. (It's all a bit of a swamp, and its the result of many people hacking together code over a long time. Tcl merely tries to do what it can in this space; I'm not convinced it gets it right, but past design choices are sanctified by the number of scripts that rely on them now.)
What I wouldn't do is put the exec
or the redirection(s) in the list
command invocation; I think that is too confusing. I prefer to keep “things that belong to Tcl” more directly expressed. This is just a matter of taste, but I think it is clearer. It also allows me to think of constructing the command and then firing it off to a subordinate shell for evaluation; the inner syntax is different (shell, not Tcl) but you can then do stuff like this:
set runcmd "$SCRIPTS_PATH/config $build_tag -u"
exec /bin/sh -c $runcmd 2>/dev/null
This is logically pretty equivalent, and better for some things.
If you're using 8.4 (or earlier!) still, you won't be able to use the {*}
syntax. Provided $runcmd
is constructed by list
, you're fine doing this as an alternative.
eval exec $runcmd 2>/dev/null
Theoretically, you should actually write:
eval [list exec] [lrange $runcmd 0 end] [list 2>/dev/null]
But that's awful and the sort of thing that's only necessary when $runcmd
might not be a canonical-format list. (We added the list-expansion syntax in 8.5 because we never wanted to see that sort of monstrosity again, and because we found that being sufficiently careful was hard in all cases, with being slapdash about it leading to bugs.)
If you're using 8.4 and aren't very strictly forced to stick with it, do upgrade to at least 8.5; 8.4 is no longer in support. (We did support it for well over a decade…) 8.5 is pretty strongly compatible with 8.4, but it's worth checking whether your code really works. It pays to be careful. If you hit a problem, ask a question on Stack Overflow about fixing it of course. For all that, my scripts all Just Worked when I migrated them so its worth a try.