-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathtouch_if
executable file
·144 lines (112 loc) · 3.6 KB
/
touch_if
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#! /usr/bin/perl
use strict;
use warnings;
use Errno qw();
use Getopt::Long;
use Pod::Usage;
our $VERSION = '0.01';
my (
$opt_help, $opt_version, $opt_touch_file, $opt_ignore_case, $opt_match_str,
$opt_pipe);
my $opt_span = 0;
my $opt_threshold = 1;
GetOptions(
help => \$opt_help,
version => \$opt_version,
'file=s' => \$opt_touch_file,
'ignore-case' => \$opt_ignore_case,
'match=s' => \$opt_match_str,
'pipe' => \$opt_pipe,
'span=i' => \$opt_span,
'threshold=i' => \$opt_threshold,
) or pod2usage(1);
if ($opt_help) {
pod2usage(0);
} elsif ($opt_version) {
print "$VERSION\n";
exit 0;
}
die "mandatory option: --file (-f) is missing\n"
unless defined $opt_touch_file;
die "mandatory option: --match (-m) is missing\n"
unless defined $opt_match_str;
# prepare compiled expr
my $match;
eval {
$match = $opt_ignore_case ? qr/$opt_match_str/i : qr/$opt_match_str/;
};
die $@ if $@;
# prepare output handle
my $fh;
if ($opt_pipe) {
die "no command\n"
unless @ARGV;
pipe my $rfh, $fh
or die "failed to create pipe:$!";
unless (my $pid = fork) {
die "fork failed:$!"
unless defined $pid;
# child process
close $fh;
open STDIN, '<&', $rfh
or die "failed to redirect piped input to stdin:$!";
exec @ARGV;
die "failed to execute:$ARGV[0]:$!";
}
} else {
die "too many arguments\n"
if @ARGV > 1;
if (@ARGV == 0 || $ARGV[0] eq '-') {
$fh = \*STDOUT;
} else {
open $fh, '>>', $ARGV[0]
or die "failed to open file:$ARGV[0]:$!";
}
}
# main loop
$| = 1;
my @match_at = map { 0 } (1 .. $opt_threshold);
while (my $l = <STDIN>) {
print $fh $l;
if ($l =~ /$match/) {
my $now = time;
shift @match_at;
push @match_at, $now;
if ($match_at[0] + $opt_span >= $now) {
utime undef, undef, $opt_touch_file or do {
if ($! == Errno::ENOENT) {
open my $fh, '>', $opt_touch_file
or die "failed to create file:$opt_touch_file:$!";
} else {
die "failed to touch file:$opt_touch_file:$!";
}
};
}
}
}
__END__
=head1 NAME
touch_if - touch another file if regexp matches, while writing STDIN to file
=head1 SYNOPSIS
touch_if [options] out_file
touch_if [options] -p -- pipe_cmd pipe_args...
# common usage (in httpd.conf)
CustomLog "| touch_if -m ' 50[23] [^ ]+$' -f is_down access_log" common
=over 4
=item -f file_to_touch, --file=file_to_touch (mandatory)
file to be touched when a match (or matches) has been found
=item -m regexp, --match=regexp (mandatory)
regular expression to be applied against each line of input
=item -i, --ignore-case
ignore case
=item -p, --pipe
if not set, touch_if writes the input into out_file. If set, arguments are treated as a command to spawn to handle the input.
=item -s seconds, --span=seconds (default: 0)
=item -t count, --threshold=count (default: 1)
touches file if and only if the match has been found more than `count' times within `seconds' seconds
=head1 DESCRIPTION
Touch_if is a script that writes input from STDIN to file, but while doing so, tests each line of the input against given regular expression, and if it matches, touches another file.
It is useful for detecting errors from log output. Other processes (like crontab tasks) should be used to check the last-modified date of the touched file and send alerts to administrators.
=head1 AUTHOR
Kazuho Oku
=cut