2

I have a C shell that usually calls Tcl routines using Tcl_Eval. Normally I was fine with just executing what the user typed and getting some status as a result. However, now I need to receive the actual stdio output from the command that user typed. Is there any way to get it using the Tcl C procedures?

As a side note: I need to figure out the list of current procedures available in the Tcl interpreter, both built in and user sourced. Basically, the output from info procs *.

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
ilya1725
  • 4,496
  • 7
  • 43
  • 68
  • Intercepting stdout from a process is quite intrusive. The Tcl-ish way to do it is to simply _not_ intercept it (and just grab the result instead), or if you have to intercept, to run the code in a subprocess. – Donal Fellows Jan 14 '13 at 23:08

1 Answers1

1

I think you could go like this:

  1. Create a pipe by calling pipe(2).
  2. Then in your interp:

    1. Close stdout by calling Tcl_Close() on it.
    2. Turn the write-end file descriptor of your pipe into Tcl's stdout channel by calling Tcl_MakeChannel() right after closing stdout.

    Or use just replace the stdout with a call to Tcl_SetStdChannel().

  3. Process the data coming from the pipe.

As to your side note — I think you could just call Tcl_Eval() in your interpreter and process the returned list using the list-processing functions from the Tcl API.

Update (from one of my comments): after some more thought I think it might be possible to just create a custom Tcl channel which implementation would just save away the data written to it and then register an instance of such a channel as stdout. See Tcl_CreateChannel() and Tcl_RegisterChannel().

kostix
  • 51,517
  • 14
  • 93
  • 176
  • Processing lists sounds a lot simpler. But how will it really work? `Tcl_Eval` just returns status. Which command should I use to get the returned list? – ilya1725 Jan 14 '13 at 19:53
  • @ilya1725, `Tcl_GetObjResult()`. – kostix Jan 14 '13 at 21:49
  • @ilya1725, after some more thought I think it might be possible to just create a custom Tcl channel which implementation would just save away the data written to it and then register an instance of such a channel as `stdout`. See `Tcl_CreateChannel()` and `Tcl_RegisterChannel()`. – kostix Jan 14 '13 at 21:53
  • Is there any way to prevent the result from being echoed to the stdout? – ilya1725 Jan 15 '13 at 19:25
  • @ilya1725, I don't understand this question: if you subverted the `stdout` channel, the output of the procs in your interp cannot be echoed to `stdout` kind of by definition. So what do you really mean? What did you do to capture output to `stdout` of your procs? Or may be they're writing to `stderr` instead? – kostix Jan 15 '13 at 23:10
  • Is there a limitation on how large a string can `Tcl_GetStringResult` return? Sorry, guys, but the documentation is not very exact. – ilya1725 Jan 16 '13 at 02:49
  • @ilya1725, wherever a Tcl API call returns a C string, it's guaranteed to be terminated using the NUL-terminator standard for C (a byte with code 0x00), so the practical limit on the length of this string is dictated by the OS and current runtime conditions. – kostix Jan 16 '13 at 10:18
  • @ilya1725, note that if you're about to deal with the output of `info procs *` call, you're trying to do it wrong: you have the full power of the Tcl API at your disposal, so use the `Tcl_GetObjResult()` (which would return a Tcl list) and then use the [`Tcl_ListObj*()`](http://www.tcl.tk/man/tcl8.5/TclLib/ListObj.htm) API calls on this result. Retrieve a string representation only of individual objects representing the elements in the returned list. – kostix Jan 16 '13 at 10:22
  • Oh, nice. Yes, using lists is much better. Thanks, @kostix. – ilya1725 Jan 16 '13 at 21:32
  • @ilya1725, you should probably take this as a rule of thumb: the "everything is a string" dogma was only true up to a certain (very old) release of Tcl. Since long ago, it's "everything is representable as a string", and all Tcl values have "native" internal representations, so if possible, always use those parts of Tcl API dealing with `Tcl_Obj`s -- this is both more convenient and faster. – kostix Jan 17 '13 at 09:27
  • @kostix A question about side note: How one can get returned list from Tcl_Eval? The return type of it is int which indicates to error. – Ashot May 02 '13 at 08:38
  • @Ashot, as I've said in my second comment to this answer, one should use [`Tcl_GetObjResult()`](http://www.tcl.tk/man/tcl8.6/TclLib/SetResult.htm). – kostix May 03 '13 at 11:18
  • @kostix Do you mean that after calling `Tcl_eval(interp, "puts hello")` and `Tcl_Obj* obj = Tcl_GetObjResult(interp)` I will get an object containing "hello"? – Ashot May 03 '13 at 11:22
  • @Ashot, to make it more clear: at the C-level, each Tcl command sets the result of its work (either the actual result or an error message) using the special singleton "result object" maintained by each Tcl interpreter, and then returns one of the standard (like `TCL_OK`, `TCL_ERROR`, `TCL_BREAK` etc) or custom return codes signalizing the next command in the sequence how to interpret the object it left in the interp result. – kostix May 03 '13 at 11:23
  • @kostix So you are saying that if we close stdout of interpreter, and then evaluate something which needs to output, that will be an error, Interpreter will put the error in its result, and we can get the output from interp->result? – Ashot May 03 '13 at 11:29
  • @Ashot, I'm not saying that! Please note very well that *the result* of a Tcl command, its *return code* and what it prints to *any* channel are unrelated things. Say, the `puts stdout foo` command, if succeeds, 1) resets the interp result; 2) returns `TCL_OK`, and sends the string `foo` to whatever is currently registered as the `stdout` channel. If you employ any of the techniques to subverting `stdout`, *you* are in charge of dealing with what is sent to your code by commands writing to `stdout`. This has nothing to do with what `Tcl_Eval()` returns or how it sets the interp result. – kostix May 03 '13 at 11:56
  • @Ashot, say, you create a custom channel, register it as `stdout` and then run `Tcl_Eval()` -- your channel driver will receive everything the commands of the evaluated script send to `stdout` *unless* some of the commands reopens (or closes) `stdout`. – kostix May 03 '13 at 11:59
  • @kostix So how you are getting the output of `puts stdout foo` using interpreter's result? You stated that you can do it in side note part of you answer. – Ashot May 03 '13 at 11:59
  • @Ashot, I did not state that. Ilya was dealing with a call to `info procs *` which *does not* write anything to any channel and rather puts a list of strings to the interp result; so I showed him what's the best way to get hold on it. Please understand, that writing things to a channel (an `stdout` (which may be connected to a an interactive console or may be not), a file, a network socket) is a pure side effect, and it has nothing to do with the *result* of running a Tcl command. I tried to demonstrate this using that `puts` example -- please re-read it until this idea really sinks in. – kostix May 03 '13 at 17:38
  • @kostix I got it, thanks for such detailed explanations. Can you please answer this question too? http://stackoverflow.com/questions/16063303/redirect-tcl-stdout-to-file-using-tcl-c-api. – Ashot May 03 '13 at 17:55