-3

Is there a simple way in Perl to send STDOUT or STDERR to multiple places without forking, using File::Tee, or opening a pipe to /usr/bin/tee?

Surely there is a way to do this in pure perl without writing 20+ lines of code, right? What am I missing? Similar questions have been asked, both here on SO and elsewhere, but none of the answers satisfy the requirements that I not have to

  • fork
  • use File::Tee / IO::Tee / some other module+dependencies whose code footprint is 1000x larger than my actual script
  • open a pipe to the actual tee command

I can see the use of a Core module as a tradeoff here, but really is that needed?

RaWkStAr
  • 3
  • 1
  • 5
  • 1
    Well, you can copy it manually if you insist on that. Redirect the standard streams, capture their contents, then put that to multiple places. You can pack that in a couple of neat subroutines. Why these requirements? What you are asking for isn't as simple as your question makes it sound. – zdim Apr 28 '16 at 18:32
  • @ikegami ...which is exactly what I said I wish to avoid... – RaWkStAr Apr 28 '16 at 18:34
  • Why do you want to do that? What are you trying to accomplish? – Rob K Apr 28 '16 at 18:34
  • @zdim it looks like you might be correct. The simplicity of the `tee` command must belie the intricacies of whatever makes this so complicated. – RaWkStAr Apr 28 '16 at 18:35
  • The use of tee is simple. The program itself must do a bit of work. – zdim Apr 28 '16 at 18:36
  • Ah, nevermind, misread. – ikegami Apr 28 '16 at 18:38
  • Why do you think the modules are larger than they need to be? – ikegami Apr 28 '16 at 18:39
  • What is the problem with using `tee`? – ikegami Apr 28 '16 at 18:39
  • If my script is only 20-30 lines long, for example, using PerlIO::Util (which appears to be broken right now) or IO::Tee would greatly outweigh my own code footprint--by orders of magnitude. It's difficult to think that IO redirection requires me to load so much code. The old-school Perl Cookbook has examples of duping and aliasing filehandles which you can accomplish in less than 3 lines of code. I guess I just didn't think this would require so much more effort. It seems, at least conceptually, a simple step beyond this: – RaWkStAr Apr 28 '16 at 18:44
  • `open my $log_handle, '>>', $log_file or die $!` ; `open STDERR, '>&', $log_handle or die $!;` – RaWkStAr Apr 28 '16 at 18:45
  • 1
    Speaking of modules and lines of code, we can't have our cake and eat it too. In order for them to be of general use there must be far more code than just to solve your or my immediate need. If you must do otherwise, do exactly what you say you know: a few lines of code in a subroutine. Note that you can also use `select` to switch between destinations for your prints. – zdim Apr 28 '16 at 18:49
  • 1
    I didn't ask how big the modules are compared to your script. I asked why you think they're bigger than they should be, since you're asking us for a shorter version of them – ikegami Apr 28 '16 at 18:52
  • Your example does the opposite of what you want: creates two handles that writes to one place. That's easy. What you actually asked to do is have code execute when you write to a file handle, and that code should write to two other handles when called. – ikegami Apr 28 '16 at 18:53
  • @ikegami my example above was the continuation of the proceeding comment. It's basically a half-way-there construct that I'm using which only allows me to redirect STDERR to a file as opposed to preserving STDERR and also sending it to a file (tee). – RaWkStAr Apr 28 '16 at 18:57
  • 1
    I know. Read what I said. It's not "halfway there". It's not even related to what you want. Or maybe you don't realize that file handles aren't subroutines that are executed when used? They are just identifiers or data structures – ikegami Apr 28 '16 at 18:59
  • `IO::Tee` is just three pages long without POD. Yes, you can throw away two of those pages dealing with output and filehandle flags and leave only output, but what exactly would you gain from that? FYI and comparison, tee.c is two pages long. – Oleg V. Volkov Apr 28 '16 at 19:02
  • @ikegami I know. I read it too. I think the problem is simply that this isn't simple. :-/ – RaWkStAr Apr 28 '16 at 19:09
  • Wow, OK, two hours later -- I realize I can just trap $SIG{__DIE__} and $SIG{__WARN__} and proxy the error message(s) out to the logs I want and go from there. That is *simple* and will take maybe 15 lines of code. – RaWkStAr Apr 28 '16 at 19:17
  • Good luck outputing all errors from `eval`s in other modules in process. – Oleg V. Volkov Apr 28 '16 at 19:19
  • Trapping signals to redirect output is not simple nor obvious. It's a hack that will lead to unmaintainable code. Small code is not simplicity. False hubris leads down the wrong path. – Rob K Apr 28 '16 at 19:28
  • 1
    Sure, but that's not the question you asked at all. It doesn't affect things printed to STDOUT and only some of the stuff printed to STDERR – ikegami Apr 28 '16 at 19:34
  • Related: http://stackoverflow.com/q/11461794/132382 – pilcrow Apr 28 '16 at 19:58

1 Answers1

-1

It looks like I can simply do this:

BEGIN {
  open my $log, '>>', 'error.log' or die $!;

  $SIG{__WARN__} = sub { print $log @_ and print STDERR @_ };

  $SIG{__DIE__} = sub { warn @_ and exit 1 };
}

This simply and effectively sends most error messages both to the original STDERR and to a log file (apparently stuff trapped in an eval doesn't show up, I'm told). So there are downsides to this, mentioned in the comments. But as mentioned in the original question, the need was specific. This isn't meant for reuse. It's for a simple, small script that will never be more than 100 lines long.

If you are looking for a way to do this that isn't a "hack", the following was adapted from http://grokbase.com/t/perl/beginners/096pcz62bk/redirecting-stderr-with-io-tee

use IO::Tee;

open my $save_stderr, '>&STDERR' or die $!;

close STDERR;

open my $error_log, '>>', 'error.log' or die $!;

*STDERR = IO::Tee->new( $save_stderr, $error_log ) or die $!;
RaWkStAr
  • 3
  • 1
  • 5
  • 1
    It's good that you have found a solution to your problem. Well done. I think few people would consider this a ***simple way*** to avoid using the ordinary methods, but your discovery is invaluable – Borodin Apr 28 '16 at 22:47
  • 3
    This doesn't do even remotely what the question asks for... even though you wrote both. – hobbs Apr 29 '16 at 20:55