new tool: args2env
[hband-tools.git] / user-tools / stdmux
blob7f2fec758d14866ac9f33b3794f9cff2d9a969b2
1 #!/usr/bin/env perl
3 =pod
5 =head1 NAME
7 stdmux - Multiplex the given command's STDOUT and STDERR by prefixing lines
9 =head1 SYNOPSIS
11 stdmux [-o I<STDOUT_PREFIX> | -e I<STDERR_PREFIX>] [--] I<COMMAND> [I<ARGS>]
13 =head1 OPTIONS
15 =over 4
17 =back
19 =head1 EXIT STATUS
21 stdmux(1) exits with the I<COMMAND>'s exit status.
23 =head1 USAGE EXAMPLE
25 mux_output=`stdmux command`
26 demux() { local prefix=$1; sed -ne "s/^$prefix//p"; }
27 output_text=`echo "$mux_output" | demux 1`
28 error_text=`echo "$mux_output" | demux 2`
30 =cut
33 use Getopt::Long qw/:config no_ignore_case bundling no_getopt_compat/;
34 use POSIX;
35 use Pod::Usage;
38 $FileDesc = {
39 1 => {
40 'prefix' => "1",
42 2 => {
43 'prefix' => "2",
48 GetOptions(
49 'o|stdout=s' => \$FileDesc->{1}->{'prefix'},
50 'e|stderr=s' => \$FileDesc->{2}->{'prefix'},
51 ) or pod2usage(-exitval=>2, -verbose=>99);
53 if(not @ARGV)
55 pod2usage(-exitval=>2, -verbose=>99);
61 sub process_stream
63 my $fdesc = shift;
64 while(1)
66 my $nl = index $fdesc->{'buf'}, "\n";
67 last if $nl == -1;
68 $nl++;
70 my $line = substr $fdesc->{'buf'}, 0, $nl;
71 print $fdesc->{'prefix'}.$line;
73 $fdesc->{'buf'} = substr $fdesc->{'buf'}, $nl;
80 pipe($stdout_r, $stdout_w) or die "$0: pipe: $!\n";
81 pipe($stderr_r, $stderr_w) or die "$0: pipe: $!\n";
84 my $child_pid = fork // die "$0: fork: $!\n";
86 if($child_pid == 0)
88 open STDOUT, '>&', $stdout_w or die "$0: replace stdout: $!\n";
89 open STDERR, '>&', $stderr_w or die "$0: replace stderr: $!\n";
90 select STDERR; $|++;
91 select STDOUT; $|++;
92 exec {$ARGV[0]} @ARGV;
93 exit 127;
97 my $child_status = undef;
98 close STDIN;
99 close $stdout_w;
100 close $stderr_w;
101 fcntl($stdout_r, F_SETFL, fcntl($stdout_r, F_GETFL, 0) | O_NONBLOCK);
102 fcntl($stderr_r, F_SETFL, fcntl($stderr_r, F_GETFL, 0) | O_NONBLOCK);
103 select STDERR; $|++;
104 select STDOUT; $|++;
108 $FileDesc->{1}->{'fh'} = $stdout_r;
109 $FileDesc->{1}->{'fileno'} = fileno $stdout_r;
110 $FileDesc->{2}->{'fh'} = $stderr_r;
111 $FileDesc->{2}->{'fileno'} = fileno $stderr_r;
115 while(1)
117 $fds = '';
118 for my $fd (keys %$FileDesc)
120 vec($fds, $FileDesc->{$fd}->{'fileno'}, 1) = 1 if defined $FileDesc->{$fd}->{'fh'};
122 last if $fds eq '';
124 $! = 0;
125 select($fds, undef, undef, undef);
126 my $errno = int $!;
128 next if $errno;
130 for my $fd (keys %$FileDesc)
132 my $fdesc = $FileDesc->{$fd};
134 if(vec($fds, $fdesc->{'fileno'}, 1) == 1)
136 my $bytes = sysread $fdesc->{'fh'}, $fdesc->{'buf'}, 1024, length $fdesc->{'buf'};
137 if($bytes)
139 process_stream($fdesc);
141 else
143 # this stream is closed.
144 undef $fdesc->{'fh'};
145 # echo last unterminated line (if any)
146 print $fdesc->{'prefix'}.$fdesc->{'buf'} if length $fdesc->{'buf'};
152 waitpid($child_pid, 0);
153 $child_status = $?;
154 $exit_status = WEXITSTATUS($child_status);
155 $exit_status = 128 + WTERMSIG($child_status) if WIFSIGNALED($child_status);
156 exit $exit_status;