bugfixes, features, documentation, examples, new tool
[hband-tools.git] / user-tools / stdmux
blob917b25b3c68e64439ef6076fea4996c7b8277d88
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 =item -o
19 =item -e
21 =item -u, --unbuffered
23 TODO
25 =back
27 =head1 EXIT STATUS
29 stdmux(1) exits with the I<COMMAND>'s exit status.
31 =head1 USAGE EXAMPLE
33 mux_output=`stdmux command`
34 demux() { local prefix=$1; sed -ne "s/^$prefix//p"; }
35 output_text=`echo "$mux_output" | demux 1`
36 error_text=`echo "$mux_output" | demux 2`
38 =cut
41 use Data::Dumper;
42 use Getopt::Long qw/:config no_ignore_case bundling no_getopt_compat/;
43 use POSIX;
44 use Pod::Usage;
47 $FileDesc = {
48 1 => {
49 'prefix' => "1",
51 2 => {
52 'prefix' => "2",
57 GetOptions(
58 'o|stdout=s' => \$FileDesc->{1}->{'prefix'},
59 'e|stderr=s' => \$FileDesc->{2}->{'prefix'},
60 '<>' => sub { unshift @ARGV, @_[0]; die '!FINISH'; },
61 ) or pod2usage(-exitval=>2, -verbose=>99);
63 if(not @ARGV)
65 pod2usage(-exitval=>2, -verbose=>99);
71 sub process_stream
73 my $fdesc = shift;
74 while(1)
76 my $nl = index $fdesc->{'buf'}, "\n";
77 last if $nl == -1;
78 $nl++;
80 my $line = substr $fdesc->{'buf'}, 0, $nl;
81 print $fdesc->{'prefix'}.$line;
83 $fdesc->{'buf'} = substr $fdesc->{'buf'}, $nl;
90 pipe($stdout_r, $stdout_w) or die "$0: pipe: $!\n";
91 pipe($stderr_r, $stderr_w) or die "$0: pipe: $!\n";
94 my $child_pid = fork // die "$0: fork: $!\n";
96 if($child_pid == 0)
98 open STDOUT, '>&', $stdout_w or die "$0: replace stdout: $!\n";
99 open STDERR, '>&', $stderr_w or die "$0: replace stderr: $!\n";
100 select STDERR; $|++;
101 select STDOUT; $|++;
103 exec {$ARGV[0]} @ARGV;
104 my ($errno, $errstr) = (int $!, $!);
105 warn "$0: ${ARGV[0]}: $errstr\n";
106 exit 125+$errno;
110 my $child_status = undef;
111 close STDIN;
112 close $stdout_w;
113 close $stderr_w;
114 fcntl($stdout_r, F_SETFL, fcntl($stdout_r, F_GETFL, 0) | O_NONBLOCK);
115 fcntl($stderr_r, F_SETFL, fcntl($stderr_r, F_GETFL, 0) | O_NONBLOCK);
116 select STDERR; $|++;
117 select STDOUT; $|++;
121 $FileDesc->{1}->{'fh'} = $stdout_r;
122 $FileDesc->{1}->{'fileno'} = fileno $stdout_r;
123 $FileDesc->{2}->{'fh'} = $stderr_r;
124 $FileDesc->{2}->{'fileno'} = fileno $stderr_r;
128 while(1)
130 $fds = '';
131 for my $fd (keys %$FileDesc)
133 vec($fds, $FileDesc->{$fd}->{'fileno'}, 1) = 1 if defined $FileDesc->{$fd}->{'fh'};
135 last if $fds eq '';
137 $! = 0;
138 select($fds, undef, undef, undef);
139 my $errno = int $!;
141 next if $errno;
143 for my $fd (keys %$FileDesc)
145 my $fdesc = $FileDesc->{$fd};
147 if(vec($fds, $fdesc->{'fileno'}, 1) == 1)
149 my $bytes = sysread $fdesc->{'fh'}, $fdesc->{'buf'}, 1024, length $fdesc->{'buf'};
150 if($bytes)
152 process_stream($fdesc);
154 else
156 # this stream is closed.
157 undef $fdesc->{'fh'};
158 # echo last unterminated line (if any)
159 print $fdesc->{'prefix'}.$fdesc->{'buf'} if length $fdesc->{'buf'};
165 waitpid($child_pid, 0);
166 $child_status = $?;
167 $exit_status = WEXITSTATUS($child_status);
168 $exit_status = 128 + WTERMSIG($child_status) if WIFSIGNALED($child_status);
169 exit $exit_status;