4 use Getopt
::Long qw
/:config no_ignore_case bundling no_getopt_compat/;
5 use Fcntl qw
/F_GETFL F_SETFL O_NONBLOCK/;
20 pop @
{$Filters->{'out'}} while scalar @
{$Filters->{'out'}};
21 pop @
{$Filters->{'err'}} while scalar @
{$Filters->{'err'}};
23 'F|filter-file=s@' => sub { process_filter_file
($_[1]); },
24 'o|po|pass-stdout=s@' => sub { push @
{$Filters->{'out'}}, {'pass'=>1, 'pattern'=>$_[1]}; },
25 'e|pe|pass-stderr=s@' => sub { push @
{$Filters->{'err'}}, {'pass'=>1, 'pattern'=>$_[1]}; },
26 'O|fo|filter-stdout=s@' => sub { push @
{$Filters->{'out'}}, {'pass'=>0, 'pattern'=>$_[1]}; },
27 'E|fe|filter-stderr=s@' => sub { push @
{$Filters->{'err'}}, {'pass'=>0, 'pattern'=>$_[1]}; },
28 'p|pass=s@' => sub { push @
{$Filters->{$_}}, {'pass'=>1, 'pattern'=>$_[1]} for ('out', 'err'); },
29 'f|filter=s@' => sub { push @
{$Filters->{$_}}, {'pass'=>0, 'pattern'=>$_[1]} for ('out', 'err'); },
34 process_args
or pod2usage
(-exitval
=>2, -verbose
=>99);
38 pod2usage
(-exitval
=>2, -verbose
=>99);
42 sub process_filter_file
45 open my $fh, '<', $filepath or die "$filepath: $!\n";
46 my @channels = ('out', 'err');
52 if(/^\[std(out|err)\]$/)
59 @channels = ('out', 'err');
63 $pass = 1 if s/^!\s*//;
65 push @
{$Filters->{$_}}, {'pass'=>$pass, 'pattern'=>$pattern} for @channels;
77 for my $filt (@
$filters)
79 if($line =~ $filt->{'pattern'})
81 $pass = $filt->{'pass'};
91 print {$stream->{'write_fh'}} $line if filter_pass
$line, $stream->{'filter'};
99 my $nl = index $stream->{'buf'}, "\n";
103 my $line = substr $stream->{'buf'}, 0, $nl;
104 process_line
$line, $stream;
106 $stream->{'buf'} = substr $stream->{'buf'}, $nl;
112 pipe($stdout_r, $stdout_w) or die "$0: pipe: $!\n";
113 pipe($stderr_r, $stderr_w) or die "$0: pipe: $!\n";
116 my $child_pid = fork // die "$0: fork: $!\n";
120 open STDOUT
, '>&', $stdout_w or die "$0: replace stdout: $!\n";
121 open STDERR
, '>&', $stderr_w or die "$0: replace stderr: $!\n";
125 exec {$ARGV[0]} @ARGV;
126 my ($errno, $errstr) = (int $!, $!);
127 warn "$0: ${ARGV[0]}: $errstr\n";
132 my $child_status = undef;
136 fcntl($stdout_r, F_SETFL
, fcntl($stdout_r, F_GETFL
, 0) | O_NONBLOCK
);
137 fcntl($stderr_r, F_SETFL
, fcntl($stderr_r, F_GETFL
, 0) | O_NONBLOCK
);
145 'write_fh' => \
*STDOUT
,
146 'fd' => fileno $stdout_r,
148 'filter' => $Filters->{'out'},
152 'write_fh' => \
*STDERR
,
153 'fd' => fileno $stderr_r,
155 'filter' => $Filters->{'err'},
160 $SIG{'HUP'} = sub { $do_reload = 1; };
166 for my $n ('out', 'err')
168 vec($fds, $pipe->{$n}->{'fd'}, 1) = 1 if defined $pipe->{$n}->{'fh'};
173 select($fds, undef, undef, undef);
184 for my $n ('out', 'err')
186 if(vec($fds, $pipe->{$n}->{'fd'}, 1) == 1)
188 my $bytes = sysread $pipe->{$n}->{'fh'}, $pipe->{$n}->{'buf'}, 1024, length $pipe->{$n}->{'buf'};
191 process_stream
($pipe->{$n});
195 # this stream is closed.
196 undef $pipe->{$n}->{'fh'};
197 # process last unterminated line (if any)
198 process_line
($pipe->{$n}->{'buf'}, $pipe->{$n}) if length $pipe->{$n}->{'buf'};
204 waitpid($child_pid, 0);
206 $exit_status = WEXITSTATUS
($child_status);
207 $exit_status = 128 + WTERMSIG
($child_status) if WIFSIGNALED
($child_status);
217 stdfilt - Run a command but filter its STDOUT and STDERR
221 stdfilt [I<OPTIONS>] [--] I<COMMAND> [I<ARGS>]
227 =item -F, --filter-file I<FILE>
229 =item -o, -po, --pass-stdout I<PATTERN>
231 =item -e, -pe, --pass-stderr I<PATTERN>
233 =item -p, --pass I<PATTERN>
235 =item -O, -fo, --filter-stdout I<PATTERN>
237 =item -E, -fe, --filter-stderr I<PATTERN>
239 =item -f, --filter I<PATTERN>
245 Run I<COMMAND> and match each of its output lines (both stdout and stderr separately) against
246 the given perl-compatible regexp (see perlre(1)) patterns.
248 =head1 FILTER FILE FORMAT
250 Empty and comment lines (starting with C<#> hashmark) are ignored as well as leading whitespace.
251 Each line is either a filter pattern,
252 or the literal string C<[stdout]>, C<[stderr]>, or C<[*]>,
253 which indicates which standard fd will be filtered through the subsequent patterns.
254 If not indicated, C<[*]> (all fd) is implied.
256 A pattern may start with an C<!> exclamation mark, in which case, the lines matching it,
257 will be passed, not filtered.
261 HUP - re-read filter files given at command line
265 grep(1), stdbuf(1), logwall(8), perlre(1)