0

I'm not a Tcl programmer, but I need to modify a Tcl script that invokes an external command and tries to separate stdout and stderr. The following is a minimal example of how the script currently does this.

#!/usr/bin/tclsh8.4

set pipe [open "|cmd" r]
while {[gets $pipe line] >= 0} {puts $line}
catch "close $pipe" errorMsg
puts "$errorMsg"

Here, cmd is a an external command, and for the sake of this example, I will replace it with the following shell script. (I'm working on a Linux machine, but you can modify this to write to stdout and stderr however is appropriate for your system.)

#!/bin/sh -f
echo "A" > /dev/stdout
echo "B" > /dev/stdout
echo "C" > /dev/stderr
echo "D" > /dev/stderr

When I execute cmd, I get the following four lines as expected:

 % ./cmd
A
B
C
D

However, when I execute my Tcl script, I get:

 % ./test.tcl
A
B
D

This is an example of a more general phenomenon, which is that catch seems to swallow all but the last line of stderr.

To me, the "obvious" way to approach this is to try to mimic what is happening with stdout, which obviously works and prints all lines of the output. However, the current implementation is based on getting a Tcl channel by using open "|cmd", which requires running an external command. I can't figure out how to create a channel without opening an external command, and even if I could figure that out, there are subsequent issues with this approach. (How do I get the output of close into the channel? And if I need to open a new channel to get the output of each channel I am closing, then wouldn't I need an infinite number of channels?)

If anyone has any idea why errorMsg drops the initial lines or another approach that does not suffer from this problem, please let me know.

I know that this will come up, so I will say in advance that switching to Tcl 8.5 is probably not an option for me in the short term, since I do not control the environment in which this script is run.

sasquires
  • 356
  • 3
  • 15
  • 1
    Interestingly, it works as expected if you change the last line of your cmd script to `echo "D" >> /dev/stderr`. – Schelte Bron Nov 25 '19 at 19:54
  • @SchelteBron That's fascinating but somewhat bizarre, since I didn't think that the difference between `>` and `>>` mattered for `stdout` and `stderr`. This suggests that I may be able to fix the behavior by changing `cmd` rather than the Tcl script, which in my case is actually easier for me. – sasquires Nov 25 '19 at 20:28
  • I'm reading a bit more about how this works, and it's possible that this is a case of an interesting interaction between how the unix filesystem treats file descriptors for `stderr` with how Tcl stores the file descriptor. – sasquires Nov 25 '19 at 20:36
  • This is not related to Tcl, but rather Bash. See the discussions at https://stackoverflow.com/questions/2990414/echo-that-outputs-to-stderr. – mrcalvin Nov 25 '19 at 21:56
  • @mrcalvin For reasons that I do not control, the tool `cmd` actually has to be in `csh`, not `bash`, and this answer does not apply. Since I only need this to work on a particular Linux platform, redirecting to `/dev/stderr` is a good solution for the shell side of things. But I understand your point. The ultimate reason is because `> /dev/stderr` replaces the file descriptor for `stderr` with another one, and (unlike the terminal) Tcl doesn't automatically figure this out. As SchelteBron pointed out, this can be fixed using `>> /dev/stderr` where applicable. – sasquires Nov 25 '19 at 22:05
  • 1
    Well, then you should change your cmd example to use `csh`, actually. Still, it remains a problem of the executed shell scripts, not Tcl related. – mrcalvin Nov 25 '19 at 22:27
  • @mrcalvin Sure, when I posted the question, I didn't think that was relevant. I was only explaining to you after the fact that I can't use `>&2 echo`. – sasquires Nov 25 '19 at 23:57
  • So, you might want to update the question (`csh`) and provide a self-answer. – mrcalvin Nov 26 '19 at 22:36
  • @mrcalvin 1. The code I presented is completely equivalent using `csh` and `sh`. The only reason that `csh` came up at all is because you suggested a different syntax (rather than a different *operation*) in the shell script, which is entirely irrelevant to the question. 2. I am not confident enough in my understanding of the underlying mechanics of the filesystem to provide a definitive answer, since I am by no means an expert on this. (In the short term future, I will just hold out hope that someone will post a link to a clear explanation.) – sasquires Nov 27 '19 at 22:08

0 Answers0